test webpack
This commit is contained in:
parent
fb6b310a49
commit
488f3a56b9
2
.gitignore
vendored
2
.gitignore
vendored
@ -23,3 +23,5 @@ go.work
|
||||
|
||||
/__debug_bin
|
||||
/.env
|
||||
|
||||
front/node_modules/**
|
11
front/.babelrc
Normal file
11
front/.babelrc
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"plugins": ["@babel/syntax-dynamic-import"],
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"modules": false
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
15
front/README.md
Normal file
15
front/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# 🚀 Welcome to your new awesome project!
|
||||
|
||||
This project has been created using **webpack-cli**, you can now run
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
yarn build
|
||||
```
|
||||
|
||||
to bundle your application
|
12
front/index.html
Normal file
12
front/index.html
Normal file
@ -0,0 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Webpack App</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello world!</h1>
|
||||
<h2>Tip: Check your console</h2>
|
||||
</body>
|
||||
|
||||
</html>
|
11428
front/package-lock.json
generated
Normal file
11428
front/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
front/package.json
Normal file
26
front/package.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.2",
|
||||
"@babel/preset-env": "^7.20.2",
|
||||
"@webpack-cli/generators": "^3.0.7",
|
||||
"babel-loader": "^9.1.0",
|
||||
"css-loader": "^6.7.2",
|
||||
"less": "^4.1.3",
|
||||
"less-loader": "^11.1.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"webpack": "^5.88.2",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^4.11.1"
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"description": "My webpack project",
|
||||
"name": "my-webpack-project",
|
||||
"scripts": {
|
||||
"build": "webpack --mode=production --node-env=production",
|
||||
"build:dev": "webpack --mode=development",
|
||||
"build:prod": "webpack --mode=production --node-env=production",
|
||||
"watch": "webpack --watch",
|
||||
"watch:prod": "webpack --watch --mode=production --node-env=production",
|
||||
"serve": "webpack serve"
|
||||
}
|
||||
}
|
56
front/src/decorat/desktop-decorat.js
Normal file
56
front/src/decorat/desktop-decorat.js
Normal file
@ -0,0 +1,56 @@
|
||||
export default class DesktopDecorat{
|
||||
// static windowsLayer
|
||||
/** @type {Element} */
|
||||
static windowLayer
|
||||
|
||||
constructor(){
|
||||
this.windowLayer = document.body.querySelector('#windows-layer')
|
||||
DesktopDecorat.windowsLayer = document.body.querySelector('#windows-layer')
|
||||
if (!WebDesktopEnvironment.isMobile) {
|
||||
let startDrag = function(event) {
|
||||
let window = event.target.closest('.WindowFrame')
|
||||
DesktopDecorat.bringWindowToFront(window)
|
||||
if (event.target.classList.contains("DragArea")){
|
||||
let xPosInit = event.offsetX
|
||||
let yPosInit = event.offsetY
|
||||
let dragWindow = function(event){
|
||||
window.style.left = `${event.clientX - xPosInit}px`
|
||||
window.style.top = `${event.clientY - yPosInit}px`
|
||||
}
|
||||
let stopDrag = function(){
|
||||
removeEventListener('mousemove', dragWindow)
|
||||
removeEventListener('mouseup', stopDrag)
|
||||
}
|
||||
|
||||
addEventListener('mousemove', dragWindow)
|
||||
addEventListener('mouseup', stopDrag)
|
||||
}
|
||||
}
|
||||
DesktopDecorat.windowsLayer.addEventListener('mousedown', startDrag)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} window
|
||||
*/
|
||||
static bringWindowToFront(window){ //FIXME
|
||||
let previousWindow = DesktopDecorat.windowsLayer.lastChild
|
||||
if (window == null || window == undefined){
|
||||
return
|
||||
}
|
||||
if (window != previousWindow){
|
||||
DesktopDecorat.windowsLayer.insertBefore(window, previousWindow.nextSibling)
|
||||
previousWindow.classList.remove("Focused")
|
||||
window.classList.add("Focused")
|
||||
} else {
|
||||
window.classList.add("Focused")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
static ChangeURL(appWindow){
|
||||
let appId = appWindow.getAttribute('appid')
|
||||
window.history.replaceState(null, "", `/${appId}/`);
|
||||
}
|
||||
}
|
3
front/src/decorat/mobile-decorat.js
Normal file
3
front/src/decorat/mobile-decorat.js
Normal file
@ -0,0 +1,3 @@
|
||||
export default class MobileDecorat {
|
||||
|
||||
}
|
4
front/src/desktop.js
Normal file
4
front/src/desktop.js
Normal file
@ -0,0 +1,4 @@
|
||||
import WebDesktopEnvironment from "./wde/wde-desktop";
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
let wde = new WebDesktopEnvironment()
|
||||
}, false);
|
6
front/src/init.js
Normal file
6
front/src/init.js
Normal file
@ -0,0 +1,6 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log("init")
|
||||
|
||||
}, false);
|
||||
|
||||
|
5
front/src/mobile.js
Normal file
5
front/src/mobile.js
Normal file
@ -0,0 +1,5 @@
|
||||
import MobileWebDesktopEnvironment from "./wde/wde-mobile";
|
||||
require("./mobile.less")
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
let wde = new MobileWebDesktopEnvironment()
|
||||
}, false);
|
4
front/src/mobile.less
Normal file
4
front/src/mobile.less
Normal file
@ -0,0 +1,4 @@
|
||||
// @import "./wde/desktop/mobile-desktop.less"
|
||||
.body{
|
||||
background-color: aqua;
|
||||
}
|
0
front/src/wde/desktop/desktop.js
Normal file
0
front/src/wde/desktop/desktop.js
Normal file
3
front/src/wde/desktop/mobile-desktop.js
Normal file
3
front/src/wde/desktop/mobile-desktop.js
Normal file
@ -0,0 +1,3 @@
|
||||
export default class MobileDesktop{
|
||||
|
||||
}
|
3
front/src/wde/desktop/mobile-desktop.less
Normal file
3
front/src/wde/desktop/mobile-desktop.less
Normal file
@ -0,0 +1,3 @@
|
||||
.test{
|
||||
background-color: aqua;
|
||||
}
|
54
front/src/wde/scrollbar/scrollbar.js
Normal file
54
front/src/wde/scrollbar/scrollbar.js
Normal file
@ -0,0 +1,54 @@
|
||||
export default class WdeScrollBar{
|
||||
/**
|
||||
* @param {HTMLElement} scrollBarContainer
|
||||
* @param {HTMLElement} content
|
||||
*/
|
||||
constructor(scrollBarContainer, content){
|
||||
let nonNativeScroll = false
|
||||
// console.log(scrollBarContainer, content)
|
||||
// let handler = scrollBarContainer.children[0]
|
||||
//TODO On scroll move focus on window?
|
||||
let handler = scrollBarContainer.querySelector(".ScrollBarScrollElement") //TODO Refactor classes
|
||||
// console.log(handler)
|
||||
|
||||
handler.style.height = (content.clientHeight /content.scrollHeight)* handler.parentElement.clientHeight + 'px'
|
||||
|
||||
let max = handler.parentElement.clientHeight - handler.clientHeight
|
||||
let yPosInit = 0
|
||||
|
||||
handler.addEventListener('mousedown', (event) => {
|
||||
nonNativeScroll = true
|
||||
yPosInit = event.clientY - Number(handler.style.top.replace('px','' ))
|
||||
|
||||
document.addEventListener('mousemove', drag);
|
||||
document.addEventListener('mouseup', stop)
|
||||
})
|
||||
|
||||
content.addEventListener('scroll', (event) =>{
|
||||
if (!this.nonNativeScroll){
|
||||
let handlerPathLength = handler.parentElement.clientHeight - handler.clientHeight //TODO recalculate only on resize event
|
||||
let coefficient = (content.scrollHeight - content.clientHeight) /handlerPathLength
|
||||
handler.style.top = content.scrollTop/coefficient + 'px'
|
||||
}
|
||||
})
|
||||
|
||||
function drag() {
|
||||
// console.log(event.clientY - yPosInit, Number(handler.style.top.replace('px','' )))
|
||||
let pos = event.clientY - yPosInit
|
||||
let clampPos = Math.min(Math.max(pos, 0), max)
|
||||
handler.style.top = clampPos + "px";
|
||||
|
||||
let handlerPathLength = handler.parentElement.clientHeight - handler.clientHeight //TODO recalculate only on resize event
|
||||
let coefficient = (content.scrollHeight - content.clientHeight) /handlerPathLength
|
||||
// console.log(clampPos, coefficient, content.clientHeight, clampPos* coefficient)
|
||||
|
||||
content.scrollTop = clampPos* coefficient
|
||||
}
|
||||
function stop() {
|
||||
// console.log("stop")
|
||||
document.removeEventListener('mousemove', drag);
|
||||
document.removeEventListener('mouseup', stop)
|
||||
nonNativeScroll = false
|
||||
}
|
||||
}
|
||||
}
|
110
front/src/wde/scrollbar/wde-scrollbar.css
Normal file
110
front/src/wde/scrollbar/wde-scrollbar.css
Normal file
@ -0,0 +1,110 @@
|
||||
.scroller {
|
||||
overflow-y: scroll;
|
||||
scrollbar-color: #0A4C95 #C2D2E4;
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
.scroll_content {
|
||||
position: relative;
|
||||
width: 400px;
|
||||
height: 414px;
|
||||
top: -17px;
|
||||
padding: 20px 10px 20px 10px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.ScrollbarPlace{
|
||||
overflow: hidden;
|
||||
|
||||
border-left: 1px solid #555555;
|
||||
width: 14px;
|
||||
height: 100%;
|
||||
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
width: 14px;
|
||||
height: 100%;
|
||||
|
||||
background-color: #EEEEEE;
|
||||
|
||||
|
||||
/* Inside auto layout */
|
||||
flex: none;
|
||||
order: 0;
|
||||
align-self: stretch;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.Focused .ScrollbarPlace{
|
||||
border-left: 1px solid #000000;
|
||||
background-color: #AAAAAA;
|
||||
box-shadow: inset -1px 0px 0px rgba(255, 255, 255, 0.29),
|
||||
inset -2px 0px 0px rgba(255, 255, 255, 0.19),
|
||||
inset 1px 1px 0px rgba(0, 0, 0, 0.14),
|
||||
inset 2px 2px 0px rgba(0, 0, 0, 0.19);
|
||||
}
|
||||
|
||||
.ScrollBarScrollElement{
|
||||
position: relative;
|
||||
visibility: hidden;
|
||||
width: 14px;
|
||||
height: 31px;
|
||||
|
||||
background: #9999FF;
|
||||
|
||||
box-shadow: 0px -1px 0px #000000,
|
||||
0px 1px 0px #000000,
|
||||
0px 2px 0px rgba(0, 0, 0, 0.13),
|
||||
0px 3px 0px rgba(0, 0, 0, 0.19),
|
||||
inset 0px 1px 0px rgba(255, 255, 255, 0.5),
|
||||
inset 1px 0px 0px rgba(255, 255, 255, 0.5),
|
||||
inset -1px -1px 0px rgba(102, 102, 204, 0.91);
|
||||
|
||||
/* Auto layout */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
gap: 5px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.Focused .ScrollBarScrollElement{
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.ScrollBarScrollElementDrag{
|
||||
pointer-events: none;
|
||||
/* background-color: #0A4C95; */
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
margin-left: -1px;
|
||||
|
||||
background: linear-gradient(transparent 0%,#CCCCFF 0%, #CCCCFF 50%, transparent 50%);
|
||||
background-size: 2px 2px;
|
||||
|
||||
/* TODO white pixels in rows start */
|
||||
filter: drop-shadow(1px 1px 0px #333399);
|
||||
}
|
||||
|
||||
/* TODO to wde css */
|
||||
.ScrollContent {
|
||||
/* width: 100%;
|
||||
height: 100%; */
|
||||
overflow: scroll;
|
||||
overflow-x: hidden;
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* Internet Explorer 10+ */
|
||||
|
||||
/* Auto layout */
|
||||
/* display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
padding: 0px; */
|
||||
}
|
||||
|
||||
.ScrollContent::-webkit-scrollbar { /* WebKit */
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
231
front/src/wde/wde-desktop.js
Normal file
231
front/src/wde/wde-desktop.js
Normal file
@ -0,0 +1,231 @@
|
||||
import DesktopDecorat from "../decorat/desktop-decorat";
|
||||
import WdeScrollBar from "./scrollbar/scrollbar";
|
||||
import WebFS from "../web-fs/web-fs";
|
||||
export default class WebDesktopEnvironment{
|
||||
#decorat
|
||||
static Applications = {};
|
||||
static isMobile = false
|
||||
// static decorat
|
||||
static webFs
|
||||
constructor(){
|
||||
document.body.style.setProperty('--zoom', 1)
|
||||
this.#decorat = new DesktopDecorat()
|
||||
|
||||
WebDesktopEnvironment.webFs = new WebFS()
|
||||
// this.loadWDE()
|
||||
}
|
||||
|
||||
async loadWDE(){
|
||||
let autoStart = document.body.querySelector("wde-autostart")
|
||||
if (autoStart == null){
|
||||
WebDesktopEnvironment.Alert("Error in loading DE")
|
||||
return
|
||||
}
|
||||
for (const child of autoStart.children) {
|
||||
if (child.nodeName != "APP") continue
|
||||
let appPath = child.getAttribute("app-path")
|
||||
if (appPath == null) continue
|
||||
|
||||
let args = []
|
||||
let argsRaw = child.querySelector("args")
|
||||
if (argsRaw == null) continue
|
||||
|
||||
for (const argRaw of argsRaw.children) {
|
||||
let arg = argRaw.getAttribute("string")
|
||||
if (arg == null) continue
|
||||
args.push(arg)
|
||||
}
|
||||
|
||||
// console.log(appPath, args)
|
||||
await WebDesktopEnvironment.Open(appPath, args)
|
||||
}
|
||||
|
||||
autoStart.remove()
|
||||
|
||||
// await WebDesktopEnvironment.load2('/Applications/Finder.app', [ "desktop", document.querySelector('#desktop-layer')]) //FIXME
|
||||
// await WebDesktopEnvironment.Open('/Applications/Finder.app', ["/home/user/.desktop", "-desktop", document.querySelector('#desktop-layer')])
|
||||
// await WebDesktopEnvironment.Open('/Applications/Finder.app', ["/home/user/Blogs",])
|
||||
// await WebDesktopEnvironment.Open('/Applications/Finder.app', ["/home/user",])
|
||||
// await WebDesktopEnvironment.Open('/Applications/Finder.app', ["/home",])
|
||||
// WebDesktopEnvironment.Open('/Applications/AboutMe.app', [])
|
||||
// WebDesktopEnvironment.Open('/Applications/FinderAdmin.app', ["/home/user",])
|
||||
// await WebDesktopEnvironment.Open('/Applications/BlogViewer.app', ["/home/user/Blogs/blog1.blog",])
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} appPath
|
||||
* @param {string[]} args
|
||||
* @param {string} runPath
|
||||
*/
|
||||
static async Open(appPath, args, runPath){
|
||||
const appManifest = await WebDesktopEnvironment.fetchApp(appPath)
|
||||
|
||||
if (appManifest === undefined) return //TODO return err
|
||||
const runContext = {
|
||||
isMobile: false,
|
||||
bundlePath: appPath,
|
||||
runPath: runPath //TODO
|
||||
}
|
||||
if (WebDesktopEnvironment.Applications[appManifest.appId] === undefined){
|
||||
WebDesktopEnvironment.load2(appManifest, () =>{
|
||||
WebDesktopEnvironment.Applications[appManifest.appId].NewWindow(args, runContext)
|
||||
})
|
||||
} else {
|
||||
WebDesktopEnvironment.Applications[appManifest.appId].NewWindow(args, runContext)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} appManifest
|
||||
* @param {function} onload callback after script loading
|
||||
*/
|
||||
static async load2(appManifest, onload){
|
||||
let appElem = document.createElement("div")
|
||||
appElem.setAttribute("appId", appManifest.appId)
|
||||
//TODO Render scripts nodes on server
|
||||
//FIXME Not support more than one js now :)
|
||||
let script = document.createElement("script")
|
||||
script.setAttribute("src", appManifest.js[0]) //FIXME path by fs read
|
||||
script.setAttribute("async", "false") //TODO Possible may creates a problems??
|
||||
appElem.appendChild(script)
|
||||
|
||||
document.getElementById("applications").appendChild(appElem)
|
||||
script.addEventListener('load', (event) => {
|
||||
let newApp = eval(`new ${appManifest.appId}()`);
|
||||
//TODO Check if newApp undefined
|
||||
WebDesktopEnvironment.Applications[appManifest.appId] = newApp;
|
||||
onload()
|
||||
})
|
||||
return //TODO return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @returns {Object | undefined} //FIXME
|
||||
*/
|
||||
static async fetchApp(path){
|
||||
// console.log("path: " + path )
|
||||
const params = new URLSearchParams({path: path, mode: "json"})
|
||||
const response = await fetch(`/system/loadApp?` + params)
|
||||
if (response.status != 200){
|
||||
const error = await response.json()
|
||||
WebDesktopEnvironment.Alert(error.message)
|
||||
return undefined
|
||||
}
|
||||
//TODO Validate manifest
|
||||
const appManifest = response.json()
|
||||
return appManifest
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} appId
|
||||
* @param {number} width
|
||||
* @param {number} height
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
static CreateNewWindow(appId, width, height) {
|
||||
let newWindow = document.createElement("div")
|
||||
newWindow.setAttribute('appid', appId)
|
||||
if (WebDesktopEnvironment.isMobile){
|
||||
newWindow.setAttribute("class", "MobileApplicationWindow")
|
||||
document.body.querySelector('#windows-layer').appendChild(newWindow)
|
||||
return newWindow
|
||||
} else {
|
||||
newWindow.setAttribute("class", "WindowFrame ConvexElement")
|
||||
newWindow.setAttribute("windowId", WebDesktopEnvironment.makeid(4)) //TODO:
|
||||
|
||||
newWindow.style.width = width.toString() + "px"
|
||||
newWindow.style.height = height.toString() + "px"
|
||||
|
||||
document.body.querySelector('#windows-layer').appendChild(newWindow)
|
||||
return newWindow
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {HTMLElement} window
|
||||
*/
|
||||
static CloseWindow(window) {
|
||||
window.remove()
|
||||
}
|
||||
|
||||
static CloseFocusedWindow() {
|
||||
if (document.body.querySelector('#windows-layer').childElementCount > 1){
|
||||
document.body.querySelector('#windows-layer').lastElementChild.remove()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} html
|
||||
*/
|
||||
static SetBasicWindow(html){
|
||||
this.basicWindow = html
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
static GetBasicWindow(){
|
||||
return this.basicWindow
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} alertText
|
||||
*/
|
||||
static Alert(alertText){
|
||||
WebDesktopEnvironment.CreateAlertWindow(alertText)
|
||||
console.log(alertText)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} alertText
|
||||
*/
|
||||
static CreateAlertWindow(alertText){
|
||||
let newWindow = document.createElement("div")
|
||||
newWindow.setAttribute("class", "WindowFrameless")
|
||||
newWindow.setAttribute("windowId", "SuperUniqUUID") //TODO:
|
||||
newWindow.style.cssText = "position:absolute;width:450px;height:116px; margin-left: -225px; margin-top:-58px;left: 50%;top: 50%;background-color:#FFFFFF;border: 1px solid #000000;box-shadow: 2px 2px 0px #000000;"
|
||||
|
||||
let alertImage = document.createElement("img")
|
||||
alertImage.setAttribute("src", "/res/sys/wde/icons/ohno.png")
|
||||
alertImage.style.cssText = "position:absolute; width:64px;height:64px;top:15px;left:25px"
|
||||
newWindow.appendChild(alertImage)
|
||||
|
||||
let errorText = document.createElement("div")
|
||||
errorText.style.cssText = "position:absolute; width: 300px; left:128px; top:30px;font-family: 'Virtue';"
|
||||
errorText.innerHTML = alertText
|
||||
newWindow.appendChild(errorText)
|
||||
|
||||
let closeButton = document.createElement("button")
|
||||
closeButton.style.cssText = "position:absolute; left: 382px; bottom: 10px; background-color:#FFFFFF; width: 55px; height:18px; font-family: 'Virtue'; border-radius:4px;border: 1px solid #000000;"
|
||||
closeButton.innerHTML = "Close"
|
||||
closeButton.addEventListener('click', () => { newWindow.remove()})
|
||||
newWindow.appendChild(closeButton)
|
||||
|
||||
document.body.querySelector('#windows-layer').appendChild(newWindow)
|
||||
}
|
||||
|
||||
static CheckMobile(){
|
||||
var check = false;
|
||||
(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera);
|
||||
return check;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {num} length
|
||||
*/
|
||||
static makeid(length) {
|
||||
let result = '';
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
const charactersLength = characters.length;
|
||||
let counter = 0;
|
||||
while (counter < length) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
counter += 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
10
front/src/wde/wde-mobile.js
Normal file
10
front/src/wde/wde-mobile.js
Normal file
@ -0,0 +1,10 @@
|
||||
import MobileDecorat from "../Decorat/mobile-decorat"
|
||||
|
||||
export default class MobileWebDesktopEnvironment{
|
||||
/** @type {MobileDecorat} */
|
||||
#decorat
|
||||
constructor(){
|
||||
document.body.style.setProperty('--zoom', 3)
|
||||
this.#decorat = new MobileDecorat()
|
||||
}
|
||||
}
|
131
front/src/web-fs/web-fs.js
Normal file
131
front/src/web-fs/web-fs.js
Normal file
@ -0,0 +1,131 @@
|
||||
export default class WebFS{
|
||||
/**
|
||||
* @param {string} path
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static async CreateDirectory(path){
|
||||
if (path == undefined){
|
||||
WebDesktopEnvironment.Alert("Path is undefined")
|
||||
return false
|
||||
}
|
||||
const params = new URLSearchParams({
|
||||
path: `${path}/New Directory`
|
||||
})
|
||||
const response = await fetch(`/system/fs/createDir?` + params)
|
||||
if (response.status != 200){
|
||||
WebDesktopEnvironment.Alert("DIRCTORY CREATION ERROR") //TODO
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static async DeleteFile(path){
|
||||
const params = new URLSearchParams({
|
||||
path: path
|
||||
})
|
||||
const response = await fetch(`/system/fs/delete?` + params)
|
||||
if (response.status != 200){
|
||||
WebDesktopEnvironment.Alert("DELETE ERROR") //TODO
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static async MoveFile(sourcePath, targetPath){
|
||||
const params = new URLSearchParams({
|
||||
sourcePath: sourcePath,
|
||||
targetPath: targetPath
|
||||
})
|
||||
const response = await fetch(`/system/fs/move?` + params)
|
||||
if (response.status != 200){
|
||||
// WebDesktopEnvironment.Alert("Move ERROR") //TODO
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} linkPath
|
||||
* @returns {string}
|
||||
*/
|
||||
static async ReadObjectLink(linkPath){
|
||||
const params = new URLSearchParams({
|
||||
linkPath: linkPath,
|
||||
})
|
||||
const response = await fetch(`/system/fs/readObjectLink?` + params)
|
||||
if (response.status != 200){
|
||||
WebDesktopEnvironment.Alert("TODO") //TODO
|
||||
return ""
|
||||
}
|
||||
const path = await response.text()
|
||||
//TODO Validate
|
||||
return path
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} sourcePath
|
||||
* @param {string} linkPath
|
||||
* @returns {string}
|
||||
*/
|
||||
static async CreatePathLink(sourcePath, linkPath){
|
||||
const params = new URLSearchParams({
|
||||
sourcePath: sourcePath,
|
||||
linkPath: linkPath,
|
||||
})
|
||||
const response = await fetch(`/system/fs/createPathLink?` + params)
|
||||
return response.status == 200
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} linkPath
|
||||
* @returns {string}
|
||||
*/
|
||||
static async ReadPathLink(linkPath){
|
||||
const params = new URLSearchParams({
|
||||
linkPath: linkPath,
|
||||
})
|
||||
const response = await fetch(`/system/fs/readPathLink?` + params)
|
||||
if (response.status != 200){
|
||||
WebDesktopEnvironment.Alert("TODO") //TODO
|
||||
return ""
|
||||
}
|
||||
// console.log(response)
|
||||
const file = await response.json()
|
||||
//TODO Validate
|
||||
return file
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {File} file
|
||||
* @param {string} parentPath
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static async UploadFile(file, parentPath){
|
||||
console.log('here')
|
||||
let formData = new FormData()
|
||||
formData.append("file", file) //FIXME Conn reset
|
||||
const params = new URLSearchParams({
|
||||
parentPath: parentPath,
|
||||
})
|
||||
const response = await fetch(`/system/fs/upload?` + params,
|
||||
{
|
||||
method: "POST", //TODO Change to PUT?
|
||||
body: formData
|
||||
})
|
||||
console.log(response.status)
|
||||
if (response.status != 201){
|
||||
const error = await response.json()
|
||||
WebDesktopEnvironment.Alert(error.message)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
66
front/webpack.config.js
Normal file
66
front/webpack.config.js
Normal file
@ -0,0 +1,66 @@
|
||||
// Generated using webpack-cli https://github.com/webpack/webpack-cli
|
||||
|
||||
const path = require('path');
|
||||
|
||||
const isProduction = process.env.NODE_ENV == 'production';
|
||||
|
||||
|
||||
const stylesHandler = 'style-loader';
|
||||
|
||||
|
||||
|
||||
const config = {
|
||||
// entry: [ './src/wde-mobile.js'],
|
||||
entry: {
|
||||
init: './src/init.js',
|
||||
mobile: './src/mobile.js',
|
||||
desktop: './src/desktop.js',
|
||||
},
|
||||
output: {
|
||||
// path: path.resolve(__dirname, 'dist'),
|
||||
path: path.resolve('../res/dev-fs/wde/dist'),
|
||||
filename: '[name].js'
|
||||
},
|
||||
devServer: {
|
||||
open: true,
|
||||
host: 'localhost',
|
||||
},
|
||||
plugins: [
|
||||
// Add your plugins here
|
||||
// Learn more about plugins from https://webpack.js.org/configuration/plugins/
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|jsx)$/i,
|
||||
loader: 'babel-loader',
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: [stylesHandler,'css-loader'],
|
||||
},
|
||||
{
|
||||
test: /\.less$/i,
|
||||
use: [stylesHandler, 'css-loader', 'less-loader'],
|
||||
},
|
||||
{
|
||||
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
|
||||
type: 'asset',
|
||||
},
|
||||
|
||||
// Add your rules for custom modules here
|
||||
// Learn more about loaders from https://webpack.js.org/loaders/
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = () => {
|
||||
if (isProduction) {
|
||||
config.mode = 'production';
|
||||
|
||||
|
||||
} else {
|
||||
config.mode = 'development';
|
||||
}
|
||||
return config;
|
||||
};
|
1
go.mod
1
go.mod
@ -21,6 +21,7 @@ require (
|
||||
require (
|
||||
github.com/bytedance/sonic v1.8.0 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/floresj/go-contrib-mobile v0.0.0-20150714204826-47557cfa26f5
|
||||
github.com/gin-contrib/cors v1.4.0
|
||||
github.com/gin-contrib/location v0.0.2
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
|
2
go.sum
2
go.sum
@ -10,6 +10,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/floresj/go-contrib-mobile v0.0.0-20150714204826-47557cfa26f5 h1:e+OSfbvxkjub+bPlfDmqENiV9IEOnLUk9TU7fMhu2Ks=
|
||||
github.com/floresj/go-contrib-mobile v0.0.0-20150714204826-47557cfa26f5/go.mod h1:2F+cddOTo8Rjp/98JPjMwZRCcCA1dP1VeNBvfk/tU6I=
|
||||
github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=
|
||||
github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs=
|
||||
github.com/gin-contrib/location v0.0.2 h1:QZKh1+K/LLR4KG/61eIO3b7MLuKi8tytQhV6texLgP4=
|
||||
|
@ -1,11 +0,0 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"path": "."
|
||||
},
|
||||
{
|
||||
"path": "../personal-website-frontend"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
}
|
0
res/dev-fs/wde/desktop.js
Normal file
0
res/dev-fs/wde/desktop.js
Normal file
126
res/dev-fs/wde/dist/desktop.js
vendored
Normal file
126
res/dev-fs/wde/dist/desktop.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
res/dev-fs/wde/dist/desktop.js.LICENSE.txt
vendored
Normal file
1
res/dev-fs/wde/dist/desktop.js.LICENSE.txt
vendored
Normal file
@ -0,0 +1 @@
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
32
res/dev-fs/wde/dist/init.js
vendored
Normal file
32
res/dev-fs/wde/dist/init.js
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
|
||||
* This devtool is neither made for production nor for readable output files.
|
||||
* It uses "eval()" calls to create a separate source file in the browser devtools.
|
||||
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
||||
* or disable the default devtool with "devtool: false".
|
||||
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
||||
*/
|
||||
/******/ (() => { // webpackBootstrap
|
||||
/******/ var __webpack_modules__ = ({
|
||||
|
||||
/***/ "./src/init.js":
|
||||
/*!*********************!*\
|
||||
!*** ./src/init.js ***!
|
||||
\*********************/
|
||||
/***/ (() => {
|
||||
|
||||
eval("document.addEventListener('DOMContentLoaded', function () {\n console.log(\"init\");\n}, false);\n\n//# sourceURL=webpack://my-webpack-project/./src/init.js?");
|
||||
|
||||
/***/ })
|
||||
|
||||
/******/ });
|
||||
/************************************************************************/
|
||||
/******/
|
||||
/******/ // startup
|
||||
/******/ // Load entry module and return exports
|
||||
/******/ // This entry module can't be inlined because the eval devtool is used.
|
||||
/******/ var __webpack_exports__ = {};
|
||||
/******/ __webpack_modules__["./src/init.js"]();
|
||||
/******/
|
||||
/******/ })()
|
||||
;
|
223
res/dev-fs/wde/dist/mobile.js
vendored
Normal file
223
res/dev-fs/wde/dist/mobile.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
res/dev-fs/wde/mobile.js
Normal file
1
res/dev-fs/wde/mobile.js
Normal file
@ -0,0 +1 @@
|
||||
(()=>{"use strict";function e(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function n(n,t,r){return t&&e(n.prototype,t),r&&e(n,r),Object.defineProperty(n,"prototype",{writable:!1}),n}new(n((function e(){!function(e,n){if(!(e instanceof n))throw new TypeError("Cannot call a class as a function")}(this,e)})))})();
|
@ -11,6 +11,7 @@ import (
|
||||
"personalwebsite/webfilesystem"
|
||||
"time"
|
||||
|
||||
mobile "github.com/floresj/go-contrib-mobile"
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-contrib/location"
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -25,6 +26,7 @@ type AppString struct { //TODO temp
|
||||
func PrivateRoutes(port string, webfs *webfilesystem.WebFileSystem, webde *wde.WDE, appsStorage *apps.ApplicationsStorage) {
|
||||
router := gin.New()
|
||||
router.Use(location.Default())
|
||||
router.Use(mobile.Resolver())
|
||||
router.Use(favicon.New("./res/dev-fs/wde/icons/ohno.png"))
|
||||
router.LoadHTMLGlob("templates/**/*")
|
||||
router.Static("/res", "res")
|
||||
@ -53,9 +55,24 @@ func PrivateRoutes(port string, webfs *webfilesystem.WebFileSystem, webde *wde.W
|
||||
}
|
||||
|
||||
autostart := []AppString{desktop, appString, aboutMe}
|
||||
ctx.HTML(http.StatusOK, "index.tmpl", gin.H{
|
||||
"autostart": autostart,
|
||||
})
|
||||
d := mobile.GetDevice(ctx)
|
||||
|
||||
switch {
|
||||
// Hey I'm a desktop!... or laptop but not a mobile or tablet!
|
||||
case d.Normal():
|
||||
ctx.HTML(http.StatusOK, "index.html", gin.H{
|
||||
"autostart": autostart,
|
||||
})
|
||||
// Hey I'm a mobile device!
|
||||
case d.Mobile():
|
||||
ctx.HTML(http.StatusOK, "mobile-desktop.html", gin.H{
|
||||
// "autostart": autostart,
|
||||
})
|
||||
// Woa I'm a tablet!
|
||||
case d.Tablet():
|
||||
ctx.JSON(http.StatusOK, "Hello I'm a tablet device")
|
||||
}
|
||||
|
||||
})
|
||||
systemGroup := router.Group("system")
|
||||
{
|
||||
|
@ -23,7 +23,7 @@ func PublicRoutes(port string, webfs *webfilesystem.WebFileSystem, webde *wde.WD
|
||||
router.Static("/res", "res")
|
||||
|
||||
router.GET("/", func(ctx *gin.Context) {
|
||||
ctx.HTML(http.StatusOK, "index.tmpl", gin.H{})
|
||||
ctx.HTML(http.StatusOK, "index.html", gin.H{})
|
||||
})
|
||||
|
||||
systemGroup := router.Group("system")
|
||||
|
@ -1,25 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" dir="ltr">
|
||||
<title>Greg Brzezinski</title>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="res/dev-fs/wde/base.css">
|
||||
<!-- TODO: Move css init to js -->
|
||||
<!-- <link rel="stylesheet" type="text/css" href="res/dev-fs/wde/base.css">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="res/dev-fs/wde/wdeUI.css">
|
||||
<link rel="stylesheet" type="text/css" href="res/dev-fs/wde/basic-widgets.css">
|
||||
<link rel="stylesheet" type="text/css" href="res/dev-fs/wde/wde-scrollbar.css">
|
||||
<link rel="stylesheet" type="text/css" href="res/dev-fs/wde/file-view.css">
|
||||
|
||||
<!-- TODO: Move css init to js -->
|
||||
<link rel="stylesheet" href="res/dev-fs/apps/Finder/finder.css">
|
||||
<link rel="stylesheet" href="/res/dev-fs/apps/AboutMe/about-me.css">
|
||||
<link rel="stylesheet" href="/res/dev-fs/apps/ImgViewer/img-viewer.css">
|
||||
<link rel="stylesheet" href="/res/dev-fs/apps/BlogViewer/blog-viewer.css">
|
||||
|
||||
<script src="res/dev-fs/wde/wde.js"></script>
|
||||
<script src="res/dev-fs/wde/dist/desktop.js"></script>
|
||||
<script src="res/dev-fs/wde/decorat.js"></script>
|
||||
<script src="res/dev-fs/wde/webfs.js"></script>
|
||||
<script src="/res/dev-fs/wde/wde-scrollbar.js"></script>
|
||||
<script src="/res/dev-fs/wde/file-view.js"></script>
|
||||
<script src="/res/dev-fs/wde/file-view.js"></script> -->
|
||||
<script src="res/dev-fs/wde/dist/desktop.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<wde-autostart>
|
29
templates/base/mobile-desktop.html
Normal file
29
templates/base/mobile-desktop.html
Normal file
@ -0,0 +1,29 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en" dir="ltr">
|
||||
<title>Greg Brzezinski</title>
|
||||
<head>
|
||||
<!-- TODO: Move css init to js -->
|
||||
<!-- <link rel="stylesheet" type="text/css" href="res/dev-fs/wde/base.css">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="res/dev-fs/wde/wdeUI.css">
|
||||
<link rel="stylesheet" type="text/css" href="res/dev-fs/wde/basic-widgets.css">
|
||||
<link rel="stylesheet" type="text/css" href="res/dev-fs/wde/wde-scrollbar.css">
|
||||
<link rel="stylesheet" type="text/css" href="res/dev-fs/wde/file-view.css">
|
||||
|
||||
<link rel="stylesheet" href="res/dev-fs/apps/Finder/finder.css">
|
||||
<link rel="stylesheet" href="/res/dev-fs/apps/AboutMe/about-me.css">
|
||||
<link rel="stylesheet" href="/res/dev-fs/apps/ImgViewer/img-viewer.css">
|
||||
<link rel="stylesheet" href="/res/dev-fs/apps/BlogViewer/blog-viewer.css">
|
||||
|
||||
<script src="res/dev-fs/wde/dist/desktop.js"></script>
|
||||
<script src="res/dev-fs/wde/decorat.js"></script>
|
||||
<script src="res/dev-fs/wde/webfs.js"></script>
|
||||
<script src="/res/dev-fs/wde/wde-scrollbar.js"></script>
|
||||
<script src="/res/dev-fs/wde/file-view.js"></script> -->
|
||||
<script src="res/dev-fs/wde/dist/mobile.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="applications"></div>
|
||||
<div id="windows-layer"></div>
|
||||
</body>
|
||||
</html>
|
@ -1,26 +0,0 @@
|
||||
{{ define "base/mobile-desktop.tmpl" }}
|
||||
|
||||
<div id="border" class="">
|
||||
<div id="windows-layer" class="mobile-windows-layer ContentBorder">
|
||||
<div class="FileTileView">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="toolbar">
|
||||
<div id="about-me" class="FileTile" >
|
||||
<div class="Icon NoClick" style="background-image: url('./res/sys/wde/icons/about-me.png');"></div>
|
||||
<div class="FileTileTitle NoClick">About Me</div>
|
||||
</div>
|
||||
<div id="mobile-desktop-blog" class="FileTile">
|
||||
<div class="Icon NoClick" style="background-image: url('./res/sys/wde/icons/desktop.png');"></div>
|
||||
<div class="FileTileTitle NoClick">Blog</div>
|
||||
</div>
|
||||
<div id="mobile-dekstop-close" class="FileTile">
|
||||
<div class="Icon NoClick" style="background-image: url('./res/sys/wde/icons/trash.png');"></div>
|
||||
<div class="FileTileTitle NoClick">Close</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ end }}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user