From 33686ec5c2bef70a74467afea47d7841f40b1c48 Mon Sep 17 00:00:00 2001 From: disty Date: Mon, 2 Dec 2024 09:19:26 +0200 Subject: [PATCH] add inpaint with live preview --- README.md | 5 +- __init__.py | 5 +- pyproject.toml | 2 +- web/core/content.html | 65 +- web/core/css/main.css | 2209 +++++++++-------- .../js/common/components/CanvasComponent.js | 220 +- .../js/common/components/DataComponent.js | 48 + web/core/js/common/components/DimSelector.js | 2 +- web/core/js/common/components/Seeder.js | 8 +- .../components/canvas/CanvasControlsPlugin.js | 132 +- .../common/components/canvas/CanvasLoader.js | 367 ++- .../components/canvas/CustomBrushPlugin.js | 60 +- .../components/canvas/ImageLoaderPlugin.js | 40 +- .../components/canvas/MaskBrushPlugin.js | 964 +++---- .../components/canvas/MaskExportUtilities.js | 1006 ++++++++ .../js/common/components/imageLoaderComp.js | 7 + .../js/common/components/messageHandler.js | 369 ++- web/core/js/common/scripts/componentTypes.js | 22 + web/core/js/common/scripts/interactiveUI.js | 497 ++++ .../js/common/scripts/stateManagerMain.js | 119 + web/core/main.js | 67 +- web/flow/js/main.js | 9 +- 22 files changed, 4324 insertions(+), 1899 deletions(-) create mode 100644 web/core/js/common/components/DataComponent.js create mode 100644 web/core/js/common/components/canvas/MaskExportUtilities.js create mode 100644 web/core/js/common/scripts/interactiveUI.js create mode 100644 web/core/js/common/scripts/stateManagerMain.js diff --git a/README.md b/README.md index 0685877..978a72a 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,10 @@ http://127.0.0.1:8188/flow ### Feature Support - [x] Canvas / Masking / Inpainting Functionality -- - [ ] Improved Canvas / Masking / Inpainting Functionality +- - [x] Improved Canvas / Masking / Inpainting Functionality +- - [ ] Outpainting Functionality +- - Improving Inpainting Functionality + - [ ] Enhanced Media Handling - [x] Live Preview - [ ] Prompt Tracking diff --git a/__init__.py b/__init__.py index cbb966c..2aaa542 100644 --- a/__init__.py +++ b/__init__.py @@ -38,7 +38,7 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = {} APP_CONFIGS: List[AppConfig] = [] APP_NAME: str = "Flow" -APP_VERSION: str = "0.3.3" +APP_VERSION: str = "0.4.0" PURPLE = "\033[38;5;129m" RESET = "\033[0m" FLOWMSG = f"{PURPLE}Flow{RESET}" @@ -743,7 +743,8 @@ def download_or_update_flows() -> None: "afl_mochi2v", "afl_pulid_flux", "afl_pulid_flux_GGUF", - "afl_reactor", + "afl_reactor" + "5otvy-cogvideox-orbit-left-lora", ] try: diff --git a/pyproject.toml b/pyproject.toml index 2a29ecb..06acdbe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "comfyui-disty-flow" description = "Flow is a custom node designed to provide a more user-friendly interface for ComfyUI by acting as an alternative user interface for running workflows. It is not a replacement for workflow creation.\nFlow is currently in the early stages of development, so expect bugs and ongoing feature enhancements. With your support and feedback, Flow will settle into a steady stream." -version = "0.3.3" +version = "0.4.0" license = {file = "LICENSE"} [project.urls] diff --git a/web/core/content.html b/web/core/content.html index 2b0c433..f294a37 100644 --- a/web/core/content.html +++ b/web/core/content.html @@ -1,28 +1,51 @@ -
-
-
-
-
-
+ +
+
+
+ + +
-
- -
-
-
-
-
-
-

Recent

-
-
-
-
+
+
+
+
+
+
+
+ + + + +
-
- + + diff --git a/web/core/css/main.css b/web/core/css/main.css index bfbb339..d708353 100644 --- a/web/core/css/main.css +++ b/web/core/css/main.css @@ -1,8 +1,9 @@ +/* Reset and Base Styles */ * { box-sizing: border-box; margin: 0; padding: 0; - font-family: 'Roboto', sans-serif, 'Arial Narrow', Arial; + font-family: 'Roboto', sans-serif, 'Arial Narrow', Arial, sans-serif; } :root { @@ -40,6 +41,7 @@ --color-input-range-background: #570d7b; --color-spinner:#570d7b; --color-spinner-highlight:#7d0ab6; + --color-spinner-highlight: #7d0ab6; } html, body { @@ -57,46 +59,77 @@ body { min-height: 100vh; } +.container { + display: flex; + flex-direction: column; + flex: 1; + color: var(--color-primary-text); + overflow: hidden; +} + header { display: flex; justify-content: space-between; align-items: center; background-color: var(--color-header-background); - padding-left: 50px; - padding-right: 50px; - padding-top: 10px; + padding: 4px 20px; text-align: left; border-bottom: 1px dashed var(--color-border); + position: relative; /* For positioning child elements if needed */ } -#right-header { +#logo { display: flex; justify-content: center; align-items: center; - margin-right: 10px; - padding: 5px; } -#Support { - color: var(--color-header-text); - margin: 10px; - font-size: 12px; +#img-logo { + padding-right: 40px; } -a { - text-decoration: none; +#img-logo img { + max-width: 100%; + height: auto; } -.appName { - padding: 10px; +.logo-text { + display: inline-block; + position: relative; + font-family: Arial, sans-serif; + cursor: pointer; color: var(--color-header-text); - border-top: 1px dashed var(--color-border); - border-left: 1px dashed var(--color-border); - border-right: 1px dashed var(--color-border); - font-size: 12px; + user-select: none; +} + +.logo-text .text { + overflow: hidden; + font-size: 30px; + text-align: center; + display: inline-block; + animation-name: animation-text; + animation-duration: 2s; +} + +.logo-text .left, +.logo-text .right { + position: absolute; + font-size: 30px; + line-height: 30px; + animation-duration: 1.5s; + font-weight: bold; +} + +.logo-text .left { + left: -30px; + animation-name: animation-left; +} + +.logo-text .right { + right: -30px; + animation-name: animation-right; } -/* Animations */ @keyframes animation-text { 0% { opacity: 0; } 40% { opacity: 0; } @@ -113,61 +146,53 @@ a { to { right: -30px; } } -header #logo { +.appName { + padding: 10px; + color: var(--color-header-text); + border: 1px dashed var(--color-border); + border-top: none; + border-bottom: none; + font-size: 12px; +} + +.appName h2 { + font-size: 16px; + font-weight: normal; +} + +#right-header { display: flex; justify-content: center; align-items: center; + margin-right: 10px; + padding: 5px; } -header #img-logo { - padding-right: 40px; -} - -header .logo-text { - display: inline-block; - position: relative; - font-family: Arial; - cursor: pointer; +#Support { color: var(--color-header-text); - user-select: none; - + margin: 10px; + font-size: 12px; + display: none; } -header .logo-text .text { - overflow: hidden; - font-size: 30px; - text-align: center; - display: inline-block; - animation-name: animation-text; - animation-duration: 2s; +a { + text-decoration: none; } -header .left { - position: absolute; - left: -30px; - font-size: 30px; - line-height: 30px; - animation-name: animation-left; - animation-duration: 1.5s; - font-weight: bold; +#patreon, #github, #x, #discord { + display: flex; + align-items: center; } -header .right { - position: absolute; - right: -30px; - font-size: 30px; - line-height: 30px; - animation-name: animation-right; - animation-duration: 1.5s; - font-weight: bold; +#patreon a, #github a, #x a, #discord a { + display: flex; + align-items: center; + justify-content: center; } -.container { - display: flex; - flex-direction: column; - flex: 1; - color: var(--color-primary-text); - overflow: hidden; +#patreon a svg, #github a svg, #x a svg, #discord a svg { + width: 24px; + height: 24px; } .content { @@ -175,11 +200,7 @@ header .right { flex: 1; text-align: left; overflow: hidden; -} - -.content div { - overflow: auto; - padding: 4px 6px; + position: relative; } .content .mid-col, @@ -187,178 +208,299 @@ header .right { .content .right-col { display: flex; flex-direction: column; - align-items: left; + align-items: flex-start; + transition: all 0.3s ease; } .content .left-col { - flex: 1.1; - background-color: var(--color-background); -} -.content .right-col { - flex: 0.5; + width: 24%; + min-width: 10px; background-color: var(--color-background); + position: relative; + transition: width 0.3s ease; + overflow: hidden; } -.content .mid-col { - flex: 2.5; +/* .content .mid-col { + display: flex; + flex: 1; border-left: 1px dashed var(--color-border); border-right: 1px dashed var(--color-border); - justify-content: space-around; + align-items: center; + justify-content: center; min-height: 100%; padding: 10px; - /* padding: 4px 6px; */ - -} - -.canvas-container { + transition: flex 0.3s ease; + gap: 10px; +} */ +.content .mid-col { display: flex; - justify-content: center; - align-items: center; - /* height: 100% !important; */ - /* width: 100% !important; */ - /* background: var(--color-button-primary-active); */ -} + flex: 1; + flex-direction: column; /* Ensure proper stacking */ + min-width: 0; /* Prevent overflow */ + border-left: 1px dashed var(--color-border); + border-right: 1px dashed var(--color-border); + padding: 10px; + gap: 10px; + transition: flex 0.3s ease; -#canvasContainer:focus { - border: 2px solid #4A90E2; } -#imageCanvas { - /* width: 100%; */ - /* height: 100%; */ - display: block; - /* position: absolute; */ - /* top: 0; */ - /* left: 0; */ - /* background: green; */ - overflow: hidden; +.content .left-col.minimized { + width: 0; padding: 0; - } -/* Green dashed border around the canvas wrapper */ -#canvasWrapper { - /* position: relative; */ - flex: 1; - /* width: 100%; */ - /* height: 100%; */ - overflow: hidden; - /* background-color: var(--color-background-secondary); */ - border: 1px dashed var(--color-border); - box-sizing: border-box; - /* width: 800px; */ - /* height: 600px; */ +.content .right-col { + width: 10%; + min-width: 100px; + background-color: var(--color-background); + padding:0; + padding-left: 10px; position: relative; - margin: 0; /* Remove default margins */ - padding: 10px; + transition: width 0.3s ease; + overflow: hidden; +} + +.content .right-col.minimized { + width: 0; + padding: 4px; } -.brush-ui-container { +/* Sidebar Content Styles */ +.left-panel-content, +.right-panel-content { + flex: 1; display: flex; flex-direction: column; - gap: 10px; + overflow: hidden; padding: 10px; - background-color: var(--color-button-primary-text-hover); - color: var(--color-primary-text); - border: 1px solid var(--color-border); - border-radius: 5px; - margin-bottom: 10px; -} - -#control button { - padding: 5px; - background-color: var(--color-primary); - user-select: none; -} - -#control button:hover { - box-shadow: var(--shadow-hover); - background-color: var(--color-accent); -} - -input[type="text"], -input[type="number"], -textarea { - border:none; - border-bottom: 1px solid var(--color-border); - transition: border-color 0.3s ease-in-out; - padding: 8px; - background-color: var(--color-background-secondary); color: var(--color-primary-text); - margin:0 10px; - -} + width: 100%; -input[type="text"]:hover, -input[type="number"]:hover, -textarea:hover { - border-color: var(--color-border); } - -input[type="text"]:focus, -input[type="number"]:focus, -textarea:focus { - border-color: var(--color-primary-text); - outline: none; +.right-panel-content { + padding: 0px; } -input[type="range"]::-webkit-slider-thumb { - -webkit-appearance: none; - width: 10px; - height: 10px; - border-radius: 100%; - background: var(--color-input-range-thumb); - cursor: pointer; - margin: 2px; +.left-panel-content p, +.right-panel-content p { + margin-bottom: 10px; } -input[type="range"] { - -webkit-appearance: none; +#controls { + display: flex; + flex-direction: column; + justify-content: space-between; + /* background-color: var(--color-scrollbar-track); */ + /* padding: 6px; */ width: 100%; - background-color: var(--color-input-range-background); - padding: 4px; + gap: 4px; } -#title { +#progressbar { + flex: 1; display: flex; - flex-direction: row; + justify-content: center; align-items: center; - margin: 3px 0; background-color: var(--color-background-secondary); + border-top: 1px dashed var(--color-border); + padding: 6px; width: 100%; - text-align: left; } -#title #title-text { - flex: 1; - user-select: none; +#prompts { + display: flex; + flex-direction: column; + /* background-color: var(--color-background-secondary); */ + /* border-top: 1px dashed var(--color-primary); */ + /* padding: 6px; */ + width: 100%; + gap: 4px; } -#title input[type="text"], -#title textarea { - flex: 3; +#prompt{ + align-items: center; + background-color: var(--color-background-secondary); + border-top: 1px dashed var(--color-border); + padding: 6px; + width: 100%; + display: flex; + flex-direction: row; } +#prompts textarea { + border:none; + /* border-bottom: 1px solid var(--color-border); */ + transition: border-color 0.3s ease-in-out; + /* padding: 8px; */ + /* background-color: var(--color-background-secondary); */ + color: var(--color-primary-text); + /* margin:0 10px; */ + /* width: 100%; */ + flex:3; + +} -#buttonsgen { - display: flex; - justify-content: right; +#prompt #title-text { + flex: 1; + user-select: none; } -#buttonsgen button { +#main-progress { + flex: 2; + top: 0; + left: 0; + width: 100%; + z-index: 3; + border-radius: 0; + height: 20px; + background-color: var(--color-progress-background); + border: 0; +} + +#main-progress::-moz-progress-bar { + background: var(--color-highlight); +} + +#main-progress::-webkit-progress-bar { + background: transparent; +} + +#main-progress::-webkit-progress-value { + background: var(--color-progress-value); +} + +#spinner { + height: 20px; + width: 20px; + border-radius: 10%; + border: 4px dashed var(--color-spinner); + margin-right: 10px; +} + +.spin { + animation: spin 1s infinite ease-out; + border-color: var(--color-spinner) var(--color-spinner-highlight) !important; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +#loading-area { + background-color: var(--color-background-secondary); + z-index: 100; display: flex; - justify-content: right; - background-color: var(--color-button-primary); - padding: 10px; - margin: 0px 10px; - color: var(--color-button-primary-text); - transition: background-color 0.3s ease, color 0.3s ease; + justify-content: space-evenly; + align-items: center; + width: 100%; } -#buttonsgen button:hover { - background-color: var(--color-button-primary-hover); - color: var(--color-button-primary-text-hover); +#queue-display{ + overflow-y: auto; + background-color: var(--color-background-secondary); + margin-left: 10px; + user-select: none; + width: 20px; +} + +footer { + background-color: var(--color-background-secondary); + color: var(--color-primary-text); + padding: 15px 50px; + text-align: center; + font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif; + border-top: 1px dashed var(--color-border); + margin-top: auto; +} + +#footer-content p { + font-size: 14px; +} + +input[type="text"], +input[type="number"], +textarea { + border:none; + border-bottom: 1px solid var(--color-border); + transition: border-color 0.3s ease-in-out; + /* padding: 8px; */ + background-color: var(--color-background-secondary); + color: var(--color-primary-text); + /* margin:0 10px; */ + +} + +input[type="text"]:hover, +input[type="number"]:hover, +textarea:hover { + border-color: var(--color-border); +} + +input[type="text"]:focus, +input[type="number"]:focus, +textarea:focus { + border-color: var(--color-primary-text); + outline: none; + +} + +input[type="range"] { + -webkit-appearance: none; + width: 100%; + background-color: var(--color-input-range-background); + padding: 4px; +} + +/* Chrome/Safari/Opera */ +input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + width: 10px; + height: 10px; + border-radius: 100%; + background: var(--color-input-range-thumb); + cursor: pointer; + margin: 2px; +} + +/* Firefox */ +input[type="range"]::-moz-range-thumb { + width: 10px; + height: 10px; + border-radius: 100%; + background: var(--color-input-range-thumb); + cursor: pointer; + margin: 2px; + border: none; /* Remove default border in Firefox */ +} + +/* IE/Edge */ +input[type="range"]::-ms-thumb { + width: 10px; + height: 10px; + border-radius: 100%; + background: var(--color-input-range-thumb); + cursor: pointer; + margin: 2px; + border: none; +} +/* For Chrome, Safari, Edge, Opera */ +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} + +/* For Firefox */ +input[type="number"] { + -moz-appearance: textfield; } .stepper-container { @@ -371,6 +513,7 @@ input[type="range"] { background-color: var(--color-background-secondary); font-size: 15px; font-weight: bold; + padding: 5px; } .stepper-container .range-input { @@ -397,46 +540,77 @@ input[type="range"] { background-color: var(--color-button-secondary); } -.stepper { - display: flex; - /* overflow: hidden; */ - flex: 1 1 auto; - /* background: red; */ - /* width: 10px; */ - -} .stepper__input { border: 1px solid var(--color-background-secondary); background-color: var(--color-background-secondary); color: var(--color-primary-text); - /* flex: 1 1 auto; */ text-align: center; transition: background-color 0.3s ease, color 0.3s ease; - /* margin: 0; */ - /* padding: 0; */ - /* background: red; */ - - } + .stepper__button { background-color: var(--color-background-secondary); color: var(--color-primary-text); - /* padding: 5px 10px; */ cursor: pointer; transition: background-color 0.3s ease, color 0.3s ease; width: 100%; border: none; } + .stepper__button:hover, .swap-btn:hover { - /* background-color: var(--color-accent); */ - /* box-shadow: var(--shadow-hover); */ background-color: var(--color-button-secondary-hover); color: var(--color-button-secondary-text-hover); border: none; - +} + +.seeder-container { + width: 100%; + display: flex; + align-items: center; + flex-direction: row; + justify-content: space-evenly; + margin-bottom: 5px; + background-color: var(--color-background-secondary); + font-size: 15px; + font-weight: bold; + padding: 5px; +} + +.seeder-container > input[type="text"] { + /* flex: 1; */ + margin-right: 20px; + /* margin-left: 10px; */ + /* flex: 1 1 auto; */ + /* width: auto; */ + /* min-width: 50px; */ + /* width: 200px; */ + text-align: center; + /* padding: 0 15px ; */ + /* height: 100%; */ + padding: 6px; } + +.seeder-container > span { + flex: 1; +} + +.seeder-container .seeder-buttons { + display: flex; + align-items: center; + justify-content: space-around; + flex: 0.5; +} + +.seeder-container button { + width: 30px; + height: 30px; + font-weight: bold; + color: var(--color-button-secondary-text); + background-color: var(--color-button-secondary); +} + .dimension-selector-container { display: flex; flex-direction: column; @@ -444,23 +618,38 @@ input[type="range"] { color: var(--color-primary-text); width: 100%; margin-bottom: 4px; - padding: 0px 0; + padding: 5px; } + #dimension-selector { display: flex; width: 100%; + align-items: center; + flex-direction: row; + justify-content: space-evenly; +} + +.stepper { + display: flex; + /* flex: 1 1 auto; */ } + .dimension-stepper { display: flex; flex-direction: column; - width: 100%; - /* flex: 1 1 auto; */ + width: 100%; + /* flex: 1 1 auto; */ + /* background: red; */ + /* border: solid 1px red; */ } + .dimension-stepper label { margin-bottom: 5px; text-align: center; font-size: 0.9em; + font-weight: bold; } + .swap-btn { background-color: var(--color-background-secondary); border: none; @@ -469,68 +658,147 @@ input[type="range"] { cursor: pointer; transition: background-color 0.3s ease, color 0.3s ease; } + +#aspect-ratio-selector { + width: 100%; + padding: 0; +} + .aspect-ratio-selector__select { - border-top: 1px solid var(--color-background-secondary); + /* border-top: 1px solid var(--color-background-secondary); */ background-color: var(--color-background-secondary); color: var(--color-primary-text); - padding: 5px; + /* padding: 5px; */ width: 100%; outline: none; - -} -.seeder-container{ - width: 100%; + border: none; + margin-top: 6px; +} + +.loader { display: flex; - align-items: center; flex-direction: row; + align-items: center; + margin-bottom: 4px; justify-content: space-around; - margin-bottom: 5px; background-color: var(--color-background-secondary); - font-size: 15px; - font-weight: bold; + padding: 2px; + /* box-shadow: 0 4px 8px var(--color-background-secondary); */ } -.seeder-container{ - width: 100%; - display: flex; - align-items: center; - flex-direction: row; - justify-content: space-between; - margin-bottom: 5px; - background-color: var(--color-background-secondary); + +.loader label { + width: 50%; + color: var(--color-primary-text); font-size: 15px; font-weight: bold; } -/* .seeder-container .range-input { - flex: 2; -} */ -.seeder-container > input[type="text"] { - text-align: center; - border: none; +.loader input[type="text"] { + width: 50%; + padding: 8px; + background-color: var(--color-background-secondary); color: var(--color-primary-text); - flex: 1 1 auto; /* grow | shrink | basis */ - width: 100%; - min-width: 50px; /* Minimum width when shrinking */ - max-width: 100%; /* Maximum width when expanding */ + border: none; + border-bottom: 1px solid var(--color-border); } -/* .seeder-container input[type='number'] { - flex: 2; - -} */ -.seeder-container span { - color: var(--color-primary-text); - /* flex: 1; */ +select { + width: 100%; + padding: 8px; + background-color: var(--color-background-secondary); + color: var(--color-primary-text); + border: none; + outline: none; +} +select:focus { + border-color: var(--color-accent); } -.seeder-container button { - width: 30px; - height: 30px; - font-weight: bold; - color: var(--color-button-secondary-text); +select option:hover { + background-color: var(--color-primary-text); + color: var(--color-background-secondary); +} + +.input-component-wrapper { + display: flex; + align-items: center; + justify-items: space-evenly; + background-color: var(--color-background-secondary); + padding: 10px 14px; /* Reduced padding for a narrower component */ + /* border: 1px solid var(--color-border); */ + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + color: var(--color-primary-text); + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + margin: 0; + transition: background-color 0.3s, box-shadow 0.3s, border-color 0.3s; + width: 100%; + /* flex-grow: 1; */ + +} + +.input-component-wrapper:hover { + /* box-shadow: 0 3px 5px rgba(0, 0, 0, 0.15); */ + /* border-color: var(--color-button-primary-hover); */ +} + +.input-container { + /* position: relative; */ + display: flex; + align-items: center; + justify-content: space-around; + flex: 1; + background-color: var(--color-background-secondary); + margin-bottom: 4px; + padding: 10px; +} +.input-container input[type="text"], +.input-container input[type="number"], +.input-container textarea { + padding: 6px; +} + +.input-container .text-input { + flex : 4; + width: 100%; +} + +.input-container label { + font-size: 1rem; + margin-right: 10px; + user-select: none; + flex-shrink: 0; + /* width: 120px; */ + /* background: red; */ + flex: 1; +} +.multi-component-container { + background: var(--color-background-secondary); + margin-bottom: 4px; + padding: 10px; + width: 100%; +} + +.multi-component-container .stepper-container .text-input{ + /* background: red; */ + +} +.add-lora-button { background-color: var(--color-button-secondary); + color: var(--color-button-secondary-text); + padding: 0px; + /* margin: 0 10px; */ + border: none; + cursor: pointer; + transition: background-color 0.3s ease, color 0.3s ease; + /* width:80%; */ + overflow: hidden; +} + +.add-lora-button:hover { + background-color: var(--color-button-secondary-hover); + color: var(--color-button-secondary-text-hover); } button { @@ -553,161 +821,266 @@ button:last-child { button.active { background-color: var(--color-button-secondary-active); color: var(--color-button-secondary-text-active); - /* box-shadow: var(--shadow-hover); */ } button:hover { background-color: var(--color-button-secondary-hover); color: var(--color-button-secondary-text-hover); } -input[type='number']::-webkit-inner-spin-button, -input[type='number']::-webkit-outer-spin-button { - -webkit-appearance: none; - margin: 0; +#buttonsgen { + display: flex; + justify-content: right; } -.add-lora-button { + +#buttonsgen button { + display: flex; + justify-content: right; background-color: var(--color-button-primary); + padding: 10px; + margin: 0px 10px; color: var(--color-button-primary-text); - padding: 6px 0; - margin: 0 ; - font-size: medium; - border: none; - cursor: pointer; transition: background-color 0.3s ease, color 0.3s ease; } -.dropdown-stepper-container, -.multi-stepper-container { - background-color: var(--color-background-secondary); - margin-bottom: 6px; - padding: 10px; -} -#main-progress { - flex: 2; - top: 0; - left: 0; - width: 100%; - z-index: 3; - border-radius: 0; - height: 20px; - background-color: var(--color-progress-background); - border: 0; -} -#main-progress::-moz-progress-bar { - background: var(--color-highlight); -} -#main-progress::-webkit-progress-bar { - background: transparent; + +#buttonsgen button:hover { + background-color: var(--color-button-primary-hover); + color: var(--color-button-primary-text-hover); + } -#main-progress::-webkit-progress-value { - background: var(--color-progress-value); +/* Responsive Indicator (for testing purposes) */ +#responsive-indicator { + text-decoration: none; + padding: 2px 0px; + transition: background-color 0.3s, color 0.3s; } -#control { + +/* Toggle Buttons Styles */ +.toggle-button { + position: absolute; + bottom:50%; + /* transform: translateY(-50%); */ + width:20px; + height: 30px; + background-color: var(--color-button-primary); + border: none; + /* border-radius: 4px; */ + cursor: pointer; display: flex; - justify-content: center; - background-color: var(--color-background-secondary); - border-top: 1px dashed var(--color-primary); - padding: 6px; + /* align-items: center; */ + /* justify-content: center; */ + transition: background-color 0.3s, transform 0.3s; + z-index: 10; /* Ensure buttons are above other elements */ } -#spinner { - height: 20px; - width: 20px; - border-radius: 10%; - border: 4px dashed var(--color-spinner); - margin-right: 10px; + +.toggle-button .arrow { + width: 50px; + height: 50px; + fill: var(--color-button-primary-text); + transition: transform 0.3s; } -.spin { - animation: spin 1s infinite ease-out; - border-color: var(--color-spinner) var(--color-spinner-highlight) !important; + +.toggle-button:hover { + background-color: var(--color-button-primary-hover); } -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } + +.toggle-button.active .arrow { + transform: rotate(180deg); /* Rotate arrow when active */ } -#loading-area { - background-color: var(--color-background-secondary); - z-index: 100; - display: flex; - justify-content: space-evenly; - align-items: center; + +/* Specific Positioning for Left Toggle Button */ +.left-toggle { + right: -15px; } -#queue-display{ - overflow-y: auto; - padding: 10px; - background-color: var(--color-background-secondary); - margin-left: 10px; - user-select: none; + +/* Specific Positioning for Right Toggle Button */ +.right-toggle { + left: -15px; } -footer { - background-color: var(--color-background-secondary); - color: var(--color-primary-text); - padding: 15px 50px; - text-align: center; - font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif; - border-top: 1px dashed var(--color-border); - margin-top: auto; + +#display-media-main { + display: flex; + flex: 1; + width: 100%; + gap: 4px; + height: 50%; } -.loader { + + + + +.canvas-container { display: flex; - flex-direction: row; + justify-content: center; align-items: center; - margin-bottom: 4px; - justify-content: space-around; - background-color: var(--color-background-secondary); - box-shadow: 0 4px 8px var(--color-background-secondary); -} -.loader label { - width: 50%; - color: var(--color-primary-text); - font-size: 15px; - font-weight: bold; -} -select { - width: 100%; - padding: 8px; - background-color: var(--color-background-secondary); - color: var(--color-primary-text); - border: none; + /* height: 100% !important; */ + /* width: 100% !important; */ + /* background: var(--color-button-primary-active); */ + } -select:focus { - border-color: var(--color-accent); + +#canvasContainer:focus { + border: 2px solid #4A90E2; } -select option:hover { - background-color: var(--color-primary-text); - color: var(--color-background-secondary); +/* Allow flex items to shrink properly */ +#canvasWrapper, +#image-container { + flex: 1 1 0; + min-width: 0; } -#display-media-main { - display: flex; - flex-direction: row; - justify-content: flex-start; - align-items: stretch; + +/* #imageCanvas { + display: block; + overflow: hidden; + padding: 0; + +} */ +#imageCanvas { width: 100%; - height: 550px; + height: 100%; +} + +/* Green dashed border around the canvas wrapper */ +/* #canvasWrapper { + flex: 1; + overflow: hidden; + border: 1px dashed var(--color-border); + box-sizing: border-box; + position: relative; + margin: 0; + + +} */ +#canvasWrapper { + flex: 1 1 0; + min-width: 0; overflow: hidden; + border: 1px dashed var(--color-border); + box-sizing: border-box; + position: relative; + margin: 0; + /* Remove or adjust padding if necessary */ + /* padding: 10px; */ } -#load-image-container { - display: flex; - flex-direction: column; - justify-content: flex-start; - gap: 10px; - width: 300px; + +#images-container { + /* display: flex; */ + /* flex-direction: column; */ + flex: 1; /* Allow it to grow and fill available space within #history */ + /* padding: 10px;/ */ + overflow: hidden; height: 100%; - overflow-y: auto; + } +#history #header { + /* background: var(--color-background-pattern); */ + text-align: center; + margin-top: 10px; + margin-bottom: 10px; + + /* padding: 5px; */ + width: 100%; + background-color: var(--color-button-secondary); +} + +#history { + flex: 1; /* Allow it to grow and fill space */ + display: flex; + flex-direction: column; + overflow: hidden; /* Child handles scrolling */ + width: 100%; +} +#history h2 { + color: var(--color-border); + text-align: center; + margin-bottom: 2px; + font-size: 1.2em; + /* background-color: red; */ + width: 100%; +} +.history-thumbnail { + /* border-radius: 5px; */ + overflow: hidden; + cursor: pointer; + position: relative; + margin-bottom: 2px; + width: 100%; + +} +.history-thumbnail img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.3s; + border: 1px dashed var(--color-border); + +} + +.content .mid-col #image-container img, +.content .mid-col #image-container video { + width: auto; + height: 100%; + object-fit: contain; + border-left: 1px dashed var(--color-border); + border-right: 1px dashed var(--color-border); + max-width: 100%; +} + +#image-container { + flex: 1; + height: 100%; + overflow: hidden; + position: relative; + align-items: center; + border: 1px dashed var(--color-border); + /* width: 10%; */ + /* flex: 1; */ + /* display: hidden; */ + +} + + +.image-loader-title { + display: flex; + justify-content: center; + align-items: center; + background-color: var(--color-background-secondary); + color: var(--color-primary-text); + /* font-size: 1.2em; */ + font-weight: bold; + border: 1px dashed var(--color-border); + border-bottom: none; + padding: 5px; +} +#load-image-container { + width: 20%; + border: 1px dashed var(--color-border); + padding: 10px; + + +} + .image-loader { - flex: 0 1 auto; width: 100%; - min-height: 250px; - max-height: 50%; position: relative; - border-radius: 5px; - border: 2px dashed var(--color-border); - /* background: var(--color-background-secondary); */ + margin-bottom: 10px; + padding-bottom: 10px; } -#load-image-container img { + +.image-loader img { + display: block; + width: 100% !important; /* Force same width */ + height: auto !important; /* Let height adjust automatically */ + position: relative !important; /* Remove absolute positioning */ + top: auto !important; + left: auto !important; + transform: none !important; + object-fit: contain; + border: 1px dashed var(--color-border); + +} + + +/* #load-image-container img { max-width: 100%; max-height: 100%; width: auto; @@ -718,8 +1091,8 @@ select option:hover { padding: 5px; object-fit: cover; -} -#image-container { +} */ +/* #image-container { flex: 1; height: 100%; overflow: hidden; @@ -735,44 +1108,26 @@ select option:hover { object-fit: contain; border: 1px dashed var(--color-border); border-radius: 5px; -} -#batch-images-container { +} */ +.plugin-ui-container { + display: flex; + flex-direction: row; + flex-wrap: nowrap; + border-top: 1px dashed var(--color-border); + background-color: var(--color-background-secondary); width: 100%; - height: 100px; - border: 1px solid var(--color-primary); - position: relative; } -#batch-images { - padding: 10px; - background-color: var(--color-background-secondary); - border: 1px solid var(--color-border); - border-radius: 5px; - height: 100%; - scrollbar-width: thin; - scrollbar-color: var(--color-primary) var(--color-scrollbar-track); + +#quick-controls { display: flex; flex-direction: row; + justify-content: space-around; + align-items: center; + background-color: var(--color-background-secondary); + flex: 0 1 auto; /* Don't grow, can shrink, auto basis */ + justify-self: right; } -#history h2 { - color: var(--color-primary-text); - text-align: center; - margin-bottom: 10px; - font-size: 1.2em; -} -.history-thumbnail { - border: 1px dashed var(--color-border); - /* border-radius: 5px; */ - overflow: hidden; - cursor: pointer; - position: relative; -} -.history-thumbnail img { - width: 100%; - height: 100%; - object-fit: cover; - transition: transform 0.3s; -} -#history, +#images-container, #side-workflow-controls, #load-image-container, #custom-theme-modal, @@ -781,6 +1136,7 @@ select option:hover { scrollbar-width: thin; scrollbar-color: var(--color-scrollbar-thumb) var(--color-scrollbar-track); overflow-y: auto; + padding-right: 6px; } .custom-scrollbar::-webkit-scrollbar { width: 2px; @@ -797,25 +1153,147 @@ select option:hover { .custom-scrollbar::-webkit-scrollbar-thumb:hover { background-color: var(--color-accent); } -.images-thumbnail { - border: 2px solid var(--color-border); - /* border-radius: 5px; */ - overflow: hidden; + +.add-lora-button { + background-color: var(--color-button-secondary); + color: var(--color-button-secondary-text); + /* padding: 10px; */ + /* margin: 0 10px; */ + border: none; cursor: pointer; - position: relative; -} -.images-thumbnail img { + transition: background-color 0.3s ease, color 0.3s ease; width: 100%; - height: 100%; - object-fit: cover; - transition: transform 0.3s; } -@media (max-width: 768px) { + +/* Large Tablets and Desktops */ +@media (max-width: 1450px) { + .seeder-container > input[type="text"] { + width:40%; + } + +} + +/* Large Tablets and Desktops */ +@media (max-width: 1200px) { .content { + flex-direction: row; + } + #responsive-indicator { + background-color: orange; + } + .stepper-container .range-input { + display: none; + } + .stepper-container span { + flex: 1; + } + .seeder-container > input[type="text"] { + width:26%; + } +} + +/* Tablets (768px to 992px) */ +@media (min-width: 768px) and (max-width: 992px) { + #responsive-indicator { + background-color: red; + } + .seeder-container > input[type="text"] { + width:15%; + } + #dimension-selector { + display: flex; + width: 100%; + align-items: center; flex-direction: column; + justify-content: space-evenly; + } +} + +/* Small Tablets and Large Phones */ +@media (max-width: 768px) { + #responsive-indicator { + background-color: green; + } + #Support { + display: none; + } + .logo-text { + display: none; + } + #dimension-selector { + display: flex; + width: 100%; + align-items: center; + flex-direction: column; + justify-content: space-evenly; + } +} + +/* Mobile Devices */ +@media (max-width: 576px) { + #responsive-indicator { + background-color: blue; + } + #right-header, + #left-col, + #right-col { + display: none; + } + + #mid { + display: none; + } + .logo-text { + display: none; + } + + #logo { + padding-right: 10px; + } + .appName { + padding: 0; + border: none; + flex: 1; + display: flex; + justify-content: flex-end; + align-items: center; + } + + .appName h2 { + font-size: 14px; + } + + #img-logo img { + max-width: 80px; + } + + #footer-content p { + font-size:11px; + } + .toggle-button { + width: 25px; + height: 25px; + } + + .toggle-button .arrow { + width: 12px; + height: 12px; + } + + /* Adjust positioning if needed */ + .left-toggle { + right: -12.5px; + } + + .right-toggle { + left: -12.5px; } } + + + + #theme-selector { display: flex; justify-content: center; @@ -864,7 +1342,6 @@ select option:hover { font-weight: bold; font-size: 14px; - } #theme-option-create-custom { color: var(--color-primary-text) !important; @@ -1248,6 +1725,8 @@ html:not(.css-loading) body { transition: none; } } + + .lightbox-overlay { position: fixed; top: 0; @@ -1353,606 +1832,360 @@ html:not(.css-loading) body { max-height: 100%; border: 1px dashed var(--color-border); } -.add-lora-button { - background-color: var(--color-button-secondary); - color: var(--color-button-secondary-text); - /* padding: 10px; */ - /* margin: 0 10px; */ - border: none; - cursor: pointer; - transition: background-color 0.3s ease, color 0.3s ease; - width: 100%; -} -.add-lora-button:hover { - background-color: var(--color-button-secondary-hover); - color: var(--color-button-secondary-text-hover); -} -/* ToggleComponent.css */ -.toggle-component-wrapper { - display: flex; - align-items: center; - background-color: var(--color-background-secondary); - padding: 15px 20px; - /* border-radius: 12px; */ - /* box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); */ - color: var(--color-primary-text); - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - /* margin: 10px 0; */ - transition: background-color 0.3s, box-shadow 0.3s; - width: 100%; - -} -.toggle-component-wrapper:hover { - box-shadow: 0 6px 8px rgba(0, 0, 0, 0.15); -} -.toggle-label-container { - display: flex; - align-items: center; - margin: 0 10px; - flex-grow: 1; -} -.toggle-label { - font-size: 1.1rem; - margin: 0 10px; - user-select: none; - transition: color 0.3s; -} - -.toggle-icon { - width: 24px; - height: 24px; - object-fit: contain; - transition: opacity 0.3s; -} -.off-icon { - display: inline; -} -.on-icon { - display: none; -} - -.toggle-switch { - position: relative; - display: inline-block; - width: 60px; - height: 34px; -} -.toggle-switch input { - opacity: 0; - width: 0; - height: 0; -} -.slider { +/* CSS Isolation: Prefix all classes with 'cbp-' */ +.cbp-brush-ui-container { + background-color: var(--color-background); + user-select: none; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + font-family: Arial, sans-serif; + font-size: 14px; + color: var(--color-text); position: absolute; + top: 10%; + left: 25%; + z-index: 1000; + transition: width 0.3s, height 0.3s; + display: flex; + flex-direction: column; + align-items: stretch; + + border: 1px dashed var(--color-border); + } + .cbp-brush-ui-header { + cursor: move; + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px; + background-color: var(--color-header-background); + border-bottom: 1px dashed var(--color-border); + border-top-left-radius: 8px; + border-top-right-radius: 8px; + } + .cbp-brush-ui-title { + font-weight: bold; + } + .cbp-brush-ui-minimize-btn { + background: none; + border: none; + font-size: 18px; cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: var(--color-border); - transition: background-color 0.4s, box-shadow 0.4s; - /* border-radius: 34px; */ - box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2); -} - -.slider::before { - position: absolute; - content: ""; - height: 26px; - width: 26px; - left: 4px; - bottom: 4px; - background-color: var(--color-background); - transition: transform 0.4s, background-color 0.4s; - /* border-radius: 50%; */ - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); -} - -.toggle-switch input:checked + .slider { - background-color: var(--color-button-primary); -} - -.toggle-switch input:checked + .slider::before { - transform: translateX(26px); - background-color: var(--color-button-primary-text); -} - -.toggle-switch input:focus + .slider { - box-shadow: 0 0 2px var(--color-button-primary-hover); -} - -.toggle-switch:hover .slider { - background-color: var(--color-button-primary-hover); -} - -.toggle-switch input:checked + .slider:hover { - background-color: var(--color-button-primary-active); -} - -.toggle-switch input:checked + .slider::before:hover { - background-color: var(--color-button-primary-text-active); -} - -@media (max-width: 600px) { - .toggle-component-wrapper { - flex-direction: column; - align-items: flex-start; + width: 24px; + height: 24px; + padding: 0; + color: var(--color-text); + display: none; } - - .toggle-label-container { - margin: 0 0 10px 0; + .cbp-brush-ui-content { + padding: 10px; + display: flex; + flex-direction: column; + gap: 10px; + width: 100%; + box-sizing: border-box; } -} - -.input-component-wrapper { + .cbp-brush-ui-content label { display: flex; - align-items: center; - background-color: var(--color-background-secondary); - padding: 10px 14px; /* Reduced padding for a narrower component */ - /* border: 1px solid var(--color-border); */ - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - color: var(--color-primary-text); - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - margin: 0; - transition: background-color 0.3s, box-shadow 0.3s, border-color 0.3s; + flex-direction: column; + font-size: 12px; + gap: 4px; + } + .cbp-brush-ui-content input[type="range"], + .cbp-brush-ui-content input[type="number"], + .cbp-brush-ui-content input[type="color"], + .cbp-brush-ui-content button { width: 100%; - /* flex-grow: 1; */ - -} - -.input-component-wrapper:hover { - /* box-shadow: 0 3px 5px rgba(0, 0, 0, 0.15); */ - /* border-color: var(--color-button-primary-hover); */ -} - -.input-label { - font-size: 1rem; - margin-right: 10px; - user-select: none; - flex-shrink: 0; - width: 120px; -} - -.input-container { - position: relative; + height: 30px; + box-sizing: border-box; + padding: 4px; + /* border: 1px solid #ccc; */ + /* border-radius: 4px; */ + font-size: 14px; + } + + .cbp-brush-ui-toggle-btn { + flex: 2; display: flex; + justify-content: center; align-items: center; - /* flex-grow: 1; */ - background-color: var(--color-background-secondary); - /* margin-bottom: 4px; */ -} -.input-container input[type="text"], -.input-container input[type="number"], -.input-container textarea { - /* border:none; - border-bottom: 1px solid var(--color-border); - transition: border-color 0.3s ease-in-out; - padding: 8px; - background-color: var(--color-background-secondary); - color: var(--color-primary-text); - margin:0 10px; */ - /* background-color: red; */ - width: 100%; -} - -/* -.input-container input[type="text"]:hover, -.input-container input[type="number"]:hover, -.input-container textarea:hover { - border-color: var(--color-border); -} - -.input-container input[type="text"]:focus, -.input-container input[type="number"]:focus, -.input-container textarea:focus { - border-color: var(--color-primary-text); - outline: none; -} */ - -.styled-input { + background: var(--color-button-primary); + border: none; + cursor: pointer; + padding: 4px !important; + height: 40px !important; + width: auto; + } + #cbp-color-picker { + flex: 1; + height: auto; + + } + #cbp-color-picker:hover { + border: 1px dashed var(--color-button-primary-text); + } + #cbp-toggle-btn-icon { + width: 70%; + height: 100%; + /* display: block; Ensures the SVG takes up space */ + } + #cbp-toggle-drawing-mode-btn.active { + /* background: var(--color-button-primary-text); */ + border: 1px dashed var(--color-button-primary-text) !important; + } + #ui-toggle-btn.active { + background: var(--color-button-primary-text); + /* border: 1px dashed var(--color-button-secondary-text-active) !important; */ + + } + .cbp-brush-ui-color-picker { + flex: 1; + display: flex; + flex-direction: row; + gap: 10px; + width: 100%; - padding: 8px 12px; - padding-left: 40px; - border: 1px solid var(--color-border); + height: 100%; + } + + .cbp-brush-ui-content input[type="color"] { + padding: 0; + /* border: none; */ background-color: var(--color-background); - color: var(--color-primary-text); - font-size: 1rem; - transition: border-color 0.3s, box-shadow 0.3s; + outline: none; + -webkit-appearance: none; /* Removes default styling in WebKit browsers */ + border-bottom: 1px dashed var(--color-border); + + } + + /* Remove the color swatch border in WebKit browsers */ + .cbp-brush-ui-content input[type="color"]::-webkit-color-swatch-wrapper { + padding: 0; + border: none; + } + + .cbp-brush-ui-content input[type="color"]::-webkit-color-swatch { + border: none; + } + + /* Remove the color swatch border in Firefox */ + .cbp-brush-ui-content input[type="color"]::-moz-color-swatch { + border: none; + } + .cbp-brush-ui-minimized .cbp-brush-ui-content { + display: none; + } + .cbp-brush-ui-full { + width: 155px; + height: auto; + } + .cbp-brush-ui-mini { + width: 80px; + height: auto; + } + .cbp-brush-ui-minimized { + width: 60px; + height: auto; + } + .cbp-brush-ui-input-group { + display: flex; + align-items: center; + gap: 10px; + } + .cbp-brush-ui-input-group input { flex: 1; -} + } + .cbp-hidden-cursor { + cursor: none !important; + } + + + + + .mbp-container * { + box-sizing: border-box; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + } + + .mbp-container { + /* --mbp-primary: var(--color-button-primary); + --mbp-primary-hover: var(--color-button-primary-hover); + --mbp-bg: var(--color-background-secondary); + --mbp-border: var(--color-border); + --mbp-text: var(--color-primary-text); + --mbp-icon: var(--color-primary); */ + padding: 0.5rem; + } + + .mbp-header { + display: flex; + gap: 0.5rem; + margin-bottom: 0.75rem; + } + + .mbp-header .mbp-select { + flex: 1; + } + + .mbp-button { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.5rem; + border: 1px solid var(--mbp-border); + background: var(--color-button-primary) ; + /* border-radius: 0.375rem; */ + cursor: pointer; + transition: all 0.2s; + color: var(--mbp-icon); + min-width: 36px; + height: 36px; + width: 100%; + } -.styled-input:focus { + .mbp-button:hover { + background: var(--color-button-primary-hover); + color: var(--mbp-primary); + } + + .mbp-button svg { + width: 1.25rem; + height: 1.25rem; + } + #clearAllMasksBtn { + /* Make the button a flex container to center the SVG */ + /* display: flex; */ + /* align-items: center; */ + /* justify-content: center; */ + + /* Set a specific size for your button if needed */ + /* width: 50px; */ + height: 35px; + /* padding: 8px; */ + } + + #clearAllMasksBtn svg { + width: 100%; + height: 100%; + /* width: auto; */ + height: 35px; + } + + .mbp-select { + padding: 0.5rem; + border: none; + background: var(--color-background); + color: var(--mbp-text); + cursor: pointer; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%236b7280'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 0.5rem center; + background-size: 1.25rem; + padding-right: 2.5rem; + height: 36px; outline: none; - border-color: var(--color-button-primary); - /* box-shadow: 0 0 3px rgba(86, 13, 123, 0.5); */ -} - -.input-component-wrapper, -.input-container, -.styled-input { - border-radius: 0; -} - -@media (max-width: 600px) { - .input-component-wrapper { - flex-direction: column; - align-items: flex-start; - padding: 8px 12px; + } + .mbp-select:hover { + /* background: var(--color-button-primary-hover); */ + border-top: solid 1px var(--color-button-primary-hover); + } + + .mbp-button-group { + display: flex; + justify-content: space-between; + margin-bottom: 10px; } - .input-label { - margin-right: 0; - margin-bottom: 6px; - width: 100%; + .mbp-toggle-wrapper { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; + border: 1px dashed var(--color-border); + padding: 2px; } + .mbp-toggle { + position: relative; + display: inline-block; + width: 40px; + height: 100%; + border-left: 1px dashed var(--color-border); + align-items: center; + - .styled-input { - padding-left: 36px; } -} + .mbp-toggle input { + opacity: 0; + width: 0; + height: 0; + } + .mbp-toggle-slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + /* background-color: var(--color-button-primary); */ + /* background-color: var(--color-background-secondary); */ + transition: .4s; + } + .mbp-toggle-slider:before { + position: absolute; + content: ""; + height: 20px; + width: 20px; + left: 2px; + bottom: 0px; + background-color: var(--color-button-secondary); + transition: .4s; + } + .mbp-toggle-slider:after { + position: absolute; + content: ""; + height: 20px; + width: 20px; + left: 2px; + bottom: 0px; + background-color: var(--color-button-primary); + transition: .4s; + } + .mbp-toggle input:checked + .mbp-toggle-slider { + /* background-color: var(--color-button-secondary-active); */ -.multi-component-container { - background: var(--color-background-secondary); - margin-bottom: 4px; -} -.plugin-ui-container { - background: var(--color-background-secondary); - padding: 4px; - /* position: absolute; */ - /* top: 100px; */ - /* left: 19%; */ - z-index: 1000; - transition: width 0.3s, height 0.3s; - display: flex; - flex-direction: row; - /* justify-content: space-between; */ - align-items: stretch; - /* border: 1px dashed var(--color-border); */ - margin: 5px; -} -/* Style for the plugin UI container */ -#pluginUIContainer { - /* padding: 10px; */ - /* background-color: var(--color-background-secondary); */ - /* Adjust as needed */ -} + } + .mbp-toggle input:checked + .mbp-toggle-slider:before { + transform: translateX(20px); + } -/* Parent container for the canvas */ -/* #canvasContainer { - position: relative; - width: 100%; - height: calc(100% - 60px); - border: 1px solid #ccc; - box-sizing: border-box; -} */ + + .mbp-toggle input:checked + .mbp-toggle-slider:after { + transform: translateX(20px); + background-color: var(--color-button-secondary-active); -/* Make the canvas fill its parent container */ -/* #imageCanvas { - width: 100% !important; - height: 100% !important; - display: block; - background: var(--color-progress-background); + } + .mbp-label { + font-size: 0.75rem; + color: var(--mbp-text); + user-select: none; + padding: 0 6px; + } -} */ - - /* CSS Isolation: Prefix all classes with 'cbp-' */ - .cbp-brush-ui-container { - background-color: var(--color-background); - user-select: none; - border-radius: 8px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); - font-family: Arial, sans-serif; - font-size: 14px; - color: var(--color-text); - position: absolute; - top: 100px; - left: 19%; - z-index: 1000; - transition: width 0.3s, height 0.3s; - display: flex; - flex-direction: column; - align-items: stretch; - - border: 1px dashed var(--color-border); - } - .cbp-brush-ui-header { - cursor: move; - display: flex; - justify-content: space-between; - align-items: center; - padding: 10px; - background-color: var(--color-header-background); - border-bottom: 1px dashed var(--color-border); - border-top-left-radius: 8px; - border-top-right-radius: 8px; - } - .cbp-brush-ui-title { - font-weight: bold; - } - .cbp-brush-ui-minimize-btn { - background: none; - border: none; - font-size: 18px; - cursor: pointer; - width: 24px; - height: 24px; - padding: 0; - color: var(--color-text); - display: none; - } - .cbp-brush-ui-content { - padding: 10px; - display: flex; - flex-direction: column; - gap: 10px; - width: 100%; - box-sizing: border-box; - } - .cbp-brush-ui-content label { - display: flex; - flex-direction: column; - font-size: 12px; - gap: 4px; - } - .cbp-brush-ui-content input[type="range"], - .cbp-brush-ui-content input[type="number"], - .cbp-brush-ui-content input[type="color"], - .cbp-brush-ui-content button { - width: 100%; - height: 30px; - box-sizing: border-box; - padding: 4px; - /* border: 1px solid #ccc; */ - /* border-radius: 4px; */ - font-size: 14px; - } - - .cbp-brush-ui-toggle-btn { - flex: 2; - display: flex; - justify-content: center; - align-items: center; - background: var(--color-button-primary); - border: none; - cursor: pointer; - padding: 4px !important; - height: 40px !important; - width: auto; - } - #cbp-color-picker { - flex: 1; - height: auto; - - } - #cbp-color-picker:hover { - border: 1px dashed var(--color-button-primary-text); - } - #cbp-toggle-btn-icon { - width: 70%; - height: 100%; - /* display: block; Ensures the SVG takes up space */ - } - #cbp-toggle-drawing-mode-btn.active { - /* background: var(--color-button-primary-text); */ - border: 1px dashed var(--color-button-primary-text) !important; - } - #ui-toggle-btn.active { - background: var(--color-button-primary-text); - /* border: 1px dashed var(--color-button-secondary-text-active) !important; */ - - } - .cbp-brush-ui-color-picker { - flex: 1; - display: flex; - flex-direction: row; - gap: 10px; - - width: 100%; - height: 100%; - } - - .cbp-brush-ui-content input[type="color"] { - padding: 0; - /* border: none; */ - background-color: var(--color-background); - outline: none; - -webkit-appearance: none; /* Removes default styling in WebKit browsers */ - border-bottom: 1px dashed var(--color-border); - - } - - /* Remove the color swatch border in WebKit browsers */ - .cbp-brush-ui-content input[type="color"]::-webkit-color-swatch-wrapper { - padding: 0; - border: none; - } - - .cbp-brush-ui-content input[type="color"]::-webkit-color-swatch { - border: none; - } - - /* Remove the color swatch border in Firefox */ - .cbp-brush-ui-content input[type="color"]::-moz-color-swatch { - border: none; - } - .cbp-brush-ui-minimized .cbp-brush-ui-content { - display: none; - } - .cbp-brush-ui-full { - width: 155px; - height: auto; - } - .cbp-brush-ui-mini { - width: 80px; - height: auto; - } - .cbp-brush-ui-minimized { - width: 60px; - height: auto; - } - .cbp-brush-ui-input-group { - display: flex; - align-items: center; - gap: 10px; - } - .cbp-brush-ui-input-group input { - flex: 1; - } - .cbp-hidden-cursor { - cursor: none !important; - } - - - - - .mbp-container * { - box-sizing: border-box; - font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; - } - - .mbp-container { - /* --mbp-primary: var(--color-button-primary); - --mbp-primary-hover: var(--color-button-primary-hover); - --mbp-bg: var(--color-background-secondary); - --mbp-border: var(--color-border); - --mbp-text: var(--color-primary-text); - --mbp-icon: var(--color-primary); */ - padding: 0.5rem; - } - - .mbp-header { - display: flex; - gap: 0.5rem; - margin-bottom: 0.75rem; - } - - .mbp-header .mbp-select { - flex: 1; - } - - .mbp-button { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0.5rem; - border: 1px solid var(--mbp-border); - background: var(--color-button-primary) ; - /* border-radius: 0.375rem; */ - cursor: pointer; - transition: all 0.2s; - color: var(--mbp-icon); - min-width: 36px; - height: 36px; - width: 100%; - } - - .mbp-button:hover { - background: var(--color-button-primary-hover); - color: var(--mbp-primary); - } - - .mbp-button svg { - width: 1.25rem; - height: 1.25rem; - } - - .mbp-select { - padding: 0.5rem; - border: none; - background: var(--color-background); - color: var(--mbp-text); - cursor: pointer; - appearance: none; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%236b7280'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: right 0.5rem center; - background-size: 1.25rem; - padding-right: 2.5rem; - height: 36px; - outline: none; - } - .mbp-select:hover { - /* background: var(--color-button-primary-hover); */ - border-top: solid 1px var(--color-button-primary-hover); - } - - .mbp-toggle-wrapper { - display: flex; - align-items: center; - gap: 0.75rem; - margin: 0.75rem 0; - padding: 0.25rem 0; - display: none !important; - - } - - .mbp-toggle { - position: relative; - display: inline-block; - width: 3.5rem; - height: 1.75rem; - margin: 0; - } - - .mbp-toggle input { - opacity: 0; - width: 0; - height: 0; - margin: 0; - } - - .mbp-toggle-slider { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - /* background-color: #e5e7eb; */ - transition: .3s; - border-radius: 2rem; - } - - .mbp-toggle-slider:before { - position: absolute; - content: ""; - height: 1.25rem; - width: 1.25rem; - left: 0.25rem; - bottom: 0.25rem; - /* background-color: white; */ - transition: .3s; - border-radius: 50%; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); - } - - .mbp-toggle input:checked + .mbp-toggle-slider { - background-color: var(--mbp-primary); - } - - .mbp-toggle input:checked + .mbp-toggle-slider:before { - transform: translateX(1.75rem); - } - - .mbp-label { - font-size: 0.75rem; - color: var(--mbp-text); - user-select: none; - padding: 0 6px; - } - - .mbp-button-group { - display: flex; - gap: 0.5rem; - margin: 0.75rem 0; - - } \ No newline at end of file + .mbp-button-group { + display: flex; + gap: 0.5rem; + margin: 0.75rem 0; + + } + + + \ No newline at end of file diff --git a/web/core/js/common/components/CanvasComponent.js b/web/core/js/common/components/CanvasComponent.js index 8fb3fbb..c420394 100644 --- a/web/core/js/common/components/CanvasComponent.js +++ b/web/core/js/common/components/CanvasComponent.js @@ -1,5 +1,6 @@ import { showSpinner, hideSpinner } from './utils.js'; import { updateWorkflow } from './workflowManager.js'; +import { messageHandler } from './messageHandler.js'; function dataURLToBlob(dataURL) { const [header, data] = dataURL.split(','); @@ -16,7 +17,15 @@ function dataURLToBlob(dataURL) { return new Blob([new Uint8Array(array)], { type: mime }); } -async function processAndUpload(items, getImage, uploadDescription, defaultErrorMessage, successMessagePrefix, workflow) { +async function processAndUpload( + items, + getImage, + uploadDescription, + defaultErrorMessage, + successMessagePrefix, + workflow, + postUploadCallback = null +) { for (const item of items) { const { id, label, nodePath } = item; @@ -58,14 +67,41 @@ async function processAndUpload(items, getImage, uploadDescription, defaultError throw new Error('Invalid JSON response from server.'); } - console.log(`${successMessagePrefix} ${id} uploaded successfully:`, result); - + // console.log(`${successMessagePrefix} ${id} uploaded successfully:`, result); if (result && result.name) { updateWorkflow(workflow, nodePath, result.name); - console.log(`Workflow updated at nodePath ${nodePath} with imageUrl: ${result.name}`); + switch (uploadDescription) { + case 'Original Image': + messageHandler.setOriginalImage(imageDataURL); + // console.log('Original Image set in MessageHandler'); + break; + case 'Cropped Mask Image': + messageHandler.setCroppedMaskImage(imageDataURL); + // console.log('Cropped Mask Image set in MessageHandler'); + break; + case 'Mask Alpha Image': + messageHandler.setAlphaMaskImage(imageDataURL); + // console.log('Mask Alpha Image set in MessageHandler'); + break; + // case 'Mask Image': + // messageHandler.setMaskImage(imageDataURL); + // console.log('Mask Image set in MessageHandler'); + // break; + // case 'Selected Mask Alpha Image': + // messageHandler.setCanvasSelectedMaskOutputs(imageDataURL); + // // console.log('Selected Mask Alpha Image added to CanvasSelectedMaskOutputs in MessageHandler'); + // messageHandler.setMaskImage(imageDataURL); + // // console.log('Mask Image set in MessageHandler'); + // break; + default: + console.warn(`No setter defined for upload description: ${uploadDescription}`); + } } else { throw new Error('Server response did not include imageUrl or imageName.'); } + if (postUploadCallback) { + postUploadCallback(result); + } } catch (error) { console.error(`Error uploading ${uploadDescription.toLowerCase()} ${id}:`, error); alert(`Error uploading ${label}: ${error.message}`); @@ -75,6 +111,7 @@ async function processAndUpload(items, getImage, uploadDescription, defaultError } } + export default async function CanvasComponent(flowConfig, workflow, canvasLoader) { if (flowConfig.canvasOutputs && Array.isArray(flowConfig.canvasOutputs)) { await processAndUpload( @@ -87,30 +124,6 @@ export default async function CanvasComponent(flowConfig, workflow, canvasLoader ); } - // **Process canvasLoadedImages and canvasSelectedMaskOutputs (Case 1)** - if ( - flowConfig.canvasLoadedImages && Array.isArray(flowConfig.canvasLoadedImages) && - flowConfig.canvasSelectedMaskOutputs && Array.isArray(flowConfig.canvasSelectedMaskOutputs) - ) { - await processAndUpload( - flowConfig.canvasLoadedImages, - () => canvasLoader.getOriginalImage(), - 'Original Image', - 'Original image upload failed.', - 'Original Image', - workflow - ); - - await processAndUpload( - flowConfig.canvasSelectedMaskOutputs, - () => canvasLoader.getSelectedMaskAlphaOnImage(), - 'Selected Mask Alpha Image', - 'Selected mask alpha image upload failed.', - 'Selected Mask Alpha Image', - workflow - ); - } - if (flowConfig.canvasMaskOutputs && Array.isArray(flowConfig.canvasMaskOutputs)) { await processAndUpload( flowConfig.canvasMaskOutputs, @@ -133,7 +146,39 @@ export default async function CanvasComponent(flowConfig, workflow, canvasLoader ); } - // **Process canvasSelectedMaskOutputs (New Functionality for Case 1)** + if (flowConfig.canvasCroppedMaskOutputs && Array.isArray(flowConfig.canvasCroppedMaskOutputs)) { + await processAndUpload( + flowConfig.canvasCroppedMaskOutputs, + () => canvasLoader.getCroppedMask(), + 'Cropped Mask Image', + 'Cropped mask image upload failed.', + 'Cropped Mask Image', + workflow + ); + } + + if (flowConfig.canvasCroppedImageOutputs && Array.isArray(flowConfig.canvasCroppedImageOutputs)) { + await processAndUpload( + flowConfig.canvasCroppedImageOutputs, + () => canvasLoader.getCroppedImage(), + 'Cropped Image', + 'Cropped image upload failed.', + 'Cropped Image', + workflow + ); + } + + if (flowConfig.canvasCroppedAlphaOnImageOutputs && Array.isArray(flowConfig.canvasCroppedAlphaOnImageOutputs)) { + await processAndUpload( + flowConfig.canvasCroppedAlphaOnImageOutputs, + () => canvasLoader.getCroppedAlphaOnImage(), + 'Cropped Alpha Image', + 'Cropped alpha image upload failed.', + 'Cropped Alpha Image', + workflow + ); + } + if (flowConfig.canvasSelectedMaskOutputs && Array.isArray(flowConfig.canvasSelectedMaskOutputs)) { await processAndUpload( flowConfig.canvasSelectedMaskOutputs, @@ -144,4 +189,121 @@ export default async function CanvasComponent(flowConfig, workflow, canvasLoader workflow ); } + + // ** ( Case 1 ) ** + if ( + flowConfig.canvasLoadedImages && Array.isArray(flowConfig.canvasLoadedImages) && + flowConfig.canvasAlphaOutputs && Array.isArray(flowConfig.canvasAlphaOutputs) + ) { + await processAndUpload( + flowConfig.canvasLoadedImages, + () => canvasLoader.getOriginalImage(), + 'Original Image', + 'Original image upload failed.', + 'Original Image', + workflow + ); + + await processAndUpload( + flowConfig.canvasAlphaOutputs, + () => canvasLoader.getMaskAlphaOnImage(), + 'Mask Alpha Image', + 'Mask alpha image upload failed.', + 'Mask Alpha Image', + workflow + ); + } + + // ** ( Case 3 ) ** + // if ( + // flowConfig.canvasCroppedImageOutputs && Array.isArray(flowConfig.canvasCroppedImageOutputs) && + // flowConfig.canvasCroppedMaskOutputs && Array.isArray(flowConfig.canvasCroppedMaskOutputs) + // ) { + // await processAndUpload( + // flowConfig.canvasCroppedImageOutputs, + // () => canvasLoader.getCroppedImage(), + // 'Cropped Image', + // 'Cropped image upload failed.', + // 'Cropped Image', + // workflow + // ); + + // await processAndUpload( + // flowConfig.canvasCroppedMaskOutputs, + // () => canvasLoader.getCroppedMask(), + // 'Cropped Mask Image', + // 'Cropped mask image upload failed.', + // 'Cropped Mask Image', + // workflow + // ); + // } + + // ** ( Case 4 ) ** + if ( + flowConfig.canvasCroppedImageOutputs && Array.isArray(flowConfig.canvasCroppedImageOutputs) && + flowConfig.canvasCroppedMaskOutputs && Array.isArray(flowConfig.canvasCroppedMaskOutputs) && + flowConfig.canvasLoadedImages && Array.isArray(flowConfig.canvasLoadedImages) + ) { + await processAndUpload( + flowConfig.canvasCroppedImageOutputs, + () => canvasLoader.getCroppedImage(), + 'Cropped Image', + 'Cropped image upload failed.', + 'Cropped Image', + workflow + ); + + await processAndUpload( + flowConfig.canvasCroppedMaskOutputs, + () => canvasLoader.getCroppedMask(), + 'Cropped Mask Image', + 'Cropped mask image upload failed.', + 'Cropped Mask Image', + workflow + ); + + await processAndUpload( + flowConfig.canvasLoadedImages, + () => canvasLoader.getOriginalImage(), + 'Original Image', + 'Original image upload failed.', + 'Original Image', + workflow + ); + } + + // ** ( Case 5 ) ** + if ( + flowConfig.canvasCroppedImageOutputs && Array.isArray(flowConfig.canvasCroppedImageOutputs) && + flowConfig.canvasCroppedAlphaOnImageOutputs && Array.isArray(flowConfig.canvasCroppedAlphaOnImageOutputs) && + flowConfig.canvasLoadedImages && Array.isArray(flowConfig.canvasLoadedImages) + ) { + await processAndUpload( + flowConfig.canvasCroppedImageOutputs, + () => canvasLoader.getCroppedImage(), + 'Cropped Image', + 'Cropped image upload failed.', + 'Cropped Image', + workflow + ); + + await processAndUpload( + flowConfig.canvasCroppedAlphaOnImageOutputs, + () => canvasLoader.getCroppedAlphaOnImage(), + 'Cropped Alpha Image', + 'Cropped alpha image upload failed.', + 'Cropped Alpha Image', + workflow + ); + + await processAndUpload( + flowConfig.canvasLoadedImages, + () => canvasLoader.getOriginalImage(), + 'Original Image', + 'Original image upload failed.', + 'Original Image', + workflow + ); + } + } diff --git a/web/core/js/common/components/DataComponent.js b/web/core/js/common/components/DataComponent.js new file mode 100644 index 0000000..464517c --- /dev/null +++ b/web/core/js/common/components/DataComponent.js @@ -0,0 +1,48 @@ +import { store } from '../scripts/stateManagerMain.js'; +import { updateWorkflow } from './workflowManager.js'; + +function getNestedValue(obj, path) { + return path.split('.').reduce((acc, key) => (acc && acc[key] !== undefined) ? acc[key] : undefined, obj); +} + +class DataComponent { + constructor(config, workflow) { + this.id = config.id; + this.name = config.name; + this.nodePath = config.nodePath; + this.dataPath = config.dataPath; + this.workflow = workflow; + + this.handleStoreUpdate = this.handleStoreUpdate.bind(this); + + this.updateWorkflowWithStoreData(); + + this.unsubscribe = store.subscribe(this.handleStoreUpdate); + } + + updateWorkflowWithStoreData() { + const state = store.getState(); + const data = getNestedValue(state, this.dataPath); + + if (data === undefined) { + console.warn(`DataComponent [${this.id}]: No data found at path "${this.dataPath}" in the store.`); + return; + } + + updateWorkflow(this.workflow, this.nodePath, data); + // console.log(`DataComponent [${this.id}]: Updated workflow at "${this.nodePath}" with data from "${this.dataPath}".`); + } + + handleStoreUpdate() { + this.updateWorkflowWithStoreData(); + } + + destroy() { + if (this.unsubscribe) { + this.unsubscribe(); + // console.log(`DataComponent [${this.id}]: Unsubscribed from store updates.`); + } + } +} + +export default DataComponent; diff --git a/web/core/js/common/components/DimSelector.js b/web/core/js/common/components/DimSelector.js index a6b0d6d..b11ba80 100644 --- a/web/core/js/common/components/DimSelector.js +++ b/web/core/js/common/components/DimSelector.js @@ -163,7 +163,7 @@ class DimensionSelector { const path = this.config.nodePath; // const path = config.nodePath updateWorkflow(this.workflow, `${path}.${dimension}`, value); - console.log(`Workflow updated - ${dimension}: ${value}`); + // console.log(`Workflow updated - ${dimension}: ${value}`); } updateWorkflowWithCurrentDimensions() { diff --git a/web/core/js/common/components/Seeder.js b/web/core/js/common/components/Seeder.js index 6c5ba35..dac87ac 100644 --- a/web/core/js/common/components/Seeder.js +++ b/web/core/js/common/components/Seeder.js @@ -39,9 +39,11 @@ class Seeder { const html = ` ${this.config.label} - - - +
+ + + +
`; this.container.innerHTML = html; diff --git a/web/core/js/common/components/canvas/CanvasControlsPlugin.js b/web/core/js/common/components/canvas/CanvasControlsPlugin.js index 831ac8c..f673f58 100644 --- a/web/core/js/common/components/canvas/CanvasControlsPlugin.js +++ b/web/core/js/common/components/canvas/CanvasControlsPlugin.js @@ -7,15 +7,21 @@ export class CanvasControlsPlugin extends CanvasPlugin { this.zoomIn = this.zoomIn.bind(this); this.zoomOut = this.zoomOut.bind(this); this.resetZoom = this.resetZoom.bind(this); + this.togglePanMode = this.togglePanMode.bind(this); this.onMouseDown = this.onMouseDown.bind(this); this.onMouseMove = this.onMouseMove.bind(this); this.onMouseUp = this.onMouseUp.bind(this); - this.onAfterRender = this.onAfterRender.bind(this); + this.onKeyDown = this.onKeyDown.bind(this); + this.onKeyUp = this.onKeyUp.bind(this); + this.onAfterRender = this.onAfterRender.bind(this); this.arraysEqual = this.arraysEqual.bind(this); - + this.onMaskActivated = this.onMaskActivated.bind(this); + this.onMaskDeactivated = this.onMaskDeactivated.bind(this); this.canvasManager = null; this.canvas = null; this.isPanning = false; + this.isPanMode = false; + this.isAltPan = false; this.lastPosX = 0; this.lastPosY = 0; this.lastTransform = null; @@ -30,6 +36,7 @@ export class CanvasControlsPlugin extends CanvasPlugin { this.attachEventListeners(); this.lastTransform = this.canvas.viewportTransform.slice(); + } createUI() { @@ -44,7 +51,7 @@ export class CanvasControlsPlugin extends CanvasPlugin { display: inline-flex; gap: 0.5rem; padding: 0.5rem; - /* background: var(--color-background); */ + /* background: var(--color-background); */ /* border: 1px dashed var(--color-border); */ user-select: none; } @@ -83,7 +90,7 @@ export class CanvasControlsPlugin extends CanvasPlugin { display: none; } #panBtn { - display: none; + } `; document.head.appendChild(styleSheet); @@ -91,32 +98,51 @@ export class CanvasControlsPlugin extends CanvasPlugin { const temp = document.createElement('div'); temp.innerHTML = `
- - -
+
-
`; @@ -134,8 +160,10 @@ export class CanvasControlsPlugin extends CanvasPlugin { console.warn('Element with id "pluginUIContainer" not found.'); } - this.updatePanButtonState = (isPanning) => { - this.panBtn.dataset.active = isPanning; + this.panBtn.dataset.active = this.isPanMode; + + this.updatePanButtonState = () => { + this.panBtn.dataset.active = this.isPanMode || this.isAltPan; }; } @@ -143,6 +171,9 @@ export class CanvasControlsPlugin extends CanvasPlugin { this.zoomInBtn.addEventListener('click', this.zoomIn); this.zoomOutBtn.addEventListener('click', this.zoomOut); this.resetZoomBtn.addEventListener('click', this.resetZoom); + this.panBtn.addEventListener('click', this.togglePanMode); + + this.canvas.on('mouse:down', this.onMouseDown); this.canvas.on('mouse:move', this.onMouseMove); @@ -150,12 +181,19 @@ export class CanvasControlsPlugin extends CanvasPlugin { this.canvas.on('mouse:out', this.onMouseUp); this.canvas.on('after:render', this.onAfterRender); + + this.canvasManager.on('mask:activated', this.onMaskActivated); + this.canvasManager.on('mask:deactivated', this.onMaskDeactivated); + + document.addEventListener('keydown', this.onKeyDown); + document.addEventListener('keyup', this.onKeyUp); } detachEventListeners() { this.zoomInBtn.removeEventListener('click', this.zoomIn); this.zoomOutBtn.removeEventListener('click', this.zoomOut); this.resetZoomBtn.removeEventListener('click', this.resetZoom); + this.panBtn.removeEventListener('click', this.togglePanMode); this.canvas.off('mouse:down', this.onMouseDown); this.canvas.off('mouse:move', this.onMouseMove); @@ -163,6 +201,12 @@ export class CanvasControlsPlugin extends CanvasPlugin { this.canvas.off('mouse:out', this.onMouseUp); this.canvas.off('after:render', this.onAfterRender); + + this.canvasManager.off('mask:activated', this.onMaskActivated); + this.canvasManager.off('mask:deactivated', this.onMaskDeactivated); + + document.removeEventListener('keydown', this.onKeyDown); + document.removeEventListener('keyup', this.onKeyUp); } arraysEqual(a, b) { @@ -173,6 +217,14 @@ export class CanvasControlsPlugin extends CanvasPlugin { return true; } + onMaskActivated() { + this.isPanMode = false; + this.updatePanButtonState(); + } + + onMaskDeactivated() { + } + onAfterRender() { const currentTransform = this.canvas.viewportTransform; if (!this.arraysEqual(currentTransform, this.lastTransform)) { @@ -193,6 +245,7 @@ export class CanvasControlsPlugin extends CanvasPlugin { zoomOut() { let zoom = this.canvas.getZoom(); zoom /= 1.1; + if (zoom < 0.1) zoom = 0.1; this.canvas.zoomToPoint({ x: this.canvas.width / 2, y: this.canvas.height / 2 }, zoom); } @@ -200,13 +253,46 @@ export class CanvasControlsPlugin extends CanvasPlugin { this.canvas.setViewportTransform([1, 0, 0, 1, 0, 0]); } + togglePanMode() { + this.isPanMode = !this.isPanMode; + this.updatePanButtonState(); + + if (this.isPanMode) { + this.canvas.setCursor('grab'); + this.canvasManager.emit('pan:activated'); + } else { + this.canvas.setCursor('default'); + this.canvasManager.emit('pan:deactivated'); + } + } + + onKeyDown(e) { + if (e.altKey && !this.isAltPan) { + this.isAltPan = true; + this.updatePanButtonState(); + this.canvas.setCursor('grab'); + this.canvasManager.emit('pan:activated'); + } + } + + onKeyUp(e) { + if (!e.altKey && this.isAltPan) { + this.isAltPan = false; + this.updatePanButtonState(); + this.canvas.setCursor(this.isPanMode ? 'grab' : 'default'); + this.canvasManager.emit('pan:deactivated'); + } + } + onMouseDown(opt) { - const evt = opt.e; - if (evt.altKey) { + if (this.isPanMode || this.isAltPan) { this.isPanning = true; + const evt = opt.e; this.lastPosX = evt.clientX; this.lastPosY = evt.clientY; - this.canvas.setCursor('move'); + this.canvas.setCursor('grabbing'); + evt.preventDefault(); + evt.stopPropagation(); } } @@ -226,8 +312,10 @@ export class CanvasControlsPlugin extends CanvasPlugin { } onMouseUp(opt) { - this.isPanning = false; - this.canvas.setCursor('default'); + if (this.isPanning) { + this.isPanning = false; + this.canvas.setCursor(this.isPanMode || this.isAltPan ? 'grab' : 'default'); + } } destroy() { @@ -237,4 +325,4 @@ export class CanvasControlsPlugin extends CanvasPlugin { this.detachEventListeners(); } -} \ No newline at end of file +} diff --git a/web/core/js/common/components/canvas/CanvasLoader.js b/web/core/js/common/components/canvas/CanvasLoader.js index 99ca1b6..160113b 100644 --- a/web/core/js/common/components/canvas/CanvasLoader.js +++ b/web/core/js/common/components/canvas/CanvasLoader.js @@ -7,235 +7,67 @@ import { CustomBrushPlugin } from './CustomBrushPlugin.js'; import { UndoRedoPlugin } from './UndoRedoPlugin.js'; import { ImageAdderPlugin } from './ImageAdderPlugin.js'; import { CanvasScaleForSavePlugin } from './CanvasScaleForSavePlugin.js'; +import { store } from '../../scripts/stateManagerMain.js'; -const loadFabric = () => { +const loadScript = (src, name = '') => { return new Promise((resolve, reject) => { const script = document.createElement('script'); - script.src = '/core/js/common/components/canvas/fabric.5.2.4.min.js'; + script.src = src; script.async = false; script.onload = () => { - console.log('Fabric.js loaded successfully'); + // console.log(`${name || src} loaded successfully`); resolve(); }; script.onerror = () => { - console.error('Failed to load Fabric.js'); - reject(new Error('Fabric.js failed to load')); + const error = `Failed to load ${name || src}`; + console.error(error); + reject(new Error(error)); }; document.head.appendChild(script); }); }; -// Floating toolbar functionality - move -const styleSheet = document.createElement('style'); -styleSheet.textContent = ` - .plugin-ui-container { - position: relative; - display: flex; - gap: 0.5rem; - padding: 0.5rem; - padding-right: calc(0.5rem + 36px); - transition: background 0.3s ease, border 0.3s ease, box-shadow 0.3s ease; - will-change: transform; - } - - .plugin-ui-container.floating { - position: fixed; - bottom: 25%; - /* left: 16%px; */ - z-index: 1000; - background: var(--color-background); - border: 1px dashed var(--color-border); - cursor: move; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - } - - .dock-toggle { - position: absolute; - right: 0; - top: 0; - height: 100%; - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0.5rem; - background: var(--color-button-primary); - border: none; - border-left: 1px dashed var(--color-border); - cursor: pointer; - transition: all 0.2s; - color: var(--color-primary-text); - width: 36px; - } - - .plugin-ui-container.floating .dock-toggle { - position: relative; - height: 36px; - margin-left: 0.5rem; - border: none; - } - - .dock-toggle:hover { - background: var(--color-button-primary-hover); - } - - .dock-toggle svg { - width: 1.25rem; - height: 1.25rem; - transition: transform 0.2s ease; - } - - .dock-toggle:hover svg { - transform: scale(1.1); - } - - .plugin-ui-container.floating .dock-toggle:hover svg { - transform: scale(1.1); - } -`; -document.head.appendChild(styleSheet); - -function initializeFloating() { - const container = document.getElementById('pluginUIContainer'); - const dockButton = document.createElement('button'); - dockButton.className = 'dock-toggle'; - dockButton.title = 'Make toolbar floating'; - dockButton.innerHTML = ` - - - - `; - container.appendChild(dockButton); - - let isDragging = false; - let currentX; - let currentY; - let initialX; - let initialY; - let xOffset = 0; - let yOffset = 0; - let originalPosition = null; - let lastFrameTime = 0; - const frameRate = 1000 / 120; - - function updateDockIcon(isFloating) { - dockButton.title = isFloating ? 'Dock toolbar' : 'Make toolbar floating'; - dockButton.innerHTML = isFloating - ? ` - - ` - : ` - - `; - } - - function updateDockButtonPosition(isFloating) { - if (isFloating) { - container.appendChild(dockButton); - } else { - container.insertBefore(dockButton, container.firstChild); - } - } - - function startDragging(e) { - if (container.classList.contains('floating') && e.target !== dockButton) { - isDragging = true; - initialX = e.clientX - xOffset; - initialY = e.clientY - yOffset; - container.style.transition = 'none'; - document.body.style.cursor = 'move'; - } +const loadDependencies = async () => { + try { + await Promise.all([ + loadScript('/core/js/common/components/canvas/fabric.5.2.4.min.js', 'Fabric.js'), + // loadScript('/core/js/common/components/canvas/pica.min.js', 'Pica.js') + ]); + // console.log('All dependencies loaded successfully'); + } catch (error) { + console.error('Failed to load dependencies:', error); } +}; - function drag(e) { - if (isDragging) { - e.preventDefault(); - - const currentTime = performance.now(); - if (currentTime - lastFrameTime < frameRate) return; - - currentX = e.clientX - initialX; - currentY = e.clientY - initialY; - xOffset = currentX; - yOffset = currentY; - container.style.transform = `translate3d(${currentX}px, ${currentY}px, 0)`; - - lastFrameTime = currentTime; - } - } +function setView(view) { + const canvasWrapper = document.getElementById("canvasWrapper"); + const imageContainer = document.getElementById("image-container"); - function stopDragging() { - if (isDragging) { - isDragging = false; - container.style.transition = ''; - document.body.style.cursor = ''; - } + if (!canvasWrapper || !imageContainer) { + console.warn("Required elements not found: 'canvasWrapper' or 'image-container'."); + return; } - function toggleFloating() { - const isFloating = container.classList.toggle('floating'); - updateDockIcon(isFloating); - updateDockButtonPosition(isFloating); - - if (isFloating) { - originalPosition = { - parent: container.parentNode, - nextSibling: container.nextSibling, - styles: { - position: container.style.position, - top: container.style.top, - left: container.style.left, - transform: container.style.transform, - zIndex: container.style.zIndex - } - }; - - container.addEventListener('mousedown', startDragging); - window.addEventListener('mousemove', drag); - window.addEventListener('mouseup', stopDragging); - window.addEventListener('mouseleave', stopDragging); - - container.style.transform = `translate3d(${xOffset}px, ${yOffset}px, 0)`; - } else { - container.removeEventListener('mousedown', startDragging); - window.removeEventListener('mousemove', drag); - window.removeEventListener('mouseup', stopDragging); - window.removeEventListener('mouseleave', stopDragging); - - container.style.transform = ''; - container.style.position = ''; - container.style.top = ''; - container.style.left = ''; - container.style.zIndex = ''; - xOffset = 0; - yOffset = 0; - } + switch(view) { + case 'output': + imageContainer.style.display = "flex"; + canvasWrapper.style.display = "none"; + break; + case 'canvas': + canvasWrapper.style.display = "flex"; + imageContainer.style.display = "none"; + break; + case 'splitView': + canvasWrapper.style.display = "flex"; + imageContainer.style.display = "flex"; + break; + default: + console.warn(`Unknown view state: ${view}. Defaulting to 'splitView'.`); + canvasWrapper.style.display = "flex"; + imageContainer.style.display = "flex"; } - - dockButton.addEventListener('click', toggleFloating); - - container.addEventListener('selectstart', (e) => { - if (isDragging) e.preventDefault(); - }); - - return function cleanup() { - container.removeEventListener('mousedown', startDragging); - window.removeEventListener('mousemove', drag); - window.removeEventListener('mouseup', stopDragging); - window.removeEventListener('mouseleave', stopDragging); - dockButton.removeEventListener('click', toggleFloating); - container.removeEventListener('selectstart', (e) => { - if (isDragging) e.preventDefault(); - }); - if (dockButton.parentNode) { - dockButton.parentNode.removeChild(dockButton); - } - }; } -const cleanupFloating = initializeFloating(); -// Floating toolbar functionality + export class CanvasLoader { constructor(canvasId, flowConfig) { @@ -243,6 +75,12 @@ export class CanvasLoader { this.flowConfig = flowConfig; this.isInitialized = false; this.initPromise = this.init(); + + store.subscribe((state) => { + setView(state.view); + }); + + setView(this.flowConfig.initialView || store.getState().view); } determineCanvasOptions(flowConfig) { @@ -259,9 +97,17 @@ export class CanvasLoader { // **Case 1**: Load maskBrush if canvasLoadedImages and canvasSelectedMaskOutputs are present if (flowConfig.canvasLoadedImages && Array.isArray(flowConfig.canvasLoadedImages) && flowConfig.canvasLoadedImages.length > 0 && - flowConfig.canvasSelectedMaskOutputs && Array.isArray(flowConfig.canvasSelectedMaskOutputs) && flowConfig.canvasSelectedMaskOutputs.length > 0) { + flowConfig.canvasAlphaOutputs && Array.isArray(flowConfig.canvasAlphaOutputs) && flowConfig.canvasAlphaOutputs.length > 0) { options.maskBrush = true; options.imageLoader = true; + store.dispatch({ + type: 'SET_VIEW', + payload: 'canvas' + }); + store.dispatch({ + type: 'SET_INPAINT_STYLE', + payload: 'full' + }); } // **Case 2**: Load customBrush if canvasOutputs are present @@ -269,10 +115,54 @@ export class CanvasLoader { options.customBrush = true; options.imageLoader = true; options.canvasScaleForSave = true; - - + store.dispatch({ + type: 'SET_VIEW', + payload: 'splitView' + }); + } + // // **Case 3**: Load canvasCroppedImageOutputs if canvasCroppedMaskOutputs are present + // if (flowConfig.canvasCroppedMaskOutputs && Array.isArray(flowConfig.canvasCroppedMaskOutputs) && flowConfig.canvasCroppedMaskOutputs.length > 0) { + // options.maskBrush = true; + // options.imageLoader = true; + // store.dispatch({ + // type: 'SET_VIEW', + // payload: 'canvas' + // }); + // } + + //MASK + // **Case 4**: Load canvasCroppedImageOutputs and canvasCroppedMaskOutputs and canvasLoadedImages are present + if (flowConfig.canvasCroppedImageOutputs && Array.isArray(flowConfig.canvasCroppedImageOutputs) && flowConfig.canvasCroppedImageOutputs.length > 0 && + flowConfig.canvasCroppedMaskOutputs && Array.isArray(flowConfig.canvasCroppedMaskOutputs) && flowConfig.canvasCroppedMaskOutputs.length > 0 && + flowConfig.canvasLoadedImages && Array.isArray(flowConfig.canvasLoadedImages) && flowConfig.canvasLoadedImages.length > 0) { + options.maskBrush = true; + options.imageLoader = true; + store.dispatch({ + type: 'SET_VIEW', + payload: 'canvas' + }); + store.dispatch({ + type: 'SET_INPAINT_STYLE', + payload: 'cropped' + }); + + } + //ALPHA MASK + // **Case 5**: Load canvasCroppedImageOutputs and canvasCroppedAlphaOnImageOutputs and canvasLoadedImages are present + if (flowConfig.canvasCroppedImageOutputs && Array.isArray(flowConfig.canvasCroppedImageOutputs) && flowConfig.canvasCroppedImageOutputs.length > 0 && + flowConfig.canvasCroppedAlphaOnImageOutputs && Array.isArray(flowConfig.canvasCroppedAlphaOnImageOutputs) && flowConfig.canvasCroppedAlphaOnImageOutputs.length > 0 && + flowConfig.canvasLoadedImages && Array.isArray(flowConfig.canvasLoadedImages) && flowConfig.canvasLoadedImages.length > 0) { + options.maskBrush = true; + options.imageLoader = true; + store.dispatch({ + type: 'SET_VIEW', + payload: 'canvas' + }); + store.dispatch({ + type: 'SET_INPAINT_STYLE', + payload: 'cropped' + }); } - return options; } @@ -282,13 +172,14 @@ export class CanvasLoader { if (!this.options.maskBrush && !this.options.customBrush) { - console.warn("No relevant fields in flowConfig. Canvas will not be displayed or initialized."); + console.log("No relevant fields in flowConfig. Canvas will not be displayed or initialized."); this.hideCanvasUI(); return; } - await loadFabric(); - + // await loadFabric(); + // loadScript('/core/js/common/components/canvas/fabric.5.2.4.min.js') + await loadDependencies(); const canvasWrapper = document.getElementById('canvasWrapper'); const pluginUIContainer = document.getElementById('pluginUIContainer'); @@ -297,7 +188,7 @@ export class CanvasLoader { } canvasWrapper.style.display = 'block'; - pluginUIContainer.style.display = 'block'; + pluginUIContainer.style.display = 'flex'; this.canvasManager = new CanvasManager({ canvasId: this.canvasId }); @@ -437,6 +328,48 @@ export class CanvasLoader { return null; } + getCroppedMask(){ + const maskBrushPlugin = this.canvasManager.getPluginByName('MaskBrushPlugin'); + if (maskBrushPlugin) { + const exportFunction = maskBrushPlugin.getExportFunction('saveCroppedMask'); + if (exportFunction) { + return exportFunction(); + } else { + console.error('Failed to get export function for saveCroppedMask.'); + return null; + } + } + return null; + } + + getCroppedImage(){ + const maskBrushPlugin = this.canvasManager.getPluginByName('MaskBrushPlugin'); + if (maskBrushPlugin) { + const exportFunction = maskBrushPlugin.getExportFunction('saveCroppedImage'); + if (exportFunction) { + return exportFunction(); + } else { + console.error('Failed to get export function for saveCroppedImage.'); + return null; + } + } + return null; + } + + getCroppedAlphaOnImage(){ + const maskBrushPlugin = this.canvasManager.getPluginByName('MaskBrushPlugin'); + if (maskBrushPlugin) { + const exportFunction = maskBrushPlugin.getExportFunction('saveCroppedAlphaOnImage'); + if (exportFunction) { + return exportFunction(); + } else { + console.error('Failed to get export function for saveCroppedAlphaOnImage.'); + return null; + } + } + return null; + } + getSelectedMaskAlphaOnImage() { const maskBrushPlugin = this.canvasManager.getPluginByName('MaskBrushPlugin'); if (maskBrushPlugin) { diff --git a/web/core/js/common/components/canvas/CustomBrushPlugin.js b/web/core/js/common/components/canvas/CustomBrushPlugin.js index d7f8681..805628e 100644 --- a/web/core/js/common/components/canvas/CustomBrushPlugin.js +++ b/web/core/js/common/components/canvas/CustomBrushPlugin.js @@ -1,4 +1,5 @@ import { CanvasPlugin } from './CanvasPlugin.js'; +import { store } from '../../scripts/stateManagerMain.js'; export class CustomBrushPlugin extends CanvasPlugin { constructor(options = {}) { @@ -74,6 +75,7 @@ export class CustomBrushPlugin extends CanvasPlugin { this.onMinimize = this.onMinimize.bind(this); this.onModeChange = this.onModeChange.bind(this); this.brushStrokeIdCounter = 0; + this.disableDrawingMode = this.disableDrawingMode.bind(this); } init(canvasManager) { @@ -165,8 +167,8 @@ export class CustomBrushPlugin extends CanvasPlugin { Toggle Drawing Mode - + - -
+ + - - + + +
+ + - - +