From 02a0fb82dd821ae80f6a4e71f5cdfd120c18f7dd Mon Sep 17 00:00:00 2001 From: Victor Seiji Hariki Date: Sat, 7 Jan 2023 09:46:33 -0300 Subject: [PATCH 01/13] workspaces Signed-off-by: Victor Seiji Hariki --- index.html | 6 +- js/defaults.js | 28 +++++++++ js/index.js | 26 ++++++--- js/lib/workspaces.d.js | 0 js/lib/workspaces.js | 128 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 177 insertions(+), 11 deletions(-) create mode 100644 js/defaults.js create mode 100644 js/lib/workspaces.d.js create mode 100644 js/lib/workspaces.js diff --git a/index.html b/index.html index ce50bf7..8b9745b 100644 --- a/index.html +++ b/index.html @@ -92,7 +92,7 @@ type="number" id="seed" onchange="changeSeed()" - min="1" + min="-1" max="9999999999" value="-1" step="1" /> @@ -321,10 +321,12 @@ + + @@ -341,7 +343,7 @@ - + - + diff --git a/js/lib/layers.d.js b/js/lib/layers.d.js index b847fd7..cd331cd 100644 --- a/js/lib/layers.d.js +++ b/js/lib/layers.d.js @@ -38,7 +38,7 @@ * @property {Point} inputOffset The offset for calculating layer coordinates from input element input information * @property {Point} origin The location of the origin ((0, 0) point) of the collection (If canvas goes from -64, -32 to 128, 512, it's (64, 32)) * @property {BoundingBox} bb The current bounding box of the collection, in layer coordinates - * @property {{[key: string]: Layer}} layers An object for quick access to named layers of the collection + * @property {{[key: string]: Layer}} layers An object for quick access to layers of the collection * @property {Size} size The size of the collection (CSS) * @property {Size} resolution The resolution of the collection (canvas) * @property {function} expand Expands the collection and its full layers by the specified amounts diff --git a/js/lib/layers.js b/js/lib/layers.js index 3c513cd..3e28ee9 100644 --- a/js/lib/layers.js +++ b/js/lib/layers.js @@ -618,6 +618,7 @@ const layers = { collection._layers.splice(index, 0, layer); } if (key) collection.layers[key] = layer; + collection.layers[id] = layer; if (key === null) console.debug( @@ -651,7 +652,8 @@ const layers = { layers.listen.onlayerdelete.emit({ layer: lobj, }); - if (lobj.key) delete collection.layers[lobj.key]; + if (lobj.key) collection.layers[lobj.key] = undefined; + collection.layers[lobj.id] = undefined; collection.element.removeChild(lobj.canvas); From 006aa608e8132a0cd398f09c013a3a97f99af1d7 Mon Sep 17 00:00:00 2001 From: Victor Seiji Hariki Date: Sun, 22 Jan 2023 02:48:56 -0300 Subject: [PATCH 03/13] we have working workspaces!! Signed-off-by: Victor Seiji Hariki --- index.html | 12 +- js/index.js | 127 +++++++++++++++--- js/lib/commands.d.js | 2 + js/lib/commands.js | 270 ++++++++++++++++++++++++++++++------- js/lib/layers.js | 6 +- js/ui/floating/layers.js | 279 +++++++++++++++++++++++++++++++-------- js/ui/tool/select.js | 9 +- 7 files changed, 576 insertions(+), 129 deletions(-) diff --git a/index.html b/index.html index eaaa3c0..483eb90 100644 --- a/index.html +++ b/index.html @@ -164,6 +164,8 @@
+ +
@@ -355,8 +357,8 @@ src="js/lib/workspaces.js?v=4fbd55b" type="text/javascript"> - - + + @@ -371,13 +373,13 @@ - + @@ -393,7 +395,7 @@ src="js/ui/tool/colorbrush.js?v=3f8c01a" type="text/javascript"> -<<<<<<< HEAD -======= - ->>>>>>> main + @@ -412,6 +452,9 @@ type="text/javascript"> + diff --git a/js/index.js b/js/index.js index 8b9e149..906b14b 100644 --- a/js/index.js +++ b/js/index.js @@ -886,7 +886,7 @@ async function exportWorkspaceState() { async function importWorkspaceState(state) { // Start from zero, effectively - await commands.undo(commands._history.length); + await commands.clear(); // Setup initial layer const layer = uil.layerIndex.default; @@ -965,19 +965,6 @@ async function saveWorkspaceToFile() { link.click(); } -async function loadWorkspaceFromFile() { - const input = document.createElement("input"); - input.type = "file"; - input.accept = "application/json"; - input.addEventListener("change", async (evn) => { - let files = Array.from(input.files); - const json = await files[0].text(); - - importWorkspaceState(JSON.parse(json)); - }); - input.click(); -} - async function getUpscalers() { /* so for some reason when upscalers request returns upscalers, the real-esrgan model names are incorrect, and need to be fetched from /sdapi/v1/realesrgan-models diff --git a/js/initalize/workspace.populate.js b/js/initalize/workspace.populate.js new file mode 100644 index 0000000..39ab5af --- /dev/null +++ b/js/initalize/workspace.populate.js @@ -0,0 +1,179 @@ +(() => { + const saveWorkspaceBtn = document.getElementById("save-workspace-btn"); + const renameWorkspaceBtn = document.getElementById("rename-workspace-btn"); + const moreWorkspaceBtn = document.getElementById("more-workspace-btn"); + const expandedWorkspaceMenu = document.getElementById("more-workspace-menu"); + const exportWorkspaceBtn = document.getElementById("export-workspace-btn"); + const importWorkspaceBtn = document.getElementById("import-workspace-btn"); + const deleteWorkspaceBtn = document.getElementById("delete-workspace-btn"); + + moreWorkspaceBtn.addEventListener("click", () => { + expandedWorkspaceMenu.classList.toggle("collapsed"); + }); + + const workspaceAutocomplete = createAutoComplete( + "Workspace", + document.getElementById("workspace-select") + ); + + workspaceAutocomplete.options = [{name: "Default", value: "default"}]; + workspaceAutocomplete.value = "default"; + renameWorkspaceBtn.disabled = true; + deleteWorkspaceBtn.disabled = true; + + workspaceAutocomplete.onchange.on(async ({name, value}) => { + if (value === "default") { + renameWorkspaceBtn.disabled = true; + deleteWorkspaceBtn.disabled = true; + await commands.clear(); + return; + } + renameWorkspaceBtn.disabled = false; + deleteWorkspaceBtn.disabled = false; + + const workspaces = db + .transaction("workspaces", "readonly") + .objectStore("workspaces"); + + workspaces.get(value).onsuccess = (e) => { + console.debug("[workspace.populate] Loading workspace"); + + const res = e.target.result; + const {workspace} = res; + importWorkspaceState(workspace); + }; + }); + + /** + * Updates Workspace selection list + */ + const listWorkspaces = async (value = undefined) => { + const options = [{name: "Default", value: "default"}]; + + const workspaces = db + .transaction("workspaces", "readonly") + .objectStore("workspaces"); + + workspaces.openCursor().onsuccess = (e) => { + /** @type {IDBCursor} */ + const c = e.target.result; + if (c) { + options.push({name: c.value.name, value: c.key}); + c.continue(); + } else { + const previousValue = workspaceAutocomplete.value; + + workspaceAutocomplete.options = options; + workspaceAutocomplete.value = value ?? previousValue; + } + }; + }; + + const saveWorkspaceToDB = async (value) => { + const workspace = await exportWorkspaceState(); + + const workspaces = db + .transaction("workspaces", "readwrite") + .objectStore("workspaces"); + + let id = value; + if (value === "default" && commands._history.length > 0) { + // If Workspace is the Default + const name = (prompt("Please enter the workspace name") ?? "").trim(); + + if (name) { + id = guid(); + workspaces.add({id, name, workspace}).onsuccess = () => { + listWorkspaces(id); + alert(`Workspace saved as '${name}'`); + }; + } + } else { + workspaces.get(id).onsuccess = (e) => { + const ws = e.target.result; + if (ws) { + workspaces.put({id, workspace}).onsuccess = () => { + alert(`Workspace saved as '${ws.value.name}'`); + listWorkspaces(); + }; + } + }; + } + }; + + // Normal Workspace Export/Import + exportWorkspaceBtn.addEventListener("click", () => saveWorkspaceToFile()); + importWorkspaceBtn.addEventListener("click", () => { + const input = document.createElement("input"); + input.type = "file"; + input.accept = "application/json"; + input.addEventListener("change", async (evn) => { + let files = Array.from(input.files); + const json = await files[0].text(); + + await importWorkspaceState(JSON.parse(json)); + saveWorkspaceToDB("default"); + }); + input.click(); + }); + + const onDatabaseLoad = async () => { + // Get workspaces from database + listWorkspaces(); + + // Save Workspace Button + saveWorkspaceBtn.addEventListener( + "click", + saveWorkspaceToDB(workspaceAutocomplete.value) + ); + + // Rename Workspace + renameWorkspaceBtn.addEventListener("click", () => { + const workspaces = db + .transaction("workspaces", "readwrite") + .objectStore("workspaces"); + + let id = workspaceAutocomplete.value; + + workspaces.get(id).onsuccess = (e) => { + const workspace = e.target.result; + const name = prompt( + `Please enter the new workspace name.\nOriginal is '${workspace.name}'` + ).trim(); + + if (!name) return; + + workspace.name = name; + + workspaces.put(workspace).onsuccess = () => { + listWorkspaces(); + }; + }; + }); + // Delete Workspace + deleteWorkspaceBtn.addEventListener("click", () => { + const workspaces = db + .transaction("workspaces", "readwrite") + .objectStore("workspaces"); + + let id = workspaceAutocomplete.value; + + workspaces.get(id).onsuccess = (e) => { + const workspace = e.target.result; + + if ( + confirm( + `Do you really want to delete the workspace '${workspace.name}'?` + ) + ) { + workspaces.delete(id).onsuccess = (e) => { + listWorkspaces("default"); + }; + } + }; + }); + }; + + if (db) onDatabaseLoad(); + else ondatabaseload.on(onDatabaseLoad); +})(); diff --git a/js/lib/commands.js b/js/lib/commands.js index d8d3184..0dc492f 100644 --- a/js/lib/commands.js +++ b/js/lib/commands.js @@ -57,6 +57,21 @@ const commands = makeReadOnly( } }, + /** + * Clears the history + */ + async clear() { + await this.undo(this._history.length); + + this._history.splice(0, this._history.length); + + _commands_events.emit({ + action: "clear", + state: {}, + current: commands._current, + }); + }, + /** * Imports an exported command and runs it * diff --git a/js/lib/db.js b/js/lib/db.js new file mode 100644 index 0000000..9e9c530 --- /dev/null +++ b/js/lib/db.js @@ -0,0 +1,36 @@ +const idb = window.indexedDB.open("openoutpaint", 2); + +idb.onerror = (e) => { + console.warn("[stamp] Failed to connect to IndexedDB"); + console.warn(e); +}; + +idb.onupgradeneeded = (e) => { + const db = e.target.result; + + console.debug(`[stamp] Setting up database version ${db.version}`); + + if (e.oldVersion < 1) { + // Resources Store + const resourcesStore = db.createObjectStore("resources", { + keyPath: "id", + }); + resourcesStore.createIndex("name", "name", {unique: false}); + } + + // Workspaces Store + const workspacesStore = db.createObjectStore("workspaces", { + keyPath: "id", + }); + workspacesStore.createIndex("name", "name", {unique: false}); +}; + +/** @type {IDBDatabase} */ +let db = null; +/** @type {Observer<{db: IDBDatabase}>} */ +const ondatabaseload = new Observer(); + +idb.onsuccess = (e) => { + db = e.target.result; + ondatabaseload.emit({db}); +}; diff --git a/js/lib/ui.js b/js/lib/ui.js index f8bfce3..36dacf4 100644 --- a/js/lib/ui.js +++ b/js/lib/ui.js @@ -209,7 +209,6 @@ function createSlider(name, wrapper, options = {}) { * @param {{name: string, value: string, optionelcb: (el: HTMLOptionElement) => void}[]} options.options Options to add to the selector * @param {object} extraEl Additional element to include in wrapper div (e.g. model refresh button) * @param {string} extraClass Additional class to attach to the autocomplete input element - * @returns {AutoCompleteElement} */ function createAutoComplete( name, diff --git a/js/ui/floating/history.js b/js/ui/floating/history.js index 87a4d9d..1594f2a 100644 --- a/js/ui/floating/history.js +++ b/js/ui/floating/history.js @@ -41,7 +41,7 @@ }; _commands_events.on((message) => { - if (message.action === "run") { + if (message.action === "run" || message.action === "clear") { Array.from(historyView.children).forEach((child) => { if ( !commands._history.find((entry) => `hist-${entry.id}` === child.id) diff --git a/js/ui/tool/stamp.js b/js/ui/tool/stamp.js index 33c61df..a7a5220 100644 --- a/js/ui/tool/stamp.js +++ b/js/ui/tool/stamp.js @@ -146,13 +146,9 @@ const stampTool = () => }; // Open IndexedDB connection - const IDBOpenRequest = window.indexedDB.open("stamp", 1); - // Synchronizes resources array with the DOM and Local Storage const syncResources = () => { // Saves to IndexedDB - /** @type {IDBDatabase} */ - const db = state.stampDB; const resources = db .transaction("resources", "readwrite") .objectStore("resources"); @@ -590,35 +586,9 @@ const stampTool = () => state.ctxmenu.resourceList = resourceList; // Performs resource fetch from IndexedDB - - IDBOpenRequest.onerror = (e) => { - console.warn("[stamp] Failed to connect to IndexedDB"); - console.warn(e); - }; - - IDBOpenRequest.onupgradeneeded = (e) => { - const db = e.target.result; - - console.debug(`[stamp] Setting up database version ${db.version}`); - - const resourcesStore = db.createObjectStore("resources", { - keyPath: "id", - }); - resourcesStore.createIndex("name", "name", {unique: false}); - }; - - IDBOpenRequest.onsuccess = async (e) => { + const loadResources = async () => { console.debug("[stamp] Connected to IndexedDB"); - state.stampDB = e.target.result; - - state.stampDB.onerror = (evn) => { - console.warn(`[stamp] Database Error:`); - console.warn(evn.target.errorCode); - }; - - /** @type {IDBDatabase} */ - const db = state.stampDB; /** @type {IDBRequest<{id: string, name: string, src: string}[]>} */ const FetchAllTransaction = db .transaction("resources") @@ -648,6 +618,9 @@ const stampTool = () => syncResources(); }; }; + + if (db) loadResources(); + else ondatabaseload.on(loadResources); } }, populateContextMenu: (menu, state) => { diff --git a/res/icons/more-horizontal.svg b/res/icons/more-horizontal.svg new file mode 100644 index 0000000..19deb1a --- /dev/null +++ b/res/icons/more-horizontal.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/res/icons/pencil.svg b/res/icons/pencil.svg new file mode 100644 index 0000000..88a4365 --- /dev/null +++ b/res/icons/pencil.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/res/icons/save.svg b/res/icons/save.svg new file mode 100644 index 0000000..d192fa4 --- /dev/null +++ b/res/icons/save.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/res/icons/upload.svg b/res/icons/upload.svg new file mode 100644 index 0000000..83c4885 --- /dev/null +++ b/res/icons/upload.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From 0f78d1e5054f2703fe47e508a0cf6bca6cf2b07c Mon Sep 17 00:00:00 2001 From: seijihariki Date: Fri, 27 Jan 2023 04:43:07 +0000 Subject: [PATCH 09/13] Fixed resource hashes --- index.html | 30 +++++++++++++++--------------- pages/configuration.html | 6 +++--- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/index.html b/index.html index 39fa69a..18a07f5 100644 --- a/index.html +++ b/index.html @@ -4,15 +4,15 @@ openOutpaint 🐠 - - + + - + - + @@ -404,13 +404,13 @@ - + - + - + - + @@ -436,24 +436,24 @@ src="js/ui/tool/generic.js?v=3e678e0" type="text/javascript"> - + - + - +