Merge pull request #86 from zero01101/testing
Just Some QOL and bugfixes
This commit is contained in:
commit
2499aa2bb0
12 changed files with 387 additions and 186 deletions
4
css/fonts.css
Normal file
4
css/fonts.css
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: "Open Sans", sans-serif;
|
||||||
|
src: url("/res/fonts/OpenSans.ttf") format("truetype");
|
||||||
|
}
|
|
@ -33,10 +33,37 @@ body {
|
||||||
border: none;
|
border: none;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
outline: none;
|
outline: none;
|
||||||
font-size: 15px;
|
padding: 0px;
|
||||||
padding: 5px;
|
}
|
||||||
|
|
||||||
|
.collapsible {
|
||||||
|
background-color: var(--c-primary);
|
||||||
|
|
||||||
|
margin-bottom: 2px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
margin-bottom: 5px;
|
|
||||||
|
transition-duration: 50ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible::before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
width: 21px;
|
||||||
|
height: 21px;
|
||||||
|
|
||||||
|
background-color: var(--c-text);
|
||||||
|
mask-image: url("/res/icons/chevron-up.svg");
|
||||||
|
-webkit-mask-image: url("/res/icons/chevron-up.svg");
|
||||||
|
mask-size: contain;
|
||||||
|
-webkit-mask-size: contain;
|
||||||
|
rotate: 90deg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible.active::before {
|
||||||
|
rotate: 180deg;
|
||||||
}
|
}
|
||||||
|
|
||||||
.display-none {
|
.display-none {
|
||||||
|
@ -44,7 +71,11 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.collapsible:hover {
|
.collapsible:hover {
|
||||||
background-color: #777;
|
background-color: var(--c-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible:active {
|
||||||
|
filter: brightness(110%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
@ -259,7 +290,7 @@ body {
|
||||||
color: #3f1f1f;
|
color: #3f1f1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
.host-field-wrapper .connection-status.cors-issue {
|
.host-field-wrapper .connection-status.webui-issue {
|
||||||
background-color: #dddd49;
|
background-color: #dddd49;
|
||||||
color: #3f3f1f;
|
color: #3f3f1f;
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,6 +145,24 @@ div.autocomplete > .autocomplete-list > .autocomplete-option:hover {
|
||||||
background-color: #dddf;
|
background-color: #dddf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.autocomplete > .autocomplete-list > .autocomplete-option.selected::after {
|
||||||
|
content: "";
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
right: 5px;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
|
||||||
|
background-color: darkgreen;
|
||||||
|
|
||||||
|
-webkit-mask-image: url("/res/icons/check.svg");
|
||||||
|
-webkit-mask-size: contain;
|
||||||
|
mask-image: url("/res/icons/check.svg");
|
||||||
|
mask-size: contain;
|
||||||
|
}
|
||||||
|
|
||||||
/* Select Input */
|
/* Select Input */
|
||||||
select > option:checked::after {
|
select > option:checked::after {
|
||||||
content: "";
|
content: "";
|
||||||
|
@ -218,3 +236,112 @@ select > option:checked::after {
|
||||||
mask-repeat: no-repeat;
|
mask-repeat: no-repeat;
|
||||||
background-color: var(--c-text);
|
background-color: var(--c-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic list
|
||||||
|
*/
|
||||||
|
|
||||||
|
.list {
|
||||||
|
height: 200px;
|
||||||
|
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
background-color: var(--c-primary);
|
||||||
|
|
||||||
|
border-top-left-radius: 5px;
|
||||||
|
border-top-right-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list > *:first-child {
|
||||||
|
border-top-left-radius: 5px;
|
||||||
|
border-top-right-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list .list-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
height: 25px;
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-right: 5px;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
color: var(--c-text);
|
||||||
|
|
||||||
|
transition-duration: 50ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list .list-item.active {
|
||||||
|
background-color: var(--c-active);
|
||||||
|
}
|
||||||
|
.list .list-item.active:hover,
|
||||||
|
.list .list-item:hover {
|
||||||
|
background-color: var(--c-hover);
|
||||||
|
}
|
||||||
|
.list .list-item.active:active,
|
||||||
|
.list .list-item:active {
|
||||||
|
background-color: var(--c-hover);
|
||||||
|
filter: brightness(120%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list .list-item > .title {
|
||||||
|
flex: 1;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
|
border: 0;
|
||||||
|
color: var(--c-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.list .list-item > .actions {
|
||||||
|
display: flex;
|
||||||
|
align-self: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list .actions > button {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
width: 25px;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list .list-item > .actions > *:hover > * {
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list .actions > button > *:first-child {
|
||||||
|
flex: 1;
|
||||||
|
margin: 3px;
|
||||||
|
|
||||||
|
-webkit-mask-size: contain;
|
||||||
|
mask-size: contain;
|
||||||
|
background-color: var(--c-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generic buttons */
|
||||||
|
.list .actions > .delete-btn > *:first-child {
|
||||||
|
-webkit-mask-image: url("/res/icons/trash.svg");
|
||||||
|
mask-image: url("/res/icons/trash.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.list .actions > .rename-btn > *:first-child {
|
||||||
|
-webkit-mask-image: url("/res/icons/edit.svg");
|
||||||
|
mask-image: url("/res/icons/edit.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.list .actions > .download-btn > *:first-child {
|
||||||
|
-webkit-mask-image: url("/res/icons/download.svg");
|
||||||
|
mask-image: url("/res/icons/download.svg");
|
||||||
|
}
|
||||||
|
|
|
@ -28,8 +28,6 @@
|
||||||
.resource-manager > .resource-list {
|
.resource-manager > .resource-list {
|
||||||
height: 200px;
|
height: 200px;
|
||||||
|
|
||||||
background-color: #ffffff66;
|
|
||||||
|
|
||||||
border-top-left-radius: 5px;
|
border-top-left-radius: 5px;
|
||||||
border-top-right-radius: 5px;
|
border-top-right-radius: 5px;
|
||||||
|
|
||||||
|
@ -37,80 +35,6 @@
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.resource-manager > .resource-list > * {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
cursor: pointer;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-manager > .resource-list > * > .resource-title {
|
|
||||||
overflow: hidden;
|
|
||||||
margin: 5px;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-manager > .resource-list > * > .actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-manager .actions > button {
|
|
||||||
display: flex;
|
|
||||||
align-items: stretch;
|
|
||||||
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
width: 30px;
|
|
||||||
aspect-ratio: 1;
|
|
||||||
|
|
||||||
background-color: transparent;
|
|
||||||
border: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-manager .actions > button:hover {
|
|
||||||
background-color: rgba(255, 255, 255, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-manager .actions > button:active {
|
|
||||||
background-color: rgba(255, 255, 255, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-manager .actions > button > *:first-child {
|
|
||||||
flex: 1;
|
|
||||||
margin: 3px;
|
|
||||||
|
|
||||||
-webkit-mask-size: contain;
|
|
||||||
mask-size: contain;
|
|
||||||
background-color: var(--c-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-manager .actions > .rename-btn > *:first-child {
|
|
||||||
-webkit-mask-image: url("/res/icons/edit.svg");
|
|
||||||
mask-image: url("/res/icons/edit.svg");
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-manager .actions > .delete-btn > *:first-child {
|
|
||||||
-webkit-mask-image: url("/res/icons/trash.svg");
|
|
||||||
mask-image: url("/res/icons/trash.svg");
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-manager > .resource-list > .selected:hover,
|
|
||||||
.resource-manager > .resource-list > *:hover {
|
|
||||||
background-color: #fff8;
|
|
||||||
}
|
|
||||||
.resource-manager > .resource-list > .selected {
|
|
||||||
background-color: #fff6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-manager > .resource-list.dragging {
|
|
||||||
background-color: #ffffff88;
|
|
||||||
transition-duration: 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-manager > .upload-button {
|
.resource-manager > .upload-button {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -62,11 +62,7 @@
|
||||||
<textarea id="negPrompt"></textarea>
|
<textarea id="negPrompt"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<label for="styleSelect">Styles:</label>
|
<label for="styleSelect">Styles:</label>
|
||||||
<select
|
<div id="style-ac-mselect"></div>
|
||||||
id="styleSelect"
|
|
||||||
class="wideSelect"
|
|
||||||
onchange="changeStyles()"
|
|
||||||
multiple></select>
|
|
||||||
<!-- <hr /> -->
|
<!-- <hr /> -->
|
||||||
</div>
|
</div>
|
||||||
<!-- SD section -->
|
<!-- SD section -->
|
||||||
|
|
114
js/index.js
114
js/index.js
|
@ -176,7 +176,7 @@ async function testHostConnection() {
|
||||||
online: () => {
|
online: () => {
|
||||||
connectionIndicator.classList.add("online");
|
connectionIndicator.classList.add("online");
|
||||||
connectionIndicator.classList.remove(
|
connectionIndicator.classList.remove(
|
||||||
"cors-issue",
|
"webui-issue",
|
||||||
"offline",
|
"offline",
|
||||||
"before",
|
"before",
|
||||||
"server-error"
|
"server-error"
|
||||||
|
@ -191,7 +191,7 @@ async function testHostConnection() {
|
||||||
"online",
|
"online",
|
||||||
"offline",
|
"offline",
|
||||||
"before",
|
"before",
|
||||||
"cors-issue"
|
"webui-issue"
|
||||||
);
|
);
|
||||||
connectionIndicatorText.textContent = "Error";
|
connectionIndicatorText.textContent = "Error";
|
||||||
connectionIndicator.title =
|
connectionIndicator.title =
|
||||||
|
@ -199,7 +199,7 @@ async function testHostConnection() {
|
||||||
connectionStatus = false;
|
connectionStatus = false;
|
||||||
},
|
},
|
||||||
corsissue: () => {
|
corsissue: () => {
|
||||||
connectionIndicator.classList.add("cors-issue");
|
connectionIndicator.classList.add("webui-issue");
|
||||||
connectionIndicator.classList.remove(
|
connectionIndicator.classList.remove(
|
||||||
"online",
|
"online",
|
||||||
"offline",
|
"offline",
|
||||||
|
@ -211,10 +211,23 @@ async function testHostConnection() {
|
||||||
"Server is online, but CORS is blocking our requests";
|
"Server is online, but CORS is blocking our requests";
|
||||||
connectionStatus = false;
|
connectionStatus = false;
|
||||||
},
|
},
|
||||||
|
apiissue: () => {
|
||||||
|
connectionIndicator.classList.add("webui-issue");
|
||||||
|
connectionIndicator.classList.remove(
|
||||||
|
"online",
|
||||||
|
"offline",
|
||||||
|
"before",
|
||||||
|
"server-error"
|
||||||
|
);
|
||||||
|
connectionIndicatorText.textContent = "API";
|
||||||
|
connectionIndicator.title =
|
||||||
|
"Server is online, but the API seems to be disabled";
|
||||||
|
connectionStatus = false;
|
||||||
|
},
|
||||||
offline: () => {
|
offline: () => {
|
||||||
connectionIndicator.classList.add("offline");
|
connectionIndicator.classList.add("offline");
|
||||||
connectionIndicator.classList.remove(
|
connectionIndicator.classList.remove(
|
||||||
"cors-issue",
|
"webui-issue",
|
||||||
"online",
|
"online",
|
||||||
"before",
|
"before",
|
||||||
"server-error"
|
"server-error"
|
||||||
|
@ -227,7 +240,7 @@ async function testHostConnection() {
|
||||||
before: () => {
|
before: () => {
|
||||||
connectionIndicator.classList.add("before");
|
connectionIndicator.classList.add("before");
|
||||||
connectionIndicator.classList.remove(
|
connectionIndicator.classList.remove(
|
||||||
"cors-issue",
|
"webui-issue",
|
||||||
"online",
|
"online",
|
||||||
"offline",
|
"offline",
|
||||||
"server-error"
|
"server-error"
|
||||||
|
@ -254,27 +267,37 @@ async function testHostConnection() {
|
||||||
var url = document.getElementById("host").value + "/startup-events";
|
var url = document.getElementById("host").value + "/startup-events";
|
||||||
// Attempt normal request
|
// Attempt normal request
|
||||||
try {
|
try {
|
||||||
/** @type {Response} */
|
// Check if API is available
|
||||||
const response = await fetch(url, {
|
const response = await fetch(
|
||||||
signal: AbortSignal.timeout(5000),
|
document.getElementById("host").value + "/sdapi/v1/options"
|
||||||
});
|
);
|
||||||
|
switch (response.status) {
|
||||||
if (response.status === 200) {
|
case 200: {
|
||||||
setConnectionStatus("online");
|
setConnectionStatus("online");
|
||||||
// Load data as soon as connection is first stablished
|
// Load data as soon as connection is first stablished
|
||||||
if (firstTimeOnline) {
|
if (firstTimeOnline) {
|
||||||
getConfig();
|
getConfig();
|
||||||
getStyles();
|
getStyles();
|
||||||
getSamplers();
|
getSamplers();
|
||||||
getUpscalers();
|
getUpscalers();
|
||||||
getModels();
|
getModels();
|
||||||
firstTimeOnline = false;
|
firstTimeOnline = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 404: {
|
||||||
|
setConnectionStatus("apiissue");
|
||||||
|
const message = `The host is online, but the API seems to be disabled. Have you run the webui with the flag --api?`;
|
||||||
|
console.error(message);
|
||||||
|
if (notify) alert(message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
setConnectionStatus("offline");
|
||||||
|
const message = `The connection with the host returned an error: ${response.status} - ${response.statusText}`;
|
||||||
|
console.error(message);
|
||||||
|
if (notify) alert(message);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
setConnectionStatus("error");
|
|
||||||
const message = `Server responded with ${response.status} - ${response.statusText}. Try running the webui with the flag '--api'`;
|
|
||||||
console.error(message);
|
|
||||||
if (notify) alert(message);
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
try {
|
try {
|
||||||
|
@ -424,6 +447,12 @@ const makeSlider = (
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const styleAutoComplete = createAutoComplete(
|
||||||
|
"Style",
|
||||||
|
document.getElementById("style-ac-mselect"),
|
||||||
|
{multiple: true}
|
||||||
|
);
|
||||||
|
|
||||||
const modelAutoComplete = createAutoComplete(
|
const modelAutoComplete = createAutoComplete(
|
||||||
"Model",
|
"Model",
|
||||||
document.getElementById("models-ac-select")
|
document.getElementById("models-ac-select")
|
||||||
|
@ -774,28 +803,23 @@ async function getStyles() {
|
||||||
stored = [];
|
stored = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
data.forEach((style) => {
|
styleAutoComplete.options = data.map((style) => ({
|
||||||
const option = document.createElement("option");
|
name: style.name,
|
||||||
option.classList.add("style-select-option");
|
value: style.name,
|
||||||
option.text = style.name;
|
title: `prompt: ${style.prompt}\nnegative: ${style.negative_prompt}`,
|
||||||
option.value = style.name;
|
}));
|
||||||
option.title = `prompt: ${style.prompt}\nnegative: ${style.negative_prompt}`;
|
styleAutoComplete.onchange.on(({value}) => {
|
||||||
if (stored.length === 0) option.selected = style.name === "None";
|
let selected = [];
|
||||||
else
|
if (value.find((v) => v === "None")) {
|
||||||
option.selected = !!stored.find(
|
styleAutoComplete.value = [];
|
||||||
(styleName) => style.name === styleName
|
} else {
|
||||||
);
|
selected = value;
|
||||||
|
|
||||||
styleSelect.add(option);
|
|
||||||
});
|
|
||||||
|
|
||||||
changeStyles();
|
|
||||||
|
|
||||||
stored.forEach((styleName, index) => {
|
|
||||||
if (!data.findIndex((style) => style.name === styleName)) {
|
|
||||||
stored.splice(index, 1);
|
|
||||||
}
|
}
|
||||||
|
stableDiffusionData.styles = selected;
|
||||||
|
localStorage.setItem("promptStyle", JSON.stringify(selected));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
styleAutoComplete.value = stored;
|
||||||
localStorage.setItem("promptStyle", JSON.stringify(stored));
|
localStorage.setItem("promptStyle", JSON.stringify(stored));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("[index] Failed to fetch prompt styles");
|
console.warn("[index] Failed to fetch prompt styles");
|
||||||
|
|
97
js/lib/ui.js
97
js/lib/ui.js
|
@ -199,11 +199,13 @@ function createSlider(name, wrapper, options = {}) {
|
||||||
* @param {string} name Name of the AutoComplete Select Element
|
* @param {string} name Name of the AutoComplete Select Element
|
||||||
* @param {HTMLDivElement} wrapper The div element that will wrap the input elements
|
* @param {HTMLDivElement} wrapper The div element that will wrap the input elements
|
||||||
* @param {object} options Extra options
|
* @param {object} options Extra options
|
||||||
* @param {{name: string, value: string}} options.options Options to add to the selector
|
* @param {boolean} options.multiple Whether multiple options can be selected
|
||||||
|
* @param {{name: string, value: string}[]} options.options Options to add to the selector
|
||||||
* @returns {AutoCompleteElement}
|
* @returns {AutoCompleteElement}
|
||||||
*/
|
*/
|
||||||
function createAutoComplete(name, wrapper, options = {}) {
|
function createAutoComplete(name, wrapper, options = {}) {
|
||||||
defaultOpt(options, {
|
defaultOpt(options, {
|
||||||
|
multiple: false,
|
||||||
options: [],
|
options: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -226,27 +228,46 @@ function createAutoComplete(name, wrapper, options = {}) {
|
||||||
const acobj = {
|
const acobj = {
|
||||||
name,
|
name,
|
||||||
wrapper,
|
wrapper,
|
||||||
_title: null,
|
_selectedOptions: new Set(),
|
||||||
_value: null,
|
|
||||||
_options: [],
|
_options: [],
|
||||||
|
|
||||||
/** @type {Observer<{name:string, value: string}>} */
|
/** @type {Observer<{name:string, value: string}>} */
|
||||||
onchange: new Observer(),
|
onchange: new Observer(),
|
||||||
|
|
||||||
get value() {
|
get value() {
|
||||||
return this._value;
|
const v = this._selectedOptions.map((opt) => opt.value);
|
||||||
|
return options.multiple ? v : v[0];
|
||||||
},
|
},
|
||||||
set value(val) {
|
set value(values) {
|
||||||
const opt = this.options.find((option) => option.value === val);
|
this._selectedOptions.clear();
|
||||||
|
|
||||||
if (!opt) return;
|
for (const val of options.multiple ? values : [values]) {
|
||||||
|
const opt = this.options.find((option) => option.value === val);
|
||||||
|
|
||||||
this._title = opt.name;
|
if (!opt) continue; // Ignore invalid options
|
||||||
this._value = opt.value;
|
|
||||||
inputEl.value = opt.name;
|
|
||||||
inputEl.title = opt.name;
|
|
||||||
|
|
||||||
this.onchange.emit({name: opt.name, value: opt.value});
|
this._selectedOptions.add(opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._sync();
|
||||||
|
},
|
||||||
|
|
||||||
|
_sync() {
|
||||||
|
const val = Array.from(this._selectedOptions).map((opt) => opt.value);
|
||||||
|
const name = Array.from(this._selectedOptions).map((opt) => opt.name);
|
||||||
|
|
||||||
|
for (const opt of this._options) {
|
||||||
|
if (acobj._selectedOptions.has(opt))
|
||||||
|
opt.optionElement.classList.add("selected");
|
||||||
|
else opt.optionElement.classList.remove("selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
updateInputField();
|
||||||
|
|
||||||
|
this.onchange.emit({
|
||||||
|
name: options.multiple ? name : name[0],
|
||||||
|
value: options.multiple ? val : val[0],
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
get options() {
|
get options() {
|
||||||
|
@ -261,15 +282,17 @@ function createAutoComplete(name, wrapper, options = {}) {
|
||||||
|
|
||||||
// Add options
|
// Add options
|
||||||
val.forEach((opt) => {
|
val.forEach((opt) => {
|
||||||
const {name, value} = opt;
|
const {name, value, title} = opt;
|
||||||
const option = {name, value};
|
|
||||||
|
|
||||||
const optionEl = document.createElement("option");
|
const optionEl = document.createElement("option");
|
||||||
optionEl.classList.add("autocomplete-option");
|
optionEl.classList.add("autocomplete-option");
|
||||||
optionEl.title = option.name;
|
optionEl.title = title || name;
|
||||||
optionEl.addEventListener("click", () => select(option));
|
|
||||||
|
|
||||||
this._options.push({name, value, optionElement: optionEl});
|
const option = {name, value, optionElement: optionEl};
|
||||||
|
|
||||||
|
this._options.push(option);
|
||||||
|
|
||||||
|
optionEl.addEventListener("click", () => select(option));
|
||||||
|
|
||||||
autocompleteEl.appendChild(optionEl);
|
autocompleteEl.appendChild(optionEl);
|
||||||
});
|
});
|
||||||
|
@ -278,6 +301,15 @@ function createAutoComplete(name, wrapper, options = {}) {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function updateInputField() {
|
||||||
|
inputEl.value = Array.from(acobj._selectedOptions)
|
||||||
|
.map((o) => o.name)
|
||||||
|
.join(", ");
|
||||||
|
inputEl.title = Array.from(acobj._selectedOptions)
|
||||||
|
.map((o) => o.name)
|
||||||
|
.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
function updateOptions() {
|
function updateOptions() {
|
||||||
const text = inputEl.value.toLowerCase().trim();
|
const text = inputEl.value.toLowerCase().trim();
|
||||||
|
|
||||||
|
@ -310,15 +342,26 @@ function createAutoComplete(name, wrapper, options = {}) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function select(options) {
|
function select(opt) {
|
||||||
ontext = false;
|
ontext = false;
|
||||||
onlist = false;
|
if (!options.multiple) {
|
||||||
|
onlist = false;
|
||||||
|
acobj._selectedOptions.clear();
|
||||||
|
autocompleteEl.classList.add("display-none");
|
||||||
|
for (const child of autocompleteEl.children) {
|
||||||
|
child.classList.remove("selected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
acobj._title = options.name;
|
if (options.multiple && acobj._selectedOptions.has(opt)) {
|
||||||
inputEl.value = options.name;
|
acobj._selectedOptions.delete(opt);
|
||||||
acobj.value = options.value;
|
opt.optionElement.classList.remove("selected");
|
||||||
|
} else {
|
||||||
|
acobj._selectedOptions.add(opt);
|
||||||
|
opt.optionElement.classList.add("selected");
|
||||||
|
}
|
||||||
|
|
||||||
autocompleteEl.classList.add("display-none");
|
acobj._sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
inputEl.addEventListener("focus", () => {
|
inputEl.addEventListener("focus", () => {
|
||||||
|
@ -331,9 +374,7 @@ function createAutoComplete(name, wrapper, options = {}) {
|
||||||
ontext = false;
|
ontext = false;
|
||||||
|
|
||||||
if (!onlist && !ontext) {
|
if (!onlist && !ontext) {
|
||||||
inputEl.value = "";
|
updateInputField();
|
||||||
updateOptions();
|
|
||||||
inputEl.value = acobj._title;
|
|
||||||
|
|
||||||
autocompleteEl.classList.add("display-none");
|
autocompleteEl.classList.add("display-none");
|
||||||
}
|
}
|
||||||
|
@ -347,9 +388,7 @@ function createAutoComplete(name, wrapper, options = {}) {
|
||||||
onlist = false;
|
onlist = false;
|
||||||
|
|
||||||
if (!onlist && !ontext) {
|
if (!onlist && !ontext) {
|
||||||
inputEl.value = "";
|
updateInputField();
|
||||||
updateOptions();
|
|
||||||
inputEl.value = acobj._title;
|
|
||||||
|
|
||||||
autocompleteEl.classList.add("display-none");
|
autocompleteEl.classList.add("display-none");
|
||||||
}
|
}
|
||||||
|
|
|
@ -208,7 +208,7 @@ const _generate = async (
|
||||||
at = 1;
|
at = 1;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(
|
alert(
|
||||||
`Error generating images. Please try again or see consolde for more details`
|
`Error generating images. Please try again or see console for more details`
|
||||||
);
|
);
|
||||||
console.warn(`[dream] Error generating images:`);
|
console.warn(`[dream] Error generating images:`);
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
|
@ -235,6 +235,8 @@ const _generate = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
const applyImg = async () => {
|
const applyImg = async () => {
|
||||||
|
if (!images[at]) return;
|
||||||
|
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
// load the image data after defining the closure
|
// load the image data after defining the closure
|
||||||
img.src = "data:image/png;base64," + images[at];
|
img.src = "data:image/png;base64," + images[at];
|
||||||
|
@ -259,7 +261,7 @@ const _generate = async (
|
||||||
imageindextxt.textContent = `${at}/${images.length - 1}`;
|
imageindextxt.textContent = `${at}/${images.length - 1}`;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
alert(
|
alert(
|
||||||
`Error generating images. Please try again or see consolde for more details`
|
`Error generating images. Please try again or see console for more details`
|
||||||
);
|
);
|
||||||
console.warn(`[dream] Error generating images:`);
|
console.warn(`[dream] Error generating images:`);
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
|
@ -273,6 +275,25 @@ const _generate = async (
|
||||||
clean();
|
clean();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const saveImg = async () => {
|
||||||
|
if (!images[at]) return;
|
||||||
|
|
||||||
|
const img = new Image();
|
||||||
|
// load the image data after defining the closure
|
||||||
|
img.src = "data:image/png;base64," + images[at];
|
||||||
|
img.addEventListener("load", () => {
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = img.width;
|
||||||
|
canvas.height = img.height;
|
||||||
|
canvas.getContext("2d").drawImage(img, 0, 0);
|
||||||
|
|
||||||
|
downloadCanvas({
|
||||||
|
canvas,
|
||||||
|
filename: `openOutpaint - dream - ${request.prompt} - ${at}.png`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Listen for keyboard arrows
|
// Listen for keyboard arrows
|
||||||
const onarrow = (evn) => {
|
const onarrow = (evn) => {
|
||||||
switch (evn.target.tagName.toLowerCase()) {
|
switch (evn.target.tagName.toLowerCase()) {
|
||||||
|
@ -385,6 +406,14 @@ const _generate = async (
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
imageSelectMenu.appendChild(resourcebtn);
|
imageSelectMenu.appendChild(resourcebtn);
|
||||||
|
|
||||||
|
const savebtn = document.createElement("button");
|
||||||
|
savebtn.textContent = "S";
|
||||||
|
savebtn.title = "Download image to computer";
|
||||||
|
savebtn.addEventListener("click", async () => {
|
||||||
|
saveImg();
|
||||||
|
});
|
||||||
|
imageSelectMenu.appendChild(savebtn);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -44,7 +44,7 @@ const stampTool = () =>
|
||||||
// Deselect
|
// Deselect
|
||||||
state.selected = null;
|
state.selected = null;
|
||||||
Array.from(state.ctxmenu.resourceList.children).forEach((child) => {
|
Array.from(state.ctxmenu.resourceList.children).forEach((child) => {
|
||||||
child.classList.remove("selected");
|
child.classList.remove("active");
|
||||||
});
|
});
|
||||||
|
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
|
@ -71,20 +71,20 @@ const stampTool = () =>
|
||||||
const resourceWrapper = resource && resource.dom.wrapper;
|
const resourceWrapper = resource && resource.dom.wrapper;
|
||||||
|
|
||||||
const wasSelected =
|
const wasSelected =
|
||||||
resourceWrapper && resourceWrapper.classList.contains("selected");
|
resourceWrapper && resourceWrapper.classList.contains("active");
|
||||||
|
|
||||||
Array.from(state.ctxmenu.resourceList.children).forEach((child) => {
|
Array.from(state.ctxmenu.resourceList.children).forEach((child) => {
|
||||||
child.classList.remove("selected");
|
child.classList.remove("active");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Select
|
// Select
|
||||||
if (!wasSelected) {
|
if (!wasSelected) {
|
||||||
resourceWrapper && resourceWrapper.classList.add("selected");
|
resourceWrapper && resourceWrapper.classList.add("active");
|
||||||
state.selected = resource;
|
state.selected = resource;
|
||||||
}
|
}
|
||||||
// If already selected, clear selection
|
// If already selected, clear selection
|
||||||
else {
|
else {
|
||||||
resourceWrapper.classList.remove("selected");
|
resourceWrapper.classList.remove("active");
|
||||||
state.selected = null;
|
state.selected = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,16 +136,35 @@ const stampTool = () =>
|
||||||
);
|
);
|
||||||
const resourceWrapper = document.createElement("div");
|
const resourceWrapper = document.createElement("div");
|
||||||
resourceWrapper.id = `resource-${resource.id}`;
|
resourceWrapper.id = `resource-${resource.id}`;
|
||||||
resourceWrapper.classList.add("resource");
|
resourceWrapper.classList.add("resource", "list-item");
|
||||||
const resourceTitle = document.createElement("span");
|
const resourceTitle = document.createElement("input");
|
||||||
resourceTitle.textContent = resource.name;
|
resourceTitle.value = resource.name;
|
||||||
resourceTitle.classList.add("resource-title");
|
resourceTitle.title = resource.name;
|
||||||
|
resourceTitle.style.pointerEvents = "none";
|
||||||
|
resourceTitle.addEventListener("change", () => {
|
||||||
|
resource.name = resourceTitle.value;
|
||||||
|
resourceTitle.title = resourceTitle.value;
|
||||||
|
|
||||||
|
syncResources();
|
||||||
|
});
|
||||||
|
|
||||||
|
resourceTitle.addEventListener("blur", () => {
|
||||||
|
resourceTitle.style.pointerEvents = "none";
|
||||||
|
});
|
||||||
|
resourceTitle.classList.add("resource-title", "title");
|
||||||
|
|
||||||
resourceWrapper.appendChild(resourceTitle);
|
resourceWrapper.appendChild(resourceTitle);
|
||||||
|
|
||||||
resourceWrapper.addEventListener("click", () =>
|
resourceWrapper.addEventListener("click", () =>
|
||||||
state.selectResource(resource)
|
state.selectResource(resource)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
resourceWrapper.addEventListener("dblclick", () => {
|
||||||
|
resourceTitle.style.pointerEvents = "auto";
|
||||||
|
resourceTitle.focus();
|
||||||
|
resourceTitle.select();
|
||||||
|
});
|
||||||
|
|
||||||
resourceWrapper.addEventListener("mouseover", () => {
|
resourceWrapper.addEventListener("mouseover", () => {
|
||||||
state.ctxmenu.previewPane.style.display = "block";
|
state.ctxmenu.previewPane.style.display = "block";
|
||||||
state.ctxmenu.previewPane.style.backgroundImage = `url(${resource.image.src})`;
|
state.ctxmenu.previewPane.style.backgroundImage = `url(${resource.image.src})`;
|
||||||
|
@ -158,24 +177,25 @@ const stampTool = () =>
|
||||||
const actionArray = document.createElement("div");
|
const actionArray = document.createElement("div");
|
||||||
actionArray.classList.add("actions");
|
actionArray.classList.add("actions");
|
||||||
|
|
||||||
const renameButton = document.createElement("button");
|
const saveButton = document.createElement("button");
|
||||||
renameButton.addEventListener(
|
saveButton.addEventListener(
|
||||||
"click",
|
"click",
|
||||||
(evn) => {
|
(evn) => {
|
||||||
evn.stopPropagation();
|
const canvas = document.createElement("canvas");
|
||||||
const name = prompt("Rename your resource:", resource.name);
|
canvas.width = resource.image.width;
|
||||||
if (name) {
|
canvas.height = resource.image.height;
|
||||||
resource.name = name;
|
canvas.getContext("2d").drawImage(resource.image, 0, 0);
|
||||||
resourceTitle.textContent = name;
|
|
||||||
|
|
||||||
syncResources();
|
downloadCanvas({
|
||||||
}
|
canvas,
|
||||||
|
filename: `openOutpaint - resource '${resource.name}'.png`,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
{passive: false}
|
{passive: false}
|
||||||
);
|
);
|
||||||
renameButton.title = "Rename Resource";
|
saveButton.title = "Download Resource";
|
||||||
renameButton.appendChild(document.createElement("div"));
|
saveButton.appendChild(document.createElement("div"));
|
||||||
renameButton.classList.add("rename-btn");
|
saveButton.classList.add("download-btn");
|
||||||
|
|
||||||
const trashButton = document.createElement("button");
|
const trashButton = document.createElement("button");
|
||||||
trashButton.addEventListener(
|
trashButton.addEventListener(
|
||||||
|
@ -191,7 +211,7 @@ const stampTool = () =>
|
||||||
trashButton.appendChild(document.createElement("div"));
|
trashButton.appendChild(document.createElement("div"));
|
||||||
trashButton.classList.add("delete-btn");
|
trashButton.classList.add("delete-btn");
|
||||||
|
|
||||||
actionArray.appendChild(renameButton);
|
actionArray.appendChild(saveButton);
|
||||||
actionArray.appendChild(trashButton);
|
actionArray.appendChild(trashButton);
|
||||||
resourceWrapper.appendChild(actionArray);
|
resourceWrapper.appendChild(actionArray);
|
||||||
state.ctxmenu.resourceList.appendChild(resourceWrapper);
|
state.ctxmenu.resourceList.appendChild(resourceWrapper);
|
||||||
|
@ -340,7 +360,7 @@ const stampTool = () =>
|
||||||
const resourceManager = document.createElement("div");
|
const resourceManager = document.createElement("div");
|
||||||
resourceManager.classList.add("resource-manager");
|
resourceManager.classList.add("resource-manager");
|
||||||
const resourceList = document.createElement("div");
|
const resourceList = document.createElement("div");
|
||||||
resourceList.classList.add("resource-list");
|
resourceList.classList.add("list");
|
||||||
|
|
||||||
const previewPane = document.createElement("div");
|
const previewPane = document.createElement("div");
|
||||||
previewPane.classList.add("preview-pane");
|
previewPane.classList.add("preview-pane");
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
@echo off
|
@echo off
|
||||||
python -m http.server 3456
|
python -m http.server -b 0.0.0.0 3456
|
BIN
res/fonts/OpenSans.ttf
Normal file
BIN
res/fonts/OpenSans.ttf
Normal file
Binary file not shown.
7
res/icons/download.svg
Normal file
7
res/icons/download.svg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||||
|
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
||||||
|
<polyline points="7 10 12 15 17 10"></polyline>
|
||||||
|
<line x1="12" y1="15" x2="12" y2="3"></line>
|
||||||
|
|
||||||
|
</svg>
|
After Width: | Height: | Size: 350 B |
Loading…
Reference in a new issue