Merge branch 'main' into workspaces
Signed-off-by: Victor Seiji Hariki <victorseijih@gmail.com>
This commit is contained in:
commit
fa82cfd35d
16 changed files with 298 additions and 108 deletions
|
@ -650,3 +650,8 @@ select > .style-select-option:active {
|
|||
.thirdwidth {
|
||||
max-width: 33%;
|
||||
}
|
||||
|
||||
.refreshbutton {
|
||||
max-width: 50%;
|
||||
max-height: 50%;
|
||||
}
|
||||
|
|
|
@ -212,6 +212,10 @@ div.autocomplete > .autocomplete-text {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
div.autocomplete > .refreshable {
|
||||
width: 82% !important;
|
||||
}
|
||||
|
||||
div.autocomplete > .autocomplete-list {
|
||||
position: absolute;
|
||||
|
||||
|
|
|
@ -149,6 +149,7 @@
|
|||
cursor: pointer;
|
||||
|
||||
transition-duration: 300ms;
|
||||
transition-property: background-color;
|
||||
|
||||
border: 2px solid #293d3d30;
|
||||
}
|
||||
|
|
32
index.html
32
index.html
|
@ -7,13 +7,13 @@
|
|||
<link href="css/colors.css?v=3f81e80" rel="stylesheet" />
|
||||
<link href="css/icons.css?v=9ae0466" rel="stylesheet" />
|
||||
|
||||
<link href="css/index.css?v=5b8d4d6" rel="stylesheet" />
|
||||
<link href="css/index.css?v=882f400" rel="stylesheet" />
|
||||
<link href="css/layers.css?v=92c0352" rel="stylesheet" />
|
||||
|
||||
<link href="css/ui/generic.css?v=802bd41" rel="stylesheet" />
|
||||
<link href="css/ui/generic.css?v=30837f8" rel="stylesheet" />
|
||||
|
||||
<link href="css/ui/history.css?v=0b03861" rel="stylesheet" />
|
||||
<link href="css/ui/layers.css?v=ae472cd" rel="stylesheet" />
|
||||
<link href="css/ui/layers.css?v=1d66c2b" rel="stylesheet" />
|
||||
<link href="css/ui/toolbar.css?v=109c78f" rel="stylesheet" />
|
||||
|
||||
<!-- Tool Specific CSS -->
|
||||
|
@ -85,6 +85,13 @@
|
|||
<div class="content">
|
||||
<label>Model:</label>
|
||||
<div id="models-ac-select"></div>
|
||||
<button id="refreshModelsBtn" onclick="getModels(true)">
|
||||
<img
|
||||
class="refreshbutton"
|
||||
src="./res/icons/refresh-cw.svg?v=f627140"
|
||||
alt="refresh models"
|
||||
title="refresh models" />
|
||||
</button>
|
||||
<label>Sampler:</label>
|
||||
<div id="sampler-ac-select"></div>
|
||||
<label for="seed">Seed (-1 for random):</label>
|
||||
|
@ -333,37 +340,38 @@
|
|||
<div class="ui separator"></div>
|
||||
<iframe
|
||||
id="page-overlay"
|
||||
src="pages/configuration.html?v=973baf2"></iframe>
|
||||
src="pages/configuration.html?v=fdbd833"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Basics -->
|
||||
<script src="js/global.js?v=3a1cde6" type="text/javascript"></script>
|
||||
<script src="js/global.js?v=ac30d16" type="text/javascript"></script>
|
||||
<script src="js/defaults.js?v=5b06818" type="text/javascript"></script>
|
||||
|
||||
<!-- Base Libs -->
|
||||
<script src="js/lib/util.js?v=49a78a6" type="text/javascript"></script>
|
||||
<script src="js/lib/util.js?v=e82dd04" type="text/javascript"></script>
|
||||
<script src="js/lib/events.js?v=2ab7933" type="text/javascript"></script>
|
||||
<script
|
||||
src="js/lib/workspaces.js?v=4fbd55b"
|
||||
type="text/javascript"></script>
|
||||
<script src="js/lib/input.js?v=09298ac" type="text/javascript"></script>
|
||||
<script src="js/lib/input.js?v=aa14afc" type="text/javascript"></script>
|
||||
<script src="js/lib/layers.js?v=a1f8aea" type="text/javascript"></script>
|
||||
<script src="js/lib/commands.js?v=bf23c83" type="text/javascript"></script>
|
||||
|
||||
<script src="js/lib/toolbar.js?v=306d637" type="text/javascript"></script>
|
||||
<script src="js/lib/ui.js?v=76ede2b" type="text/javascript"></script>
|
||||
<script src="js/lib/ui.js?v=fe9b702" type="text/javascript"></script>
|
||||
|
||||
<script
|
||||
src="js/initalize/layers.populate.js?v=39785ac"
|
||||
src="js/initalize/layers.populate.js?v=8bc8815"
|
||||
type="text/javascript"></script>
|
||||
|
||||
<!-- Configuration -->
|
||||
<script src="js/config.js?v=e0345e0" type="text/javascript"></script>
|
||||
<script src="js/theme.js?v=435cc1b" type="text/javascript"></script>
|
||||
|
||||
<!-- Content -->
|
||||
<script src="js/prompt.js?v=7a1c68c" type="text/javascript"></script>
|
||||
<script src="js/index.js?v=9d20cb0" type="text/javascript"></script>
|
||||
<script src="js/index.js?v=ce9d981" type="text/javascript"></script>
|
||||
|
||||
<script
|
||||
src="js/ui/floating/history.js?v=fc92d14"
|
||||
|
@ -385,7 +393,7 @@
|
|||
src="js/ui/tool/colorbrush.js?v=3f8c01a"
|
||||
type="text/javascript"></script>
|
||||
<script
|
||||
src="js/ui/tool/select.js?v=e27bbdf"
|
||||
src="js/ui/tool/select.js?v=f290e83"
|
||||
type="text/javascript"></script>
|
||||
<script src="js/ui/tool/stamp.js?v=4a86ff8" type="text/javascript"></script>
|
||||
<script
|
||||
|
@ -407,6 +415,6 @@
|
|||
type="text/javascript"></script>
|
||||
|
||||
<!-- Deals with webui communication -->
|
||||
<script src="js/webui.js?v=60a0a81" type="text/javascript"></script>
|
||||
<script src="js/webui.js?v=8edac89" type="text/javascript"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -53,6 +53,9 @@ const global = {
|
|||
|
||||
// HRFix compatibility shenanigans
|
||||
isOldHRFix: false,
|
||||
|
||||
// WebUI object to communitate with parent window
|
||||
webui: null,
|
||||
};
|
||||
|
||||
global._firstRun = !localStorage.getItem("openoutpaint/host");
|
||||
|
|
84
js/index.js
84
js/index.js
|
@ -602,9 +602,12 @@ const makeSlider = (
|
|||
});
|
||||
};
|
||||
|
||||
const modelAutoComplete = createAutoComplete(
|
||||
let modelAutoComplete = createAutoComplete(
|
||||
"Model",
|
||||
document.getElementById("models-ac-select")
|
||||
document.getElementById("models-ac-select"),
|
||||
{},
|
||||
document.getElementById("refreshModelsBtn"),
|
||||
"refreshable"
|
||||
);
|
||||
modelAutoComplete.onchange.on(({value}) => {
|
||||
if (value.toLowerCase().includes("inpainting"))
|
||||
|
@ -832,6 +835,32 @@ function isCanvasBlank(x, y, w, h, canvas) {
|
|||
}
|
||||
|
||||
function drawBackground() {
|
||||
{
|
||||
// Existing Canvas BG
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = config.gridSize * 2;
|
||||
canvas.height = config.gridSize * 2;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.fillStyle = theme.grid.dark;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = theme.grid.light;
|
||||
ctx.fillRect(0, 0, config.gridSize, config.gridSize);
|
||||
ctx.fillRect(
|
||||
config.gridSize,
|
||||
config.gridSize,
|
||||
config.gridSize,
|
||||
config.gridSize
|
||||
);
|
||||
|
||||
canvas.toBlob((blob) => {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
console.debug(url);
|
||||
bgLayer.canvas.style.backgroundImage = `url(${url})`;
|
||||
});
|
||||
}
|
||||
return;
|
||||
|
||||
// Checkerboard
|
||||
let darkTileColor = "#333";
|
||||
let lightTileColor = "#555";
|
||||
|
@ -969,7 +998,7 @@ async function getUpscalers() {
|
|||
*/
|
||||
}
|
||||
|
||||
async function getModels() {
|
||||
async function getModels(refresh = false) {
|
||||
const url = document.getElementById("host").value + "/sdapi/v1/sd-models";
|
||||
let opt = null;
|
||||
|
||||
|
@ -996,7 +1025,7 @@ async function getModels() {
|
|||
|
||||
const model = optData.sd_model_checkpoint;
|
||||
console.log("Current model: " + model);
|
||||
modelAutoComplete.value = model;
|
||||
if (modelAutoComplete.value !== model) modelAutoComplete.value = model;
|
||||
} catch (e) {
|
||||
console.warn("[index] Failed to fetch current model:");
|
||||
console.warn(e);
|
||||
|
@ -1006,31 +1035,32 @@ async function getModels() {
|
|||
console.warn(e);
|
||||
}
|
||||
|
||||
modelAutoComplete.onchange.on(async ({value}) => {
|
||||
console.log(`[index] Changing model to [${value}]`);
|
||||
const payload = {
|
||||
sd_model_checkpoint: value,
|
||||
};
|
||||
const url = document.getElementById("host").value + "/sdapi/v1/options/";
|
||||
try {
|
||||
await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!refresh)
|
||||
modelAutoComplete.onchange.on(async ({value}) => {
|
||||
console.log(`[index] Changing model to [${value}]`);
|
||||
const payload = {
|
||||
sd_model_checkpoint: value,
|
||||
};
|
||||
const url = document.getElementById("host").value + "/sdapi/v1/options/";
|
||||
try {
|
||||
await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
alert(`Model changed to [${value}]`);
|
||||
} catch (e) {
|
||||
console.warn("[index] Error changing model");
|
||||
console.warn(e);
|
||||
alert(`Model changed to [${value}]`);
|
||||
} catch (e) {
|
||||
console.warn("[index] Error changing model");
|
||||
console.warn(e);
|
||||
|
||||
alert(
|
||||
"Error changing model, please check console for additional information"
|
||||
);
|
||||
}
|
||||
});
|
||||
alert(
|
||||
"Error changing model, please check console for additional information"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// If first time running, ask if user wants to switch to an inpainting model
|
||||
if (global.firstRun && !modelAutoComplete.value.includes("inpainting")) {
|
||||
|
|
|
@ -72,7 +72,8 @@ const uiCtx = uiCanvas.getContext("2d", {desynchronized: true});
|
|||
let expandSize = localStorage.getItem("openoutpaint/expand-size") || 1024;
|
||||
expandSize = parseInt(expandSize, 10);
|
||||
|
||||
const askSize = () => {
|
||||
const askSize = (e) => {
|
||||
if (e.ctrlKey) return expandSize;
|
||||
const by = prompt("How much do you want to expand by?", expandSize);
|
||||
|
||||
if (!by) return null;
|
||||
|
@ -88,11 +89,15 @@ const uiCtx = uiCanvas.getContext("2d", {desynchronized: true});
|
|||
leftButton.classList.add("expand-button", "left");
|
||||
leftButton.style.width = "64px";
|
||||
leftButton.style.height = `${imageCollection.size.h}px`;
|
||||
leftButton.addEventListener("click", () => {
|
||||
leftButton.addEventListener("click", (e) => {
|
||||
let size = null;
|
||||
if ((size = askSize())) {
|
||||
if ((size = askSize(e))) {
|
||||
imageCollection.expand(size, 0, 0, 0);
|
||||
drawBackground();
|
||||
bgLayer.canvas.style.backgroundPosition = `${-snap(
|
||||
imageCollection.origin.x,
|
||||
0,
|
||||
config.gridSize * 2
|
||||
)}px ${-snap(imageCollection.origin.y, 0, config.gridSize * 2)}px`;
|
||||
const newLeft = -imageCollection.inputOffset.x - imageCollection.origin.x;
|
||||
leftButton.style.left = newLeft - 64 + "px";
|
||||
topButton.style.left = newLeft + "px";
|
||||
|
@ -106,11 +111,10 @@ const uiCtx = uiCanvas.getContext("2d", {desynchronized: true});
|
|||
rightButton.classList.add("expand-button", "right");
|
||||
rightButton.style.width = "64px";
|
||||
rightButton.style.height = `${imageCollection.size.h}px`;
|
||||
rightButton.addEventListener("click", () => {
|
||||
rightButton.addEventListener("click", (e) => {
|
||||
let size = null;
|
||||
if ((size = askSize())) {
|
||||
if ((size = askSize(e))) {
|
||||
imageCollection.expand(0, 0, size, 0);
|
||||
drawBackground();
|
||||
rightButton.style.left =
|
||||
parseInt(rightButton.style.left, 10) + size + "px";
|
||||
topButton.style.width = imageCollection.size.w + "px";
|
||||
|
@ -122,11 +126,15 @@ const uiCtx = uiCanvas.getContext("2d", {desynchronized: true});
|
|||
topButton.classList.add("expand-button", "top");
|
||||
topButton.style.height = "64px";
|
||||
topButton.style.width = `${imageCollection.size.w}px`;
|
||||
topButton.addEventListener("click", () => {
|
||||
topButton.addEventListener("click", (e) => {
|
||||
let size = null;
|
||||
if ((size = askSize())) {
|
||||
if ((size = askSize(e))) {
|
||||
imageCollection.expand(0, size, 0, 0);
|
||||
drawBackground();
|
||||
bgLayer.canvas.style.backgroundPosition = `${-snap(
|
||||
imageCollection.origin.x,
|
||||
0,
|
||||
config.gridSize * 2
|
||||
)}px ${-snap(imageCollection.origin.y, 0, config.gridSize * 2)}px`;
|
||||
const newTop = -imageCollection.inputOffset.y - imageCollection.origin.y;
|
||||
topButton.style.top = newTop - 64 + "px";
|
||||
leftButton.style.top = newTop + "px";
|
||||
|
@ -140,11 +148,10 @@ const uiCtx = uiCanvas.getContext("2d", {desynchronized: true});
|
|||
bottomButton.classList.add("expand-button", "bottom");
|
||||
bottomButton.style.height = "64px";
|
||||
bottomButton.style.width = `${imageCollection.size.w}px`;
|
||||
bottomButton.addEventListener("click", () => {
|
||||
bottomButton.addEventListener("click", (e) => {
|
||||
let size = null;
|
||||
if ((size = askSize())) {
|
||||
if ((size = askSize(e))) {
|
||||
imageCollection.expand(0, 0, 0, size);
|
||||
drawBackground();
|
||||
bottomButton.style.top =
|
||||
parseInt(bottomButton.style.top, 10) + size + "px";
|
||||
leftButton.style.height = imageCollection.size.h + "px";
|
||||
|
|
|
@ -615,6 +615,7 @@ window.onkeydown = (evn) => {
|
|||
!!callback.alt === evn.altKey &&
|
||||
!!callback.shift === evn.shiftKey
|
||||
) {
|
||||
evn.preventDefault();
|
||||
// onshortcut event
|
||||
keyboard.listen.onshortcut.emit({
|
||||
target: evn.target,
|
||||
|
|
22
js/lib/ui.js
22
js/lib/ui.js
|
@ -207,9 +207,17 @@ function createSlider(name, wrapper, options = {}) {
|
|||
* @param {object} options Extra options
|
||||
* @param {boolean} options.multiple Whether multiple options can be selected
|
||||
* @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, wrapper, options = {}) {
|
||||
function createAutoComplete(
|
||||
name,
|
||||
wrapper,
|
||||
options = {},
|
||||
extraEl = null,
|
||||
extraClass = null
|
||||
) {
|
||||
defaultOpt(options, {
|
||||
multiple: false,
|
||||
options: [],
|
||||
|
@ -220,6 +228,9 @@ function createAutoComplete(name, wrapper, options = {}) {
|
|||
const inputEl = document.createElement("input");
|
||||
inputEl.type = "text";
|
||||
inputEl.classList.add("autocomplete-text");
|
||||
if (extraClass != null) {
|
||||
inputEl.classList.add(extraClass);
|
||||
}
|
||||
|
||||
const autocompleteEl = document.createElement("div");
|
||||
autocompleteEl.classList.add("autocomplete-list", "display-none");
|
||||
|
@ -230,6 +241,9 @@ function createAutoComplete(name, wrapper, options = {}) {
|
|||
|
||||
wrapper.appendChild(inputEl);
|
||||
wrapper.appendChild(autocompleteEl);
|
||||
if (extraEl != null) {
|
||||
wrapper.appendChild(extraEl);
|
||||
}
|
||||
|
||||
const acobj = {
|
||||
name,
|
||||
|
@ -304,7 +318,7 @@ function createAutoComplete(name, wrapper, options = {}) {
|
|||
autocompleteEl.appendChild(optionEl);
|
||||
});
|
||||
|
||||
updateOptions();
|
||||
updateOptions("");
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -317,8 +331,8 @@ function createAutoComplete(name, wrapper, options = {}) {
|
|||
.join(", ");
|
||||
}
|
||||
|
||||
function updateOptions() {
|
||||
const text = inputEl.value.toLowerCase().trim();
|
||||
function updateOptions(value = null) {
|
||||
const text = value ?? inputEl.value.toLowerCase().trim();
|
||||
|
||||
acobj._options.forEach((opt) => {
|
||||
const textLocation = opt.name.toLowerCase().indexOf(text);
|
||||
|
|
|
@ -302,7 +302,7 @@ function makeWriteOnce(obj, name = "write-once object", exceptions = []) {
|
|||
* @param {number} [gridSize=64] Size of the grid
|
||||
* @returns an offset, in which [i + offset = (a location snapped to the grid)]
|
||||
*/
|
||||
function snap(i, offset = 0, gridSize = 64) {
|
||||
function snap(i, offset = 0, gridSize = config.gridSize) {
|
||||
let diff = i - offset;
|
||||
if (diff < 0) {
|
||||
diff += gridSize * Math.ceil(Math.abs(diff / gridSize));
|
||||
|
|
6
js/theme.js
Normal file
6
js/theme.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
const theme = {
|
||||
grid: {
|
||||
dark: "#333",
|
||||
light: "#555",
|
||||
},
|
||||
};
|
|
@ -25,6 +25,11 @@ const selectTransformTool = () =>
|
|||
uil.onactive.on(state.uilayeractivecb);
|
||||
|
||||
// Registers keyboard shortcuts
|
||||
keyboard.onShortcut({ctrl: true, key: "KeyA"}, state.ctrlacb);
|
||||
keyboard.onShortcut(
|
||||
{ctrl: true, shift: true, key: "KeyA"},
|
||||
state.ctrlsacb
|
||||
);
|
||||
keyboard.onShortcut({ctrl: true, key: "KeyC"}, state.ctrlccb);
|
||||
keyboard.onShortcut({ctrl: true, key: "KeyV"}, state.ctrlvcb);
|
||||
keyboard.onShortcut({ctrl: true, key: "KeyX"}, state.ctrlxcb);
|
||||
|
@ -49,6 +54,8 @@ const selectTransformTool = () =>
|
|||
|
||||
keyboard.listen.onkeyclick.clear(state.keyclickcb);
|
||||
keyboard.listen.onkeydown.clear(state.keydowncb);
|
||||
keyboard.deleteShortcut(state.ctrlacb, "KeyA");
|
||||
keyboard.deleteShortcut(state.ctrlsacb, "KeyA");
|
||||
keyboard.deleteShortcut(state.ctrlccb, "KeyC");
|
||||
keyboard.deleteShortcut(state.ctrlvcb, "KeyV");
|
||||
keyboard.deleteShortcut(state.ctrlxcb, "KeyX");
|
||||
|
@ -387,6 +394,29 @@ const selectTransformTool = () =>
|
|||
};
|
||||
|
||||
// Handles left mouse drag end events
|
||||
|
||||
/** @type {(bb: BoundingBox) => void} */
|
||||
const select = (bb) => {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = bb.w;
|
||||
canvas.height = bb.h;
|
||||
canvas
|
||||
.getContext("2d")
|
||||
.drawImage(uil.canvas, bb.x, bb.y, bb.w, bb.h, 0, 0, bb.w, bb.h);
|
||||
|
||||
uil.ctx.clearRect(bb.x, bb.y, bb.w, bb.h);
|
||||
|
||||
state.original = {
|
||||
...bb,
|
||||
sx: bb.center.x,
|
||||
sy: bb.center.y,
|
||||
layer: uil.layer,
|
||||
};
|
||||
state.selected = new _tool.MarqueeSelection(canvas, bb.center);
|
||||
|
||||
state.redraw();
|
||||
};
|
||||
|
||||
state.dragendcb = (evn) => {
|
||||
const {x, y, sx, sy} = _tool._process_cursor(evn, state.snapToGrid);
|
||||
|
||||
|
@ -397,34 +427,7 @@ const selectTransformTool = () =>
|
|||
|
||||
state.reset();
|
||||
|
||||
if (selection.exists && bb.w !== 0 && bb.h !== 0) {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = bb.w;
|
||||
canvas.height = bb.h;
|
||||
canvas
|
||||
.getContext("2d")
|
||||
.drawImage(
|
||||
uil.canvas,
|
||||
bb.x,
|
||||
bb.y,
|
||||
bb.w,
|
||||
bb.h,
|
||||
0,
|
||||
0,
|
||||
bb.w,
|
||||
bb.h
|
||||
);
|
||||
|
||||
uil.ctx.clearRect(bb.x, bb.y, bb.w, bb.h);
|
||||
|
||||
state.original = {
|
||||
...bb,
|
||||
sx: selection.bb.center.x,
|
||||
sy: selection.bb.center.y,
|
||||
layer: uil.layer,
|
||||
};
|
||||
state.selected = new _tool.MarqueeSelection(canvas, bb.center);
|
||||
}
|
||||
if (selection.exists && bb.w !== 0 && bb.h !== 0) select(bb);
|
||||
|
||||
selection.deselect();
|
||||
}
|
||||
|
@ -457,30 +460,66 @@ const selectTransformTool = () =>
|
|||
}
|
||||
};
|
||||
|
||||
// Register Ctrl-A Shortcut
|
||||
state.ctrlacb = () => {
|
||||
try {
|
||||
const {bb} = cropCanvas(uil.canvas);
|
||||
select(bb);
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
};
|
||||
|
||||
state.ctrlsacb = () => {
|
||||
// Shift Key selects based on all visible layer information
|
||||
const tl = {x: Infinity, y: Infinity};
|
||||
const br = {x: -Infinity, y: -Infinity};
|
||||
|
||||
uil.layers.forEach(({layer}) => {
|
||||
try {
|
||||
const {bb} = cropCanvas(layer.canvas);
|
||||
|
||||
tl.x = Math.min(bb.tl.x, tl.x);
|
||||
tl.y = Math.min(bb.tl.y, tl.y);
|
||||
|
||||
br.x = Math.max(bb.br.x, br.x);
|
||||
br.y = Math.max(bb.br.y, br.y);
|
||||
} catch (e) {
|
||||
// Ignore errors
|
||||
}
|
||||
});
|
||||
|
||||
if (Number.isFinite(br.x - tl.y)) {
|
||||
select(BoundingBox.fromStartEnd(tl, br));
|
||||
}
|
||||
};
|
||||
|
||||
// Register Ctrl-C/V Shortcut
|
||||
|
||||
// Handles copying
|
||||
state.ctrlccb = (evn, cut = false) => {
|
||||
if (!state.selected) return;
|
||||
|
||||
if (
|
||||
isCanvasBlank(
|
||||
0,
|
||||
0,
|
||||
state.selected.canvas.width,
|
||||
state.selected.canvas.height,
|
||||
state.selected.canvas
|
||||
)
|
||||
)
|
||||
return;
|
||||
// We create a new canvas to store the data
|
||||
state.clipboard.copy = document.createElement("canvas");
|
||||
|
||||
state.clipboard.copy.width = state.selected.w;
|
||||
state.clipboard.copy.height = state.selected.h;
|
||||
state.clipboard.copy.width = state.selected.canvas.width;
|
||||
state.clipboard.copy.height = state.selected.canvas.height;
|
||||
|
||||
const ctx = state.clipboard.copy.getContext("2d");
|
||||
|
||||
ctx.clearRect(0, 0, state.selected.w, state.selected.h);
|
||||
ctx.drawImage(
|
||||
state.selected.canvas,
|
||||
0,
|
||||
0,
|
||||
state.selected.canvas.width,
|
||||
state.selected.canvas.height,
|
||||
0,
|
||||
0,
|
||||
state.selected.w,
|
||||
state.selected.h
|
||||
);
|
||||
ctx.drawImage(state.selected.canvas, 0, 0);
|
||||
|
||||
// If cutting, we reverse the selection and erase the selection area
|
||||
if (cut) {
|
||||
|
@ -505,7 +544,7 @@ const selectTransformTool = () =>
|
|||
};
|
||||
|
||||
// Handles pasting
|
||||
state.ctrlvcb = (evn) => {
|
||||
state.ctrlvcb = async (evn) => {
|
||||
if (state.useClipboard) {
|
||||
// If we use the clipboard, do some proccessing of clipboard data (ugly but kind of minimum required)
|
||||
navigator.clipboard &&
|
||||
|
@ -532,6 +571,7 @@ const selectTransformTool = () =>
|
|||
// Use internal clipboard
|
||||
const image = document.createElement("img");
|
||||
image.src = state.clipboard.copy.toDataURL();
|
||||
await image.decode();
|
||||
|
||||
// Send to stamp, as clipboard temporary data
|
||||
tools.stamp.enable({
|
||||
|
@ -674,7 +714,7 @@ const selectTransformTool = () =>
|
|||
createVisibleResourceButton.disabled = true;
|
||||
};
|
||||
|
||||
// Disable buttons (if something is selected)
|
||||
// Enable buttons (if something is selected)
|
||||
state.ctxmenu.enableButtons = () => {
|
||||
saveSelectionButton.disabled = "";
|
||||
createResourceButton.disabled = "";
|
||||
|
@ -683,6 +723,21 @@ const selectTransformTool = () =>
|
|||
};
|
||||
state.ctxmenu.actionArray = actionArray;
|
||||
state.ctxmenu.visibleActionArray = visibleActionArray;
|
||||
|
||||
// Send Selection to Destination
|
||||
state.ctxmenu.sendSelected = document.createElement("select");
|
||||
state.ctxmenu.sendSelected.style.width = "100%";
|
||||
state.ctxmenu.sendSelected.addEventListener("change", (evn) => {
|
||||
const v = evn.target.value;
|
||||
if (state.selected && v !== "None")
|
||||
global.webui && global.webui.sendTo(state.selected.canvas, v);
|
||||
evn.target.value = "None";
|
||||
});
|
||||
|
||||
let opt = document.createElement("option");
|
||||
opt.textContent = "Send To...";
|
||||
opt.value = "None";
|
||||
state.ctxmenu.sendSelected.appendChild(opt);
|
||||
}
|
||||
const array = document.createElement("div");
|
||||
array.classList.add("checkbox-array");
|
||||
|
@ -693,6 +748,23 @@ const selectTransformTool = () =>
|
|||
menu.appendChild(state.ctxmenu.selectionPeekOpacitySlider);
|
||||
menu.appendChild(state.ctxmenu.actionArray);
|
||||
menu.appendChild(state.ctxmenu.visibleActionArray);
|
||||
if (global.webui && global.webui.destinations) {
|
||||
while (state.ctxmenu.sendSelected.lastChild.value !== "None") {
|
||||
state.ctxmenu.sendSelected.removeChild(
|
||||
state.ctxmenu.sendSelected.lastChild
|
||||
);
|
||||
}
|
||||
|
||||
global.webui.destinations.forEach((dst) => {
|
||||
const opt = document.createElement("option");
|
||||
opt.textContent = dst.name;
|
||||
opt.value = dst.id;
|
||||
|
||||
state.ctxmenu.sendSelected.appendChild(opt);
|
||||
});
|
||||
|
||||
menu.appendChild(state.ctxmenu.sendSelected);
|
||||
}
|
||||
},
|
||||
shortcut: "S",
|
||||
}
|
||||
|
|
39
js/webui.js
39
js/webui.js
|
@ -2,6 +2,32 @@
|
|||
* This file should only be actually loaded if we are in a trusted environment.
|
||||
*/
|
||||
(async () => {
|
||||
let parentWindow = null;
|
||||
const webui = {
|
||||
/** @type {{name: string, id: string}[]} */
|
||||
destinations: null,
|
||||
|
||||
/**
|
||||
* Sends a
|
||||
*
|
||||
* @param {HTMLCanvas} canvas Canvas to send the data of
|
||||
* @param {string} destination The ID of the destination
|
||||
*/
|
||||
sendTo(canvas, destination) {
|
||||
if (!this.destinations.find((d) => d.id === destination))
|
||||
throw new Error("[webui] Given destination is not available");
|
||||
|
||||
parentWindow &&
|
||||
parentWindow.postMessage({
|
||||
type: "openoutpaint/sendto",
|
||||
message: {
|
||||
image: canvas.toDataURL(),
|
||||
destination,
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// Check if key file exists
|
||||
const response = await fetch("key.json");
|
||||
|
||||
|
@ -48,8 +74,6 @@
|
|||
}
|
||||
|
||||
if (data) {
|
||||
let parentWindow = null;
|
||||
|
||||
if (!data.trusted) console.debug(`[webui] Loaded key`);
|
||||
|
||||
window.addEventListener("message", ({data, origin, source}) => {
|
||||
|
@ -65,6 +89,11 @@
|
|||
console.warn(`[webui] Communication has not been initialized`);
|
||||
}
|
||||
|
||||
if (global.debug) {
|
||||
console.debug("[webui] Received message:");
|
||||
console.debug(data);
|
||||
}
|
||||
|
||||
try {
|
||||
switch (data.type) {
|
||||
case "openoutpaint/init":
|
||||
|
@ -77,6 +106,7 @@
|
|||
data.host,
|
||||
`Are you sure you want to modify the host?\nThis configuration was provided by the hosting page\n - ${parentWindow.document.title} (${origin})`
|
||||
);
|
||||
if (data.destinations) webui.destinations = data.destinations;
|
||||
|
||||
break;
|
||||
case "openoutpaint/add-resource":
|
||||
|
@ -142,4 +172,7 @@
|
|||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
return webui;
|
||||
})().then((value) => {
|
||||
global.webui = value;
|
||||
});
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
<link href="../css/colors.css?v=3f81e80" rel="stylesheet" />
|
||||
<link href="../css/icons.css?v=9ae0466" rel="stylesheet" />
|
||||
|
||||
<link href="../css/index.css?v=5b8d4d6" rel="stylesheet" />
|
||||
<link href="../css/index.css?v=882f400" rel="stylesheet" />
|
||||
<link href="../css/layers.css?v=92c0352" rel="stylesheet" />
|
||||
|
||||
<link href="../css/ui/generic.css?v=802bd41" rel="stylesheet" />
|
||||
<link href="../css/ui/generic.css?v=30837f8" rel="stylesheet" />
|
||||
|
||||
<link href="../css/ui/history.css?v=0b03861" rel="stylesheet" />
|
||||
<link href="../css/ui/layers.css?v=ae472cd" rel="stylesheet" />
|
||||
<link href="../css/ui/layers.css?v=1d66c2b" rel="stylesheet" />
|
||||
<link href="../css/ui/toolbar.css?v=109c78f" rel="stylesheet" />
|
||||
|
||||
<!-- Tool Specific CSS -->
|
||||
|
|
|
@ -8,8 +8,7 @@
|
|||
<iframe
|
||||
id="openoutpaint"
|
||||
style="width: 100%; height: 800px"
|
||||
src="../index.html?v=daf18de"
|
||||
src="../index.html?v=daf18de"
|
||||
src="../index.html?v=34d5675"
|
||||
frameborder="0"></iframe>
|
||||
<button id="add-res">Add Resource</button>
|
||||
<script>
|
||||
|
|
7
res/icons/refresh-cw.svg
Normal file
7
res/icons/refresh-cw.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 2v6h-6"></path>
|
||||
<path d="M3 12a9 9 0 0 1 15-6.7L21 8"></path>
|
||||
<path d="M3 22v-6h6"></path>
|
||||
<path d="M21 12a9 9 0 0 1-15 6.7L3 16"></path>
|
||||
|
||||
</svg>
|
After Width: | Height: | Size: 348 B |
Loading…
Reference in a new issue