From dfb48a23e4a7ac455be3a475ebf28027b6d2c148 Mon Sep 17 00:00:00 2001 From: Alex Vitkov Date: Thu, 25 Mar 2021 11:59:30 +0200 Subject: Draggable windows --- .gitignore | 1 - build/bundle.js | 932 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ front/dragging.ts | 74 ++--- front/explorer.ts | 23 +- front/window.ts | 57 +++- style.css | 59 +++- 6 files changed, 1074 insertions(+), 72 deletions(-) create mode 100644 build/bundle.js diff --git a/.gitignore b/.gitignore index 0d7fa57..99c8efd 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,4 @@ screen files node_modules php/custom_configuration.php -build temp \ No newline at end of file diff --git a/build/bundle.js b/build/bundle.js new file mode 100644 index 0000000..05341d7 --- /dev/null +++ b/build/bundle.js @@ -0,0 +1,932 @@ +(() => { + // temp/util.js + function mk(parent, type, _class) { + var el = document.createElement(type); + parent.appendChild(el); + if (_class) + el.classList.add(_class); + return el; + } + function path_combine(a, b) { + const last_char = a.slice(-1); + if (last_char == "/") + return a + b; + else + return a + "/" + b; + } + function mkcheckbox(parent, label, togglefn) { + var hdiv = mkhdiv(parent); + var write_checkbox = mk(hdiv, "input"); + write_checkbox.type = "checkbox"; + var write_checkbox_label = mk(hdiv, "label"); + write_checkbox_label.innerText = label; + write_checkbox_label.onclick = (_e) => { + write_checkbox.click(); + }; + write_checkbox_label.classList.add("noselect"); + write_checkbox.onchange = (_e) => { + togglefn(write_checkbox.checked); + }; + return hdiv; + } + function mkhdiv(parent) { + var hdiv = mk(parent, "div"); + hdiv.style.display = "flex"; + hdiv.style.alignItems = "center"; + hdiv.style.padding = "0.3rem"; + hdiv.style.gap = "0.3rem"; + return hdiv; + } + function base64ArrayBuffer(arrayBuffer) { + var base64 = ""; + var encodings = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + var bytes = new Uint8Array(arrayBuffer); + var byteLength = bytes.byteLength; + var byteRemainder = byteLength % 3; + var mainLength = byteLength - byteRemainder; + var a, b, c, d; + var chunk; + for (var i = 0; i < mainLength; i = i + 3) { + chunk = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2]; + a = (chunk & 16515072) >> 18; + b = (chunk & 258048) >> 12; + c = (chunk & 4032) >> 6; + d = chunk & 63; + base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]; + } + if (byteRemainder == 1) { + chunk = bytes[mainLength]; + a = (chunk & 252) >> 2; + b = (chunk & 3) << 4; + base64 += encodings[a] + encodings[b] + "=="; + } else if (byteRemainder == 2) { + chunk = bytes[mainLength] << 8 | bytes[mainLength + 1]; + a = (chunk & 64512) >> 10; + b = (chunk & 1008) >> 4; + c = (chunk & 15) << 2; + base64 += encodings[a] + encodings[b] + encodings[c] + "="; + } + return base64; + } + + // temp/dragging.js + var Draggable = class { + constructor(el) { + this.reparent = true; + this.lockSize = true; + this.onBeforeDragStart = null; + this.onAfterDragEnd = null; + this.customMoveHandler = null; + this.el = el; + } + }; + var dragging = null; + var dragging_offset_x = 0; + var dragging_offset_y = 0; + var dragging_candidate = null; + var dragging_candidate_x; + var dragging_candidate_y; + function set_dragging_candidate(e, candidate) { + dragging_candidate = candidate; + dragging_candidate_x = e.clientX; + dragging_candidate_y = e.clientY; + } + function begin_drag(e, d) { + if (d.onBeforeDragStart) + d.onBeforeDragStart(); + set_iframe_enabled(false); + dragging = d; + dragging_candidate = null; + dragging.el.classList.add("dragged"); + var elemRect = dragging.el.getBoundingClientRect(); + dragging_offset_x = e.clientX - elemRect.left; + dragging_offset_y = -e.clientY + elemRect.top; + if (dragging.lockSize) { + dragging.el.style.left = e.clientX - dragging_offset_x + "px"; + dragging.el.style.top = e.clientY + dragging_offset_y + "px"; + dragging.el.style.width = elemRect.width + "px"; + dragging.el.style.height = elemRect.height + "px"; + dragging.el.style.position = "absolute"; + } + if (dragging.reparent) + document.body.appendChild(dragging.el); + } + function end_drag(_e) { + set_iframe_enabled(true); + dragging.el.classList.remove("dragged"); + if (dragging.onAfterDragEnd) + dragging.onAfterDragEnd(); + dragging = null; + } + function set_iframe_enabled(en) { + const frames = document.getElementsByTagName("iframe"); + for (var i = 0; i < frames.length; i++) + frames.item(i).hidden = !en; + } + document.body.onmouseup = (_e) => { + if (dragging_candidate) + dragging_candidate = null; + if (dragging) + end_drag(_e); + }; + document.body.onmousemove = (e) => { + if (dragging) { + const x = e.clientX - dragging_offset_x; + const y = e.clientY + dragging_offset_y; + if (dragging.customMoveHandler) { + dragging.customMoveHandler(x, y); + } else { + dragging.el.style.left = x + "px"; + dragging.el.style.top = y + "px"; + } + } else if (dragging_candidate) { + var d = Math.abs(e.clientX - dragging_candidate_x) + Math.abs(e.clientY - dragging_candidate_y); + if (d > 15) + begin_drag(e, dragging_candidate); + } + }; + function oncontextmenu_hook(e) { + if (dragging) { + end_drag(e); + e.preventDefault(); + } + } + + // temp/window.js + var windows = []; + var focused_window = null; + var depth = 20; + var WindowResizeHandleDraggable = class extends Draggable { + constructor(window2, el, xDir, yDir) { + super(el); + this.window = window2; + this.reparent = false; + this.lockSize = false; + this.xDir = xDir; + this.yDir = yDir; + this.customMoveHandler = (x, y) => { + if (this.xDir == -1) { + const oldX = parseInt(this.window.style.left.slice(0, -2)); + const oldWidth = parseInt(this.window.style.width.slice(0, -2)); + this.window.style.left = x + "px"; + this.window.style.width = (oldWidth + (oldX - x)).toString() + "px"; + } + if (this.yDir == -1) { + const oldY = parseInt(this.window.style.top.slice(0, -2)); + const oldHeight = parseInt(this.window.style.height.slice(0, -2)); + this.window.style.top = y + "px"; + this.window.style.height = (oldHeight + (oldY - y)).toString() + "px"; + } + if (this.xDir == 1) { + const oldX = parseInt(this.window.style.left.slice(0, -2)); + this.window.style.width = (x - oldX).toString() + "px"; + } + if (this.yDir == 1) { + const oldY = parseInt(this.window.style.top.slice(0, -2)); + this.window.style.height = (y - oldY).toString() + "px"; + } + }; + } + }; + var BaseWindow = class { + constructor(pwd, x, y, w, h) { + this.pwd = pwd; + this.visuals = null; + this.h2 = null; + this.fileview = null; + this.files = []; + this.txt_editor = null; + make_window_base(this, pwd, x, y, w, h); + } + destroy() { + var index = windows.indexOf(this); + if (index >= 0) + windows.splice(index, 1); + this.visuals.parentNode.removeChild(this.visuals); + if (this == focused_window) + focused_window = null; + } + focus() { + if (focused_window == this) + return; + if (focused_window) + focused_window.visuals.classList.remove("focus"); + focused_window = this; + this.visuals.classList.add("focus"); + this.visuals.style.zIndex = (depth++).toString(); + } + get_path(max_length) { + if (max_length == void 0) { + max_length = this.pwd.length; + } + var path = "/"; + for (let i = 0; i < max_length; i++) { + path += this.pwd[i]; + if (i != max_length - 1) + path += "/"; + } + return path; + } + }; + function mkdraghandle(wnd, _class, x, y) { + const d = mk(wnd.grid, "div", _class); + const dd = new WindowResizeHandleDraggable(wnd.visuals, d, x, y); + d.onmousedown = (e) => begin_drag(e, dd); + } + function make_window_base(wnd, pwd, x, y, w, h) { + windows.push(wnd); + wnd.visuals = mk(document.body, "div", "window"); + wnd.grid = mk(wnd.visuals, "div", "windowgrid"); + wnd.wc = mk(wnd.grid, "div", "wc"); + wnd.visuals.style.width = w + "px"; + wnd.visuals.style.height = h ? h + "px" : "unset"; + wnd.visuals.style.position = "absolute"; + wnd.visuals.style.left = x + "px"; + wnd.visuals.style.top = y + "px"; + wnd.h2 = mk(wnd.wc, "h2"); + mkdraghandle(wnd, "nw-resize", -1, -1); + mkdraghandle(wnd, "w-resize", -1, 0); + mkdraghandle(wnd, "sw-resize", -1, 1); + mkdraghandle(wnd, "s-resize", 0, 1); + mkdraghandle(wnd, "se-resize", 1, 1); + mkdraghandle(wnd, "e-resize", 1, 0); + mkdraghandle(wnd, "ne-resize", 1, -1); + mkdraghandle(wnd, "n-resize", 0, -1); + wnd.visuals.onmousedown = (_e) => { + wnd.focus(); + }; + wnd.h2.onmousedown = (e) => { + if (!dragging) + set_dragging_candidate(e, new Draggable(wnd.visuals)); + }; + return wnd; + } + + // temp/upload_form.js + var upload_form = document.getElementById("upload_form"); + var filename_input = document.getElementById("filename"); + var override_input = document.getElementById("override_input"); + var upload_parent_directory = document.getElementById("upload_parent_directory"); + var the_file = document.getElementById("the_file"); + var override_file = false; + var override_file_filename = ""; + var override_file_path = ""; + function show_upload_dialog() { + override_file = false; + the_file.click(); + } + function show_upload_dialog_replace_file(folder, filename) { + override_file = true; + override_file_path = folder; + override_file_filename = filename; + the_file.click(); + } + function on_file_added(_e) { + if (the_file.files.length >= 1) { + if (override_file) { + filename_input.value = override_file_filename; + override_input.value = "1"; + upload_parent_directory.value = override_file_path; + console.log(filename_input.value, override_input.value, upload_parent_directory.value); + } else { + filename_input.value = the_file.files[0].name; + override_input.value = "0"; + upload_parent_directory.value = focused_window.get_path(); + } + fetch(upload_form.action, { + method: upload_form.method, + body: new FormData(upload_form) + }).then((resp) => { + if (resp.status == 200) { + focused_window.openfile(true); + } else { + alert("Upload failed"); + } + }, () => { + alert("Upload failed"); + }); + } else { + alert("No files selected"); + } + } + the_file.onchange = (e) => { + on_file_added(e); + }; + + // temp/share_window.js + var ShareWindow = class extends BaseWindow { + constructor(folder, filename, x, y, w) { + super(null, x, y, w, 0); + make_share_window(this, folder, filename); + } + }; + function make_share_window(wnd, folder, filename) { + wnd.h2.style.display = "flex"; + var heading = mk(wnd.h2, "span", "wndtitle"); + heading.innerText = "Share " + filename; + var x_button = mk(wnd.h2, "button", "close_button"); + x_button.innerText = "X"; + x_button.onclick = () => { + wnd.destroy(); + }; + wnd.contents = mk(wnd.visuals, "div", "share_dialog_contents"); + wnd.contents.style.padding = "0.5rem"; + var data = { + write_permissions: false, + private: false, + has_password: false, + password: "", + userlist: [] + }; + var userlist, add_user; + mkcheckbox(wnd.contents, "Private link", (toggled) => { + add_user.style.display = toggled ? "block" : "none"; + userlist.style.display = toggled ? "block" : "none"; + data.private = toggled; + }); + userlist = mk(wnd.contents, "div"); + userlist.style.display = "none"; + add_user = mk(wnd.contents, "button"); + add_user.innerText = "Add user"; + add_user.style.display = "none"; + add_user.onclick = (_e) => { + var i = mk(userlist, "input"); + i.value = "John Doe"; + let index = data.userlist.length; + data.userlist.push(i.value); + i.onchange = (_e2) => { + data.userlist[index] = i.value; + }; + }; + add_user.click(); + mkcheckbox(wnd.contents, "Give write permissions", (toggled) => { + data.write_permissions = toggled; + }); + let password_container; + mkcheckbox(wnd.contents, "Password protected", (toggled) => { + data.has_password = toggled; + password_container.style.display = toggled ? "flex" : "none"; + }); + password_container = mkhdiv(wnd.contents); + password_container.style.display = "none"; + var password_label = mk(password_container, "label"); + password_label.innerText = "Password"; + var password_input = mk(password_container, "input"); + password_input.type = "password"; + password_input.autocomplete = "off"; + password_input.style.flex = "1 0 0"; + password_input.onchange = (_e) => { + data.password = password_input.value; + }; + var generate_url_button = mk(wnd.contents, "button"); + generate_url_button.innerText = "Generate link"; + generate_url_button.onclick = () => { + var users = ""; + if (data.private) { + users = data.userlist.join(","); + } + var form_data = new FormData(); + form_data.append("folder", folder); + form_data.append("filename", filename); + form_data.append("users", users); + form_data.append("permissions", (data.write_permissions ? 3 : 1).toString()); + form_data.append("password", data.has_password ? data.password : ""); + var xhr = new XMLHttpRequest(); + xhr.open("POST", "/php/share.php", true); + xhr.onload = function() { + alert(xhr.response); + }; + xhr.send(form_data); + wnd.destroy(); + }; + } + + // temp/contextmenu.js + var context_menu = null; + function context(e, entries) { + if (context_menu) + context_menu.remove(); + context_menu = mk(document.body, "ul", "context"); + context_menu.onmousedown = (e2) => { + e2.stopPropagation(); + }; + context_menu.onclick = (_e) => { + context_menu.remove(); + context_menu = null; + }; + context_menu.style.left = e.clientX + "px"; + context_menu.style.top = e.clientY + "px"; + for (const e2 of entries) { + const li = document.createElement("li"); + li.innerText = e2[0]; + li.onclick = e2[1]; + context_menu.appendChild(li); + } + } + document.body.onmousedown = (_e) => { + if (context_menu) { + context_menu.remove(); + context_menu = null; + } + }; + function oncontextmenu_hook2(_e) { + if (context_menu) { + context_menu.remove(); + context_menu = null; + } + } + + // temp/explorer.js + var ExplorerWindow = class extends BaseWindow { + constructor(pwd, x, y, w, h, has_close) { + super(pwd, x, y, w, h); + make_window(this, has_close); + } + openfile(is_directory) { + if (is_directory) { + openfile_dir(this); + } else { + openfile_nondir(this); + } + } + }; + var FileView = class { + constructor(filename, wnd, mimetype, is_directory, write_permissions) { + this.filename = filename; + this.wnd = wnd; + this.visuals = null; + this.mimetype = mimetype; + this.is_directory = is_directory; + this.write_permissions = write_permissions; + } + full_path() { + return path_combine(this.wnd.get_path(), this.filename); + } + }; + var FileViewDraggable = class extends Draggable { + constructor(fileview) { + super(fileview.visuals); + this.fileview = fileview; + this.onBeforeDragStart = () => { + this.placeholder = document.createElement("div"); + fileview.visuals.parentNode.insertBefore(this.placeholder, fileview.visuals); + }; + this.onAfterDragEnd = () => { + this.placeholder.parentNode.insertBefore(this.el, this.placeholder); + this.placeholder.remove(); + fileview.visuals.style.removeProperty("position"); + fileview.visuals.style.removeProperty("width"); + fileview.visuals.style.removeProperty("height"); + fileview.visuals.style.removeProperty("left"); + fileview.visuals.style.removeProperty("top"); + }; + } + }; + function make_window(wnd, has_close) { + mk(wnd.h2, "div", "path"); + if (has_close) { + var x_button = mk(wnd.h2, "button", "close_button"); + x_button.innerText = "X"; + x_button.onclick = () => { + wnd.destroy(); + }; + } + { + wnd.foldercontents = mk(wnd.wc, "div", "foldercontents"); + var h3 = mk(wnd.foldercontents, "h3"); + var upload_btn = mk(h3, "button"); + upload_btn.innerText = "Upload"; + upload_btn.onclick = () => { + show_upload_dialog(); + }; + mk(h3, "div", "separator"); + var new_folder_btn = mk(h3, "button"); + new_folder_btn.innerText = "New Folder"; + new_folder_btn.onclick = () => { + new_folder(wnd); + }; + mk(h3, "div", "separator"); + wnd.filegrid = mk(wnd.foldercontents, "div", "files"); + } + { + wnd.filecontentsroot = mk(wnd.wc, "div", "filecontentsroot"); + var h3 = mk(wnd.filecontentsroot, "h3"); + let download_btn = mk(h3, "button"); + download_btn.innerText = "Download"; + download_btn.onclick = () => { + download_file(true); + }; + mk(h3, "div", "separator"); + let share_btn = mk(h3, "button"); + share_btn.innerText = "Share"; + share_btn.onclick = () => { + alert("TODO NOT IMPLEMENTETD"); + }; + mk(h3, "div", "separator"); + wnd.save_btn_container = mk(h3, "div"); + wnd.save_btn_container.style.display = "flex"; + let save_btn = mk(wnd.save_btn_container, "button"); + save_btn.innerText = "Save"; + save_btn.onclick = () => save_open_text_file(wnd); + mk(wnd.save_btn_container, "div", "separator"); + wnd.filecontents = mk(wnd.filecontentsroot, "div", "filecontents"); + } + return wnd; + } + function add_file_visuals(fileview, wnd) { + var is_in_trash = wnd.pwd.length > 0 && wnd.pwd[0] == "trash"; + var is_trash = wnd.pwd.length == 0 && fileview.filename == "trash"; + var is_share = wnd.pwd.length == 0 && fileview.filename == "share"; + var visuals = mk(wnd.filegrid, "div"); + fileview.visuals = visuals; + var img = document.createElement("img"); + var filename = document.createElement("div"); + if (fileview.is_directory) { + if (wnd.get_path() == "/" && fileview.filename == "trash") + img.src = "/mimeicons/user-trash.png"; + else if (wnd.get_path() == "/" && fileview.filename == "share") + img.src = "/mimeicons/user-share.png"; + else + img.src = "/mimeicons/directory.png"; + } else { + img.src = `/mimeicons/${fileview.mimetype.replace("/", "-")}.png`; + } + fileview.visuals.onclick = () => { + wnd.pwd.push(fileview.filename); + if (!fileview.is_directory) { + wnd.opened_file = fileview; + } + wnd.openfile(fileview.is_directory); + }; + visuals.oncontextmenu = (e) => { + if (!dragging) { + var context_list = [ + ["Open", () => { + wnd.pwd.push(fileview.filename); + wnd.openfile(fileview.is_directory); + }], + ["Open in New Window", () => { + var new_pwd = wnd.pwd.slice(); + new_pwd.push(fileview.filename); + var new_wnd = new ExplorerWindow(new_pwd, 100, 100, 800, 600, true); + new_wnd.opened_file = fileview; + new_wnd.openfile(fileview.is_directory); + new_wnd.focus(); + }] + ]; + if (is_in_trash) { + context_list.push(["Restore", () => { + restore_from_trash(wnd, fileview.filename); + }]); + context_list.push(["Delete forever", () => { + delete_file(wnd, fileview.filename); + }]); + } else if (!is_trash && !is_share) { + context_list.push(["Rename", () => { + rename_file(fileview.filename, wnd); + }]); + if (!fileview.is_directory) { + for (let a of actions) { + if (fileview.filename.endsWith(a.extension)) { + context_list.push([a.text, () => { + read_file_contents(true, (x2) => { + const ue = encodeURIComponent(x2); + let url = a.url.replace("$content_urlencoded", ue).replace("$filename", fileview.filename); + if (a.open_in_iframe) { + const wnd2 = new BaseWindow([], 10, 10, 800, 600); + var title = mk(wnd2.h2, "span", "wndtitle"); + title.innerText = fileview.filename; + var x_button = mk(wnd2.h2, "button", "close_button"); + x_button.innerText = "X"; + x_button.onclick = () => { + wnd2.destroy(); + }; + const contents = mk(wnd2.wc, "div", "filecontentsroot"); + const iframe = mk(contents, "iframe"); + iframe.style.flex = "1 0 0"; + iframe.src = url; + wnd2.focus(); + } else { + window.location = url; + } + }, wnd.get_path(), fileview.filename); + }]); + } + } + if (fileview.write_permissions) { + context_list.push(["Replace", () => { + replace_file(false, fileview.filename, wnd); + }]); + } + context_list.push(["Share", () => { + share(false, fileview.filename, wnd); + }], ["Download", () => { + download_file(false, fileview.filename); + }]); + } + context_list.push(["Delete", () => { + move_to_trash(wnd, fileview.filename); + }]); + } + context(e, context_list); + } + e.preventDefault(); + e.stopPropagation(); + }; + visuals.ondragstart = (e) => { + if (is_trash || is_in_trash || is_share) { + e.preventDefault(); + return; + } + begin_drag(e, new FileViewDraggable(fileview)); + e.preventDefault(); + }; + visuals.onmouseup = (e) => { + if (dragging) { + if (fileview.is_directory) { + if (wnd.get_path() == "/" && fileview.filename == "trash") { + move_to_trash(wnd, dragging.fileview.filename); + } else if (wnd.get_path() == "/" && fileview.filename == "share") { + } else { + move_file(dragging.fileview.wnd, wnd, path_combine(wnd.get_path(), fileview.filename), dragging.fileview.filename); + } + } else { + } + end_drag(e); + } + e.preventDefault(); + }; + visuals.classList.add("file"); + filename.classList.add("filename"); + if (is_in_trash) { + var split = fileview.filename.split("/"); + filename.innerText = split[split.length - 1]; + } else if (is_trash) { + filename.innerText = "Trash"; + } else if (is_share) { + var x = mk(filename, "span"); + x.style.fontSize = "0.8rem"; + x.innerText = "Shared with me"; + } else { + filename.innerText = fileview.filename; + } + visuals.appendChild(img); + visuals.appendChild(filename); + } + function openfile_nondir(wnd) { + while (wnd.filecontents.children.length > 0) + wnd.filecontents.removeChild(wnd.filecontents.lastChild); + var data = new FormData(); + data.append("folder", wnd.get_path(wnd.pwd.length - 1)); + data.append("filename", wnd.pwd[wnd.pwd.length - 1]); + var xhr = new XMLHttpRequest(); + update_path_visuals(wnd); + xhr.open("POST", "/php/readfile.php", true); + wnd.filecontents.innerText = ""; + wnd.filecontentsroot.style.display = "flex"; + wnd.foldercontents.style.display = "none"; + let is_image = wnd.opened_file.mimetype.split("/")[0] == "image"; + wnd.save_btn_container.style.display = wnd.opened_file.write_permissions && !is_image ? "flex" : "none"; + if (is_image) { + xhr.responseType = "arraybuffer"; + xhr.onload = function() { + let b = `data:image/png;base64,${base64ArrayBuffer(xhr.response)}`; + wnd.filecontents.style.backgroundImage = `url('${b}')`; + wnd.filecontents.classList.add("imgview"); + }; + } else { + wnd.filecontents.classList.remove("imgview"); + wnd.filecontents.style.backgroundImage = "unset"; + wnd.txt_editor = mk(wnd.filecontents, "pre"); + xhr.onload = function() { + wnd.txt_editor.innerText = xhr.responseText; + if (wnd.opened_file.write_permissions) + wnd.txt_editor.contentEditable = "true"; + }; + } + xhr.send(data); + } + function openfile_dir(wnd) { + update_path_visuals(wnd); + var data = new FormData(); + data.append("path", wnd.get_path()); + var xhr = new XMLHttpRequest(); + xhr.open("POST", "/php/readdir.php", true); + xhr.onload = function() { + for (const f of wnd.files) + f.visuals.remove(); + wnd.files = []; + var json = JSON.parse(xhr.responseText); + if (!json) + return; + for (const f of json) { + var view = new FileView(f.name, wnd, f.mimetype, f.is_directory && f.is_directory != "0", f.can_edit && f.can_edit != "0"); + wnd.files.push(view); + } + wnd.files.sort((a, b) => { + if (wnd.pwd.length == 0 && a.filename == "share") + return -10; + if (wnd.pwd.length == 0 && b.filename == "share") + return 10; + if (wnd.pwd.length == 0 && a.filename == "trash") + return 10; + if (wnd.pwd.length == 0 && b.filename == "trash") + return -10; + if (a.is_directory && !b.is_directory) + return -1; + if (!a.is_directory && b.is_directory) + return 1; + return a.filename.localeCompare(b.filename); + }); + for (const f of wnd.files) { + add_file_visuals(f, wnd); + } + }; + xhr.send(data); + wnd.filecontentsroot.style.display = "none"; + wnd.foldercontents.style.display = "flex"; + wnd.foldercontents.onmouseup = () => { + if (dragging && dragging instanceof FileViewDraggable) { + move_file(dragging.fileview.wnd, wnd, wnd.get_path(), dragging.fileview.filename); + } + }; + } + function move_to_trash(wnd, filename) { + move_file(wnd, wnd, "/trash", filename, path_combine(wnd.get_path(), filename)); + } + function restore_from_trash(wnd, filename) { + var split = filename.split("/"); + var new_filename = split.pop(); + var new_directory = "/" + split.join("/"); + move_file(wnd, wnd, new_directory, filename, new_filename); + } + function delete_file(wnd, filename) { + var data = new FormData(); + data.append("folder", wnd.get_path()); + data.append("filename", filename); + var xhr = new XMLHttpRequest(); + xhr.open("POST", "/php/delete.php", true); + xhr.onload = function() { + wnd.openfile(true); + }; + xhr.send(data); + } + function rename_file(filename, wnd) { + var new_name = prompt(`Rename ${filename} to`, filename); + if (!new_name) + return; + var data = new FormData(); + data.append("folder", wnd.get_path()); + data.append("old_filename", filename); + data.append("new_filename", new_name); + var xhr = new XMLHttpRequest(); + xhr.open("POST", "/php/rename.php", true); + xhr.onload = function() { + wnd.openfile(true); + }; + xhr.send(data); + } + function move_file(srcwnd, dstwnd, new_folder2, filename, new_filename) { + if (!new_filename) + new_filename = filename; + var data = new FormData(); + data.append("old_folder", srcwnd.get_path()); + data.append("new_folder", new_folder2); + data.append("filename", filename); + data.append("new_filename", new_filename); + var xhr = new XMLHttpRequest(); + xhr.open("POST", "/php/move.php", true); + xhr.onload = () => { + srcwnd.openfile(true); + dstwnd.openfile(true); + }; + xhr.send(data); + } + function new_folder(wnd) { + var dirname = prompt(`Directory name`, "New Folder"); + if (!dirname) + return; + var data = new FormData(); + data.append("parent_directory", wnd.get_path()); + data.append("dirname", dirname); + var xhr = new XMLHttpRequest(); + xhr.open("POST", "/php/mkdir.php", true); + xhr.onload = function() { + wnd.openfile(true); + }; + xhr.send(data); + } + function replace_file(in_file, filename, wnd) { + if (in_file) { + var folder = wnd.get_path(wnd.pwd.length - 1); + filename = wnd.pwd[wnd.pwd.length - 1]; + } else { + var folder = wnd.get_path(); + } + show_upload_dialog_replace_file(folder, filename); + } + function update_path_visuals(wnd) { + var the_path = wnd.visuals.getElementsByClassName("path")[0]; + while (the_path.children.length > 0) + the_path.removeChild(the_path.lastChild); + for (let i = -1; i < wnd.pwd.length; i++) { + var d; + if (i >= 0) { + d = wnd.pwd[i]; + var separator_div = mk(the_path, "div", "separator"); + separator_div.innerText = "\xBB"; + } else + d = "Root"; + var entry = mk(the_path, "button", "pathentry"); + entry.innerText = d; + entry.onclick = (_e) => { + if (length < wnd.pwd.length) { + wnd.pwd.length = i + 1; + openfile_dir(wnd); + } + }; + entry.onmouseup = (e) => { + if (dragging && dragging instanceof FileViewDraggable) { + var new_folder2 = wnd.get_path(i + 1); + move_file(dragging.fileview.wnd, wnd, new_folder2, dragging.fileview.filename); + end_drag(e); + e.preventDefault(); + e.stopPropagation(); + } + }; + } + } + function download_file(in_file, filename, wnd) { + if (in_file) { + var folder = wnd.get_path(wnd.pwd.length - 1); + filename = wnd.pwd[wnd.pwd.length - 1]; + } else { + var folder = wnd.get_path(); + } + read_file_contents(false, (x) => { + var blob = new Blob([new Uint8Array(x, 0, x.length)]); + var url = URL.createObjectURL(blob); + var a = document.createElement("a"); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + setTimeout(() => { + document.body.removeChild(a); + URL.revokeObjectURL(url); + }); + }, folder, filename); + return; + } + function save_open_text_file(wnd) { + const contents = wnd.txt_editor.innerText; + let xhr = new XMLHttpRequest(); + xhr.open("POST", "/php/upload.php", true); + var data = new FormData(); + data.append("parent_directory", wnd.get_path(wnd.pwd.length - 1)); + data.append("filename", wnd.pwd[wnd.pwd.length - 1]); + data.append("content", contents); + data.append("overwrite", "1"); + xhr.send(data); + } + function read_file_contents(text, cb, folder, filename) { + var data = new FormData(); + data.append("folder", folder); + data.append("filename", filename); + let xhr = new XMLHttpRequest(); + xhr.open("POST", "/php/readfile.php", true); + if (text) { + xhr.onload = function() { + cb(xhr.responseText); + }; + } else { + xhr.responseType = "arraybuffer"; + xhr.onload = function() { + cb(xhr.response); + }; + } + xhr.send(data); + } + function share(in_file, filename, wnd) { + if (in_file) { + var folder = wnd.get_path(wnd.pwd.length - 1); + filename = wnd.pwd[wnd.pwd.length - 1]; + } else { + var folder = wnd.get_path(); + } + var sharewnd = new ShareWindow(folder, filename, 400, 400, 400); + sharewnd.focus(); + } + + // temp/main.js + function main() { + var root_window = new ExplorerWindow([], 100, 100, 800, 600, false); + root_window.focus(); + root_window.openfile(true); + } + document.body.oncontextmenu = (e) => { + oncontextmenu_hook2(e); + oncontextmenu_hook(e); + }; + main(); +})(); diff --git a/front/dragging.ts b/front/dragging.ts index b65f1f4..bcf6e9f 100644 --- a/front/dragging.ts +++ b/front/dragging.ts @@ -2,16 +2,15 @@ export class Draggable { el: HTMLElement; - canDragX: boolean = true; - canDragY: boolean = true; reparent: boolean = true; lockSize: boolean = true; onBeforeDragStart: () => void = null; onAfterDragEnd: () => void = null; + customMoveHandler: (x: number, y: number) => void = null; - Draggable(el: HTMLElement) { + constructor(el: HTMLElement) { this.el = el; } } @@ -31,11 +30,6 @@ export var dragging_offset_x = 0, dragging_offset_y = 0; export var dragging_candidate: Draggable = null; export var dragging_candidate_x, dragging_candidate_y; -// If we're dragging a fileview, this is set to the fileview class instance itself, because 'dragging' is just a DOM object -// The placeholder is a dummy DIV that we insert in the object's place on the grid to keep things nicely aligned -// export var dragging_fileview: FileView; -// export var dragging_placeholder = null; - export function set_dragging_candidate(e, candidate: Draggable) { dragging_candidate = candidate; dragging_candidate_x = e.clientX; @@ -46,6 +40,8 @@ export function set_dragging_candidate(e, candidate: Draggable) { // Start dragging the 'obj' DOM element // e is a DOM event, this should only get called in response of a DOM event export function begin_drag(e, d: Draggable) { + if (d.onBeforeDragStart) + d.onBeforeDragStart() set_iframe_enabled(false); dragging = d; dragging_candidate = null; @@ -55,42 +51,27 @@ export function begin_drag(e, d: Draggable) { dragging_offset_x = e.clientX - elemRect.left; dragging_offset_y = -e.clientY + elemRect.top; - //if (dragging_placeholder) - // dragging.el.parentNode.insertBefore(dragging_placeholder, dragging.el); - - dragging.el.style.left = (e.clientX - dragging_offset_x) + "px"; - dragging.el.style.top = (e.clientY + dragging_offset_y) + "px"; if (dragging.lockSize) { + dragging.el.style.left = (e.clientX - dragging_offset_x) + "px"; + dragging.el.style.top = (e.clientY + dragging_offset_y) + "px"; dragging.el.style.width = elemRect.width + "px"; dragging.el.style.height = elemRect.height + "px"; + dragging.el.style.position = "absolute"; } - dragging.el.style.position = "absolute"; - document.body.appendChild(dragging.el); + + + if (dragging.reparent) + document.body.appendChild(dragging.el); } export function end_drag(_e) { set_iframe_enabled(true); - // If there's a dragging placeholder remove it and put the dragged node back into its place - if (dragging_placeholder) { - dragging_placeholder.parentNode.insertBefore(dragging, dragging_placeholder); - dragging_placeholder.remove(); - dragging_placeholder = null; - } - - // If we were dragging a FileView, we need to reset some CSS - if (dragging_fileview) { - dragging.style.removeProperty("position"); - dragging.style.removeProperty("width"); - dragging.style.removeProperty("height"); - dragging.style.removeProperty("left"); - dragging.style.removeProperty("top"); - dragging_fileview = null; - } - - dragging.classList.remove("dragged"); + dragging.el.classList.remove("dragged"); + if (dragging.onAfterDragEnd) + dragging.onAfterDragEnd(); dragging = null; } @@ -109,30 +90,21 @@ document.body.onmouseup = (_e) => { document.body.onmousemove = (e) => { if (dragging) { - dragging.style.left = (e.clientX - dragging_offset_x) + "px"; - dragging.style.top = (e.clientY + dragging_offset_y) + "px"; + const x = e.clientX - dragging_offset_x; + const y = e.clientY + dragging_offset_y; + if (dragging.customMoveHandler) { + dragging.customMoveHandler(x, y) + } else { + dragging.el.style.left = x + "px"; + dragging.el.style.top = y + "px"; + } } else if (dragging_candidate) { var d = Math.abs(e.clientX - dragging_candidate_x) + Math.abs(e.clientY - dragging_candidate_y); if (d > 15) - begin_drag(e, dragging_candidate, true); + begin_drag(e, dragging_candidate); } } -// Dragging a fileview is a bit different from dragging a window -// This does some setup work before calling the common begin_drag -export function begin_drag_fileview(e, fileview) { - if (dragging) - end_drag(e); - - // The dragging_placeholder is inserted into its place by the begin_drag function - dragging_placeholder = document.createElement('div'); - dragging_fileview = fileview; - - dragging = fileview.visuals; - dragging.style.zIndex = 50000; - - begin_drag(e, fileview.visuals); -} export function oncontextmenu_hook(e) { if (dragging) { diff --git a/front/explorer.ts b/front/explorer.ts index 9b35f5d..f5104bc 100644 --- a/front/explorer.ts +++ b/front/explorer.ts @@ -1,7 +1,7 @@ import { BaseWindow } from './window' import { mk, path_combine, base64ArrayBuffer } from './util'; import { show_upload_dialog, show_upload_dialog_replace_file } from './upload_form'; -import { dragging, Draggable, end_drag } from './dragging' +import { dragging, Draggable, end_drag, begin_drag } from './dragging' import { ShareWindow } from './share_window'; import { context } from './contextmenu'; @@ -58,7 +58,8 @@ class FileViewDraggable extends Draggable { fileview: FileView; placeholder: HTMLDivElement; - FileViewDraggable(fileview: FileView) { + constructor(fileview: FileView) { + super(fileview.visuals); this.fileview = fileview; this.onBeforeDragStart = () => { @@ -69,7 +70,7 @@ class FileViewDraggable extends Draggable { this.onAfterDragEnd = () => { // If there's a dragging placeholder remove it and put the dragged node back into its place - this.placeholder.parentNode.insertBefore(dragging, dragging_placeholder); + this.placeholder.parentNode.insertBefore(this.el, this.placeholder); this.placeholder.remove(); fileview.visuals.style.removeProperty("position"); @@ -77,8 +78,6 @@ class FileViewDraggable extends Draggable { fileview.visuals.style.removeProperty("height"); fileview.visuals.style.removeProperty("left"); fileview.visuals.style.removeProperty("top"); - - } } } @@ -271,7 +270,7 @@ function add_file_visuals(fileview: FileView, wnd: ExplorerWindow) { e.preventDefault(); return; } - begin_drag_fileview(e, fileview); + begin_drag(e, new FileViewDraggable(fileview)); e.preventDefault(); }; @@ -280,13 +279,13 @@ function add_file_visuals(fileview: FileView, wnd: ExplorerWindow) { if (fileview.is_directory) { if (wnd.get_path() == "/" && fileview.filename == "trash") { // If we've dragged something onto the trashcan, it's trash - move_to_trash(wnd, dragging_fileview.filename); + move_to_trash(wnd, (dragging as FileViewDraggable).fileview.filename); } else if (wnd.get_path() == "/" && fileview.filename == "share") { // move to 'share' is invalid } else { // If we've dragged something onto a directory, move it into that directory - move_file(dragging_fileview.wnd, wnd, path_combine(wnd.get_path(), fileview.filename), dragging_fileview.filename); + move_file((dragging as FileViewDraggable).fileview.wnd, wnd, path_combine(wnd.get_path(), fileview.filename), (dragging as FileViewDraggable).fileview.filename); } } else { // alert(`Dropped ${dst.filename} on ${src.filename}`); @@ -422,8 +421,8 @@ function openfile_dir(wnd) { wnd.foldercontents.style.display = 'flex'; wnd.foldercontents.onmouseup = () => { - if (dragging && dragging_fileview) { - move_file(dragging_fileview.wnd, wnd, wnd.get_path(), dragging_fileview.filename); + if (dragging && dragging instanceof FileViewDraggable) { + move_file((dragging as FileViewDraggable).fileview.wnd, wnd, wnd.get_path(), (dragging as FileViewDraggable).fileview.filename); } } } @@ -557,9 +556,9 @@ function update_path_visuals(wnd) { // We can drop files onto the path, which will omve them to teh folder entry.onmouseup = (e) => { - if (dragging && dragging_fileview) { + if (dragging && dragging instanceof FileViewDraggable) { var new_folder = wnd.get_path(i + 1); - move_file(dragging_fileview.wnd, wnd, new_folder, dragging_fileview.filename); + move_file((dragging as FileViewDraggable).fileview.wnd, wnd, new_folder, (dragging as FileViewDraggable).fileview.filename); end_drag(e); e.preventDefault(); diff --git a/front/window.ts b/front/window.ts index b9de2b6..840f7a2 100644 --- a/front/window.ts +++ b/front/window.ts @@ -14,6 +14,43 @@ import * as drag from './dragging' // We then increment the depth, and the next window we click will go on top of the current one var depth = 20; +export class WindowResizeHandleDraggable extends drag.Draggable { + window: HTMLDivElement; + xDir: number; + yDir: number; + + constructor(window: HTMLDivElement, el: HTMLElement, xDir: number, yDir: number) { + super(el); + this.window = window; + this.reparent = false; + this.lockSize = false; + this.xDir = xDir; + this.yDir = yDir; + + this.customMoveHandler = (x, y) => { + if (this.xDir == -1) { + const oldX = parseInt(this.window.style.left.slice(0, -2)); + const oldWidth = parseInt(this.window.style.width.slice(0, -2)); + this.window.style.left = x + "px"; + this.window.style.width = (oldWidth + (oldX - x)).toString() + "px"; + } + if (this.yDir == -1) { + const oldY = parseInt(this.window.style.top.slice(0, -2)); + const oldHeight = parseInt(this.window.style.height.slice(0, -2)); + this.window.style.top = y + "px"; + this.window.style.height = (oldHeight + (oldY - y)).toString() + "px"; + } + if (this.xDir == 1) { + const oldX = parseInt(this.window.style.left.slice(0, -2)); + this.window.style.width = (x - oldX).toString() + "px"; + } + if (this.yDir == 1) { + const oldY = parseInt(this.window.style.top.slice(0, -2)); + this.window.style.height = (y - oldY).toString() + "px"; + } + } + } +} export class BaseWindow { pwd: string[]; @@ -87,10 +124,16 @@ export function unfocus_window() { } } +function mkdraghandle(wnd, _class, x, y) { + const d = mk(wnd.grid, 'div', _class); + const dd = new WindowResizeHandleDraggable(wnd.visuals as HTMLDivElement, d, x, y); + d.onmousedown = (e) => drag.begin_drag(e, dd); +} + // This creates the parts of a window that are common between all window types // This should only really be called by another function that will then fill up the window -function make_window_base(wnd, pwd, x, y, w, h) { +function make_window_base(wnd: BaseWindow, pwd, x, y, w, h) { windows.push(wnd); wnd.visuals = mk(document.body, 'div', 'window'); @@ -105,7 +148,15 @@ function make_window_base(wnd, pwd, x, y, w, h) { wnd.h2 = mk(wnd.wc, 'h2'); - mk(wnd.grid, 'div', 'nw-resize'); + + mkdraghandle(wnd, 'nw-resize', -1, -1) + mkdraghandle(wnd, 'w-resize', -1, 0); + mkdraghandle(wnd, 'sw-resize', -1, 1); + mkdraghandle(wnd, 's-resize', 0, 1); + mkdraghandle(wnd, 'se-resize', 1, 1); + mkdraghandle(wnd, 'e-resize', 1, 0); + mkdraghandle(wnd, 'ne-resize', 1, -1); + mkdraghandle(wnd, 'n-resize', 0, -1); wnd.visuals.onmousedown = (_e) => { wnd.focus(); @@ -113,7 +164,7 @@ function make_window_base(wnd, pwd, x, y, w, h) { wnd.h2.onmousedown = (e) => { if (!drag.dragging) - drag.set_dragging_candidate(e, wnd.visuals); + drag.set_dragging_candidate(e, new drag.Draggable(wnd.visuals)); }; return wnd; diff --git a/style.css b/style.css index f7ec8e2..a710372 100644 --- a/style.css +++ b/style.css @@ -259,6 +259,9 @@ input[type=submit]:hover { left: 0; width: 100%; z-index: -100; + user-select: none; + -webkit-user-select: none; + -ms-user-select: none; } @@ -282,7 +285,6 @@ input[type=submit]:hover { padding: 0; box-shadow: 0 0.8rem 1.3rem rgba(0,0,0,0.2); border-radius: var(--window-border-radius); - border: 1px solid #b9b9b9; overflow: hidden; position: absolute; @@ -290,9 +292,10 @@ input[type=submit]:hover { .windowgrid { display: grid; - grid-template-columns: 10px auto 10px; - grid-template-rows: 10px auto 10px; + grid-template-columns: 4px auto 4px; + grid-template-rows: 4px auto 4px; height: 100%; + background: rgba(250, 250, 250, .9); } .window .wc { @@ -304,12 +307,54 @@ input[type=submit]:hover { grid-column: 2; } +.nw-resize, .w-resize, .sw-resize, .se-resize, .ne-resize, .n-resize, .e-resize, .s-resize { + background:rgba(220, 220, 220, .9); +} + +.focus .nw-resize, .focus .w-resize, .focus .sw-resize, .focus .se-resize, .focus .ne-resize, .focus .n-resize, .focus .e-resize, .focus .s-resize { + background: #555; +} + .nw-resize { - background: red; grid-row: 1; grid-column: 1; cursor: nw-resize; } +.w-resize { + grid-row: 2; + grid-column: 1; + cursor: w-resize; +} +.sw-resize { + grid-row: 3; + grid-column: 1; + cursor: sw-resize; +} +.s-resize { + grid-row: 3; + grid-column: 2; + cursor: s-resize; +} +.se-resize { + grid-row: 3; + grid-column: 3; + cursor: se-resize; +} +.e-resize { + grid-row: 2; + grid-column: 3; + cursor: e-resize; +} +.ne-resize { + grid-row: 1; + grid-column: 3; + cursor: ne-resize; +} +.n-resize { + grid-row: 1; + grid-column: 2; + cursor: n-resize; +} .window.focus { border-color: black; @@ -382,7 +427,11 @@ pre { } .window.dragged { - /* opacity: 0.9; */ + opacity: 0.9; +} + +.dragged { + z-index: 100000; } .files { -- cgit v1.2.3