diff --git a/css/icons.css b/css/icons.css index a3b8ca9..e808b3b 100644 --- a/css/icons.css +++ b/css/icons.css @@ -1,3 +1,34 @@ +.ui.inline-icon { + position: relative; + + display: flex; +} + +.ui.inline-icon::after { + content: ""; + display: block; + + position: absolute; + + box-sizing: border-box; + + margin: auto; + top: 15%; + bottom: 15%; + + mask-size: contain; + mask-repeat: no-repeat; + + max-height: 70%; + aspect-ratio: 1; + + left: 0; + right: 0; + + background-color: var(--c-text); +} + +.ui.inline-icon.icon-eye-off::after, .ui.icon > .icon-eye-off { -webkit-mask-image: url("../res/icons/eye-off.svg"); mask-image: url("../res/icons/eye-off.svg"); @@ -42,3 +73,57 @@ -webkit-mask-image: url("../res/icons/settings.svg"); mask-image: url("../res/icons/settings.svg"); } + +.ui.inline-icon.icon-grid::after, +.ui.icon > .icon-grid { + -webkit-mask-image: url("../res/icons/grid.svg"); + mask-image: url("../res/icons/grid.svg"); +} + +.ui.inline-icon.icon-venetian-mask::after, +.ui.icon > .icon-venetian-mask { + -webkit-mask-image: url("../res/icons/venetian-mask.svg"); + mask-image: url("../res/icons/venetian-mask.svg"); +} + +.ui.inline-icon.icon-brush::after, +.ui.icon > .icon-brush { + -webkit-mask-image: url("../res/icons/brush.svg"); + mask-image: url("../res/icons/brush.svg"); +} + +.ui.inline-icon.icon-paintbrush::after, +.ui.icon > .icon-paintbrush { + -webkit-mask-image: url("../res/icons/paintbrush.svg"); + mask-image: url("../res/icons/paintbrush.svg"); +} + +.ui.inline-icon.icon-expand::after, +.ui.icon > .icon-expand { + -webkit-mask-image: url("../res/icons/expand.svg"); + mask-image: url("../res/icons/expand.svg"); +} + +.ui.inline-icon.icon-pin::after, +.ui.icon > .icon-pin { + -webkit-mask-image: url("../res/icons/pin.svg"); + mask-image: url("../res/icons/pin.svg"); +} + +.ui.inline-icon.icon-box-select::after, +.ui.icon > .icon-box-select { + -webkit-mask-image: url("../res/icons/box-select.svg"); + mask-image: url("../res/icons/box-select.svg"); +} + +.ui.inline-icon.icon-maximize::after, +.ui.icon > .icon-maximize { + -webkit-mask-image: url("../res/icons/maximize.svg"); + mask-image: url("../res/icons/maximize.svg"); +} + +.ui.inline-icon.icon-clipboard-list::after, +.ui.icon > .icon-clipboard-list { + -webkit-mask-image: url("../res/icons/clipboard-list.svg"); + mask-image: url("../res/icons/clipboard-list.svg"); +} diff --git a/css/ui/generic.css b/css/ui/generic.css index 93caf35..801e2ba 100644 --- a/css/ui/generic.css +++ b/css/ui/generic.css @@ -37,6 +37,8 @@ /* Slider Input */ div.slider-wrapper { margin: 5px; + margin-left: 0; + margin-right: 0; } div.slider-wrapper { @@ -100,6 +102,83 @@ div.slider-wrapper > input.text { background-color: transparent; } +/* Checkbox Input */ +div.checkbox-array { + display: flex; + + margin-top: 5px; + margin-bottom: 5px; +} + +input.oo-checkbox[type="checkbox"] { + /* Hide original checkbox */ + -webkit-appearance: none; + appearance: none; + + flex: 1; + + margin: 0; + + min-width: 28px; + height: 28px; + + background-color: var(--c-primary); + + cursor: pointer; +} + +input.oo-checkbox[type="checkbox"]:disabled { + background-color: #666 !important; + cursor: default !important; +} + +input.oo-checkbox[type="checkbox"]:disabled:hover { + filter: none !important; +} + +input.oo-checkbox[type="checkbox"]:checked::after { + background-color: #66f; +} + +input.oo-checkbox[type="checkbox"]:hover { + background-color: var(--c-hover); +} + +input.oo-checkbox[type="checkbox"]:active { + filter: brightness(120%); +} + +input.oo-checkbox[type="checkbox"]:first-child { + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; +} + +input.oo-checkbox[type="checkbox"]:last-child { + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; +} + +/* Mask Inversion Checkbox */ +input.oo-checkbox[type="checkbox"].invert-mask-checkbox::after { + background-color: var(--c-text); +} + +input.oo-checkbox[type="checkbox"].invert-mask-checkbox:hover { + filter: brightness(120%); +} + +input.oo-checkbox[type="checkbox"].invert-mask-checkbox:active { + filter: brightness(140%); +} + +input.oo-checkbox[type="checkbox"].invert-mask-checkbox { + background-color: #922; +} + +input.oo-checkbox[type="checkbox"].invert-mask-checkbox:checked { + background-color: #229; +} + /* Bare Select */ .bareselector { diff --git a/index.html b/index.html index c9d9462..b2bef61 100644 --- a/index.html +++ b/index.html @@ -5,12 +5,12 @@ openOutpaint 🐠 - + - + @@ -319,7 +319,7 @@ - + @@ -328,7 +328,7 @@ - + - + - + - + diff --git a/js/global.js b/js/global.js index 19a3542..72cb565 100644 --- a/js/global.js +++ b/js/global.js @@ -3,6 +3,11 @@ */ const global = { + // If this is the first run of openOutpaint + get firstRun() { + return this._firstRun; + }, + // Connection _connection: "offline", set connection(v) { @@ -46,3 +51,5 @@ const global = { this.debug = !this.debug; }, }; + +global._firstRun = !localStorage.getItem("openoutpaint/host"); diff --git a/js/index.js b/js/index.js index ab002f0..f950f1f 100644 --- a/js/index.js +++ b/js/index.js @@ -139,7 +139,6 @@ var host = ""; var url = "/sdapi/v1/"; const basePixelCount = 64; //64 px - ALWAYS 64 PX -// function startup() { testHostConfiguration(); loadSettings(); @@ -857,12 +856,14 @@ async function getUpscalers() { } async function getModels() { - var url = document.getElementById("host").value + "/sdapi/v1/sd-models"; + const url = document.getElementById("host").value + "/sdapi/v1/sd-models"; + let opt = null; + try { const response = await fetch(url); const data = await response.json(); - modelAutoComplete.options = data.map((option) => ({ + opt = data.map((option) => ({ name: option.title, value: option.title, optionelcb: (el) => { @@ -871,6 +872,8 @@ async function getModels() { }, })); + modelAutoComplete.options = opt; + try { const optResponse = await fetch( document.getElementById("host").value + "/sdapi/v1/options" @@ -891,10 +894,10 @@ async function getModels() { modelAutoComplete.onchange.on(async ({value}) => { console.log(`[index] Changing model to [${value}]`); - var payload = { + const payload = { sd_model_checkpoint: value, }; - var url = document.getElementById("host").value + "/sdapi/v1/options/"; + const url = document.getElementById("host").value + "/sdapi/v1/options/"; try { await fetch(url, { method: "POST", @@ -914,6 +917,25 @@ async function getModels() { ); } }); + + // If first time running, ask if user wants to switch to an inpainting model + if (global.firstRun && !modelAutoComplete.value.includes("inpainting")) { + const inpainting = opt.find(({name}) => name.includes("inpainting")); + + let message = + "It seems this is your first time using openOutpaint. It is highly recommended that you switch to an inpainting model. \ + These are highlighted as green in the model selector."; + + if (inpainting) { + message += `\n\nWe have found the inpainting model\n\n - ${inpainting.name}\n\navailable in the webui. Do you want to switch to it?`; + if (confirm(message)) { + modelAutoComplete.value = inpainting.value; + } + } else { + message += `\n\nNo inpainting model seems to be available in the webui. It is recommended that you download an inpainting model, or outpainting results may not be optimal.`; + alert(message); + } + } } async function getConfig() { diff --git a/js/lib/toolbar.js b/js/lib/toolbar.js index 232b0fe..10e48f8 100644 --- a/js/lib/toolbar.js +++ b/js/lib/toolbar.js @@ -145,17 +145,24 @@ const toolbar = { * Premade inputs for populating the context menus */ const _toolbar_input = { - checkbox: (state, dataKey, text, cb = null) => { + checkbox: (state, dataKey, text, classes, cb = null) => { if (state[dataKey] === undefined) state[dataKey] = false; const checkbox = document.createElement("input"); checkbox.type = "checkbox"; + checkbox.classList.add("oo-checkbox", "ui", "inline-icon"); + + if (typeof classes === "string") classes = [classes]; + + if (classes) checkbox.classList.add(...classes); checkbox.checked = state[dataKey]; checkbox.onchange = () => { state[dataKey] = checkbox.checked; cb && cb(); }; + checkbox.title = text; + const label = document.createElement("label"); label.appendChild(checkbox); label.appendChild(new Text(text)); diff --git a/js/ui/tool/colorbrush.js b/js/ui/tool/colorbrush.js index 5786307..dc1d190 100644 --- a/js/ui/tool/colorbrush.js +++ b/js/ui/tool/colorbrush.js @@ -349,13 +349,16 @@ const colorBrushTool = () => state.ctxmenu = {}; // Affects Mask Checkbox + const array = document.createElement("div"); const affectMaskCheckbox = _toolbar_input.checkbox( state, "affectMask", - "Affect Mask" - ).label; + "Affect Mask", + "icon-venetian-mask" + ).checkbox; + array.appendChild(affectMaskCheckbox); - state.ctxmenu.affectMaskCheckbox = affectMaskCheckbox; + state.ctxmenu.affectMaskCheckbox = array; // Brush size slider const brushSizeSlider = _toolbar_input.slider( diff --git a/js/ui/tool/dream.js b/js/ui/tool/dream.js index 53428a1..18e42ad 100644 --- a/js/ui/tool/dream.js +++ b/js/ui/tool/dream.js @@ -1473,24 +1473,27 @@ const dreamTool = () => state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox( state, "snapToGrid", - "Snap To Grid" - ).label; + "Snap To Grid", + "icon-grid" + ).checkbox; // Invert Mask Checkbox state.ctxmenu.invertMaskLabel = _toolbar_input.checkbox( state, "invertMask", "Invert Mask", + ["icon-venetian-mask", "invert-mask-checkbox"], () => { setMask(state.invertMask ? "hold" : "clear"); } - ).label; + ).checkbox; // Keep Masked Content Checkbox state.ctxmenu.keepUnmaskedLabel = _toolbar_input.checkbox( state, "keepUnmasked", "Keep Unmasked", + "icon-pin", () => { if (state.keepUnmasked) { state.ctxmenu.keepUnmaskedBlurSlider.classList.remove( @@ -1506,7 +1509,7 @@ const dreamTool = () => ); } } - ).label; + ).checkbox; // Keep Masked Content Blur Slider state.ctxmenu.keepUnmaskedBlurSlider = _toolbar_input.slider( @@ -1531,8 +1534,9 @@ const dreamTool = () => state.ctxmenu.preserveMasksLabel = _toolbar_input.checkbox( state, "preserveMasks", - "Preserve Brushed Masks" - ).label; + "Preserve Brushed Masks", + "icon-paintbrush" + ).checkbox; // Overmasking Slider state.ctxmenu.overMaskPxLabel = _toolbar_input.slider( @@ -1562,15 +1566,19 @@ const dreamTool = () => } menu.appendChild(state.ctxmenu.cursorSizeSlider); - menu.appendChild(state.ctxmenu.snapToGridLabel); - menu.appendChild(document.createElement("br")); - menu.appendChild(state.ctxmenu.invertMaskLabel); - menu.appendChild(document.createElement("br")); - menu.appendChild(state.ctxmenu.keepUnmaskedLabel); + const array = document.createElement("div"); + array.classList.add("checkbox-array"); + array.appendChild(state.ctxmenu.snapToGridLabel); + //menu.appendChild(document.createElement("br")); + array.appendChild(state.ctxmenu.invertMaskLabel); + array.appendChild(state.ctxmenu.preserveMasksLabel); + //menu.appendChild(document.createElement("br")); + array.appendChild(state.ctxmenu.keepUnmaskedLabel); + menu.appendChild(array); menu.appendChild(state.ctxmenu.keepUnmaskedBlurSlider); - menu.appendChild(state.ctxmenu.keepUnmaskedBlurSliderLinebreak); - menu.appendChild(state.ctxmenu.preserveMasksLabel); - menu.appendChild(document.createElement("br")); + // menu.appendChild(state.ctxmenu.keepUnmaskedBlurSliderLinebreak); + // menu.appendChild(state.ctxmenu.preserveMasksLabel); + // menu.appendChild(document.createElement("br")); menu.appendChild(state.ctxmenu.overMaskPxLabel); menu.appendChild(state.ctxmenu.eagerGenerateCountLabel); }, @@ -1991,24 +1999,27 @@ const img2imgTool = () => state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox( state, "snapToGrid", - "Snap To Grid" - ).label; + "Snap To Grid", + "icon-grid" + ).checkbox; // Invert Mask Checkbox state.ctxmenu.invertMaskLabel = _toolbar_input.checkbox( state, "invertMask", "Invert Mask", + ["icon-venetian-mask", "invert-mask-checkbox"], () => { setMask(state.invertMask ? "hold" : "clear"); } - ).label; + ).checkbox; // Keep Masked Content Checkbox state.ctxmenu.keepUnmaskedLabel = _toolbar_input.checkbox( state, "keepUnmasked", "Keep Unmasked", + "icon-pin", () => { if (state.keepUnmasked) { state.ctxmenu.keepUnmaskedBlurSlider.classList.remove( @@ -2024,7 +2035,7 @@ const img2imgTool = () => ); } } - ).label; + ).checkbox; // Keep Masked Content Blur Slider state.ctxmenu.keepUnmaskedBlurSlider = _toolbar_input.slider( @@ -2049,15 +2060,17 @@ const img2imgTool = () => state.ctxmenu.preserveMasksLabel = _toolbar_input.checkbox( state, "preserveMasks", - "Preserve Brushed Masks" - ).label; + "Preserve Brushed Masks", + "icon-paintbrush" + ).checkbox; // Inpaint Full Resolution Checkbox state.ctxmenu.fullResolutionLabel = _toolbar_input.checkbox( state, "fullResolution", - "Inpaint Full Resolution" - ).label; + "Inpaint Full Resolution", + "icon-expand" + ).checkbox; // Denoising Strength Slider state.ctxmenu.denoisingStrengthSlider = _toolbar_input.slider( @@ -2076,8 +2089,9 @@ const img2imgTool = () => state.ctxmenu.borderMaskGradientCheckbox = _toolbar_input.checkbox( state, "gradient", - "Border Mask Gradient" - ).label; + "Border Mask Gradient", + "icon-box-select" + ).checkbox; // Border Mask Size Slider state.ctxmenu.borderMaskSlider = _toolbar_input.slider( @@ -2124,20 +2138,22 @@ const img2imgTool = () => } menu.appendChild(state.ctxmenu.cursorSizeSlider); - menu.appendChild(state.ctxmenu.snapToGridLabel); - menu.appendChild(document.createElement("br")); - menu.appendChild(state.ctxmenu.invertMaskLabel); - menu.appendChild(document.createElement("br")); - menu.appendChild(state.ctxmenu.keepUnmaskedLabel); + const array = document.createElement("div"); + array.classList.add("checkbox-array"); + array.appendChild(state.ctxmenu.snapToGridLabel); + array.appendChild(state.ctxmenu.invertMaskLabel); + array.appendChild(state.ctxmenu.preserveMasksLabel); + array.appendChild(state.ctxmenu.keepUnmaskedLabel); + menu.appendChild(array); menu.appendChild(state.ctxmenu.keepUnmaskedBlurSlider); menu.appendChild(state.ctxmenu.keepUnmaskedBlurSliderLinebreak); - menu.appendChild(state.ctxmenu.preserveMasksLabel); - menu.appendChild(document.createElement("br")); - menu.appendChild(state.ctxmenu.fullResolutionLabel); - menu.appendChild(document.createElement("br")); menu.appendChild(state.ctxmenu.inpaintTypeSelect); menu.appendChild(state.ctxmenu.denoisingStrengthSlider); - menu.appendChild(state.ctxmenu.borderMaskGradientCheckbox); + const btnArray2 = document.createElement("div"); + btnArray2.classList.add("checkbox-array"); + btnArray2.appendChild(state.ctxmenu.fullResolutionLabel); + btnArray2.appendChild(state.ctxmenu.borderMaskGradientCheckbox); + menu.appendChild(btnArray2); menu.appendChild(state.ctxmenu.borderMaskSlider); menu.appendChild(state.ctxmenu.eagerGenerateCountLabel); }, diff --git a/js/ui/tool/interrogate.js b/js/ui/tool/interrogate.js index 2090c03..b7fc403 100644 --- a/js/ui/tool/interrogate.js +++ b/js/ui/tool/interrogate.js @@ -97,8 +97,9 @@ const interrogateTool = () => state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox( state, "snapToGrid", - "Snap To Grid" - ).label; + "Snap To Grid", + "icon-grid" + ).checkbox; } menu.appendChild(state.ctxmenu.cursorSizeSlider); diff --git a/js/ui/tool/select.js b/js/ui/tool/select.js index c62cf14..11eb700 100644 --- a/js/ui/tool/select.js +++ b/js/ui/tool/select.js @@ -633,23 +633,26 @@ const selectTransformTool = () => state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox( state, "snapToGrid", - "Snap To Grid" - ).label; + "Snap To Grid", + "icon-grid" + ).checkbox; // Keep Aspect Ratio state.ctxmenu.keepAspectRatioLabel = _toolbar_input.checkbox( state, "keepAspectRatio", - "Keep Aspect Ratio" - ).label; + "Keep Aspect Ratio", + "icon-maximize" + ).checkbox; // Use Clipboard const clipboardCheckbox = _toolbar_input.checkbox( state, "useClipboard", - "Use clipboard" + "Use clipboard", + "icon-clipboard-list" ); - state.ctxmenu.useClipboardLabel = clipboardCheckbox.label; + state.ctxmenu.useClipboardLabel = clipboardCheckbox.checkbox; if (!(navigator.clipboard && navigator.clipboard.write)) clipboardCheckbox.checkbox.disabled = true; // Disable if not available @@ -760,11 +763,12 @@ const selectTransformTool = () => state.ctxmenu.actionArray = actionArray; state.ctxmenu.visibleActionArray = visibleActionArray; } - menu.appendChild(state.ctxmenu.snapToGridLabel); - menu.appendChild(document.createElement("br")); - menu.appendChild(state.ctxmenu.keepAspectRatioLabel); - menu.appendChild(document.createElement("br")); - menu.appendChild(state.ctxmenu.useClipboardLabel); + const array = document.createElement("div"); + array.classList.add("checkbox-array"); + array.appendChild(state.ctxmenu.snapToGridLabel); + array.appendChild(state.ctxmenu.keepAspectRatioLabel); + array.appendChild(state.ctxmenu.useClipboardLabel); + menu.appendChild(array); menu.appendChild(state.ctxmenu.selectionPeekOpacitySlider); menu.appendChild(state.ctxmenu.actionArray); menu.appendChild(state.ctxmenu.visibleActionArray); diff --git a/js/ui/tool/stamp.js b/js/ui/tool/stamp.js index 74be4ad..fc1cb89 100644 --- a/js/ui/tool/stamp.js +++ b/js/ui/tool/stamp.js @@ -368,11 +368,17 @@ const stampTool = () => if (!state.ctxmenu) { state.ctxmenu = {}; // Snap To Grid Checkbox - state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox( - state, - "snapToGrid", - "Snap To Grid" - ).label; + const array = document.createElement("div"); + array.classList.add("checkbox-array"); + array.appendChild( + _toolbar_input.checkbox( + state, + "snapToGrid", + "Snap To Grid", + "icon-grid" + ).checkbox + ); + state.ctxmenu.snapToGridLabel = array; // Create resource list const uploadButtonId = `upload-btn-${guid()}`; diff --git a/pages/configuration.html b/pages/configuration.html index 81409ec..5e53994 100644 --- a/pages/configuration.html +++ b/pages/configuration.html @@ -5,12 +5,12 @@ openOutpaint 🐠 - + - + diff --git a/res/icons/clipboard-list.svg b/res/icons/clipboard-list.svg new file mode 100644 index 0000000..45bbcf5 --- /dev/null +++ b/res/icons/clipboard-list.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/res/icons/equal.svg b/res/icons/equal.svg new file mode 100644 index 0000000..6b5c5c8 --- /dev/null +++ b/res/icons/equal.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/res/icons/expand.svg b/res/icons/expand.svg new file mode 100644 index 0000000..6f5864a --- /dev/null +++ b/res/icons/expand.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/res/icons/grid.svg b/res/icons/grid.svg new file mode 100644 index 0000000..a7daf6d --- /dev/null +++ b/res/icons/grid.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/res/icons/maximize.svg b/res/icons/maximize.svg new file mode 100644 index 0000000..5c8a6e3 --- /dev/null +++ b/res/icons/maximize.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/res/icons/pin.svg b/res/icons/pin.svg new file mode 100644 index 0000000..aa858be --- /dev/null +++ b/res/icons/pin.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/res/icons/square.svg b/res/icons/square.svg new file mode 100644 index 0000000..ba8d095 --- /dev/null +++ b/res/icons/square.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/res/icons/venetian-mask.svg b/res/icons/venetian-mask.svg new file mode 100644 index 0000000..6cd8962 --- /dev/null +++ b/res/icons/venetian-mask.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file