mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-22 23:02:23 +02:00
Squash Odysseus development history
This commit is contained in:
Vendored
+269
-2
@@ -12,6 +12,15 @@
|
||||
export function controlsHTML({ color, brushSize, wandTolerance }) {
|
||||
const brushSliderValue = Math.round(Math.log(Math.max(1, brushSize)) / Math.log(800) * 1000);
|
||||
return `
|
||||
<div class="ge-layer-geometry-section" id="ge-layer-geometry-section">
|
||||
<div class="ge-section-title"><span>Position</span><span id="ge-layer-geometry-name"></span></div>
|
||||
<div class="ge-layer-geometry-grid">
|
||||
<label><span>X</span><input id="ge-layer-x" type="number" step="1" inputmode="numeric" /></label>
|
||||
<label><span>Y</span><input id="ge-layer-y" type="number" step="1" inputmode="numeric" /></label>
|
||||
<label><span>W</span><input id="ge-layer-width" type="number" readonly title="Edit width with Transform" /></label>
|
||||
<label><span>H</span><input id="ge-layer-height" type="number" readonly title="Edit height with Transform" /></label>
|
||||
</div>
|
||||
</div>
|
||||
<div id="ge-brush-controls">
|
||||
<div class="ge-control-row" id="ge-color-row">
|
||||
<label>Color</label>
|
||||
@@ -20,9 +29,204 @@ export function controlsHTML({ color, brushSize, wandTolerance }) {
|
||||
<div class="ge-control-row">
|
||||
<label>Size <span class="ge-size-label">${brushSize}px</span></label>
|
||||
<input type="range" class="ge-size-slider" min="0" max="1000" value="${brushSliderValue}" />
|
||||
</div>
|
||||
<div class="ge-section-title">Stroke</div>
|
||||
<div class="ge-control-row ge-eraser-row">
|
||||
<label>Spacing <span id="ge-brush-spacing-label">15%</span></label>
|
||||
<input type="range" id="ge-brush-spacing" min="1" max="100" value="15" />
|
||||
</div>
|
||||
<div class="ge-control-row ge-eraser-row">
|
||||
<label>Smoothing <span id="ge-brush-smoothing-label">25%</span></label>
|
||||
<input type="range" id="ge-brush-smoothing" min="0" max="95" value="25" />
|
||||
</div>
|
||||
<div class="ge-control-row">
|
||||
<label for="ge-brush-blend">Blend</label>
|
||||
<select id="ge-brush-blend">
|
||||
<option value="source-over">Normal</option><option value="multiply">Multiply</option>
|
||||
<option value="screen">Screen</option><option value="overlay">Overlay</option>
|
||||
<option value="soft-light">Soft Light</option><option value="color">Color</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ge-control-row ge-brush-pressure-row">
|
||||
<span class="ge-pressure-option" title="Pen pressure changes brush size"><span>Size</span><label class="toggle-switch"><input type="checkbox" id="ge-pressure-size" checked /><span class="toggle-slider"></span></label></span>
|
||||
<span class="ge-pressure-option" title="Pen pressure changes opacity"><span>Opacity</span><label class="toggle-switch"><input type="checkbox" id="ge-pressure-opacity" /><span class="toggle-slider"></span></label></span>
|
||||
<span class="ge-pressure-option" title="Pen pressure changes flow"><span>Flow</span><label class="toggle-switch"><input type="checkbox" id="ge-pressure-flow" checked /><span class="toggle-slider"></span></label></span>
|
||||
</div>
|
||||
<div class="ge-control-row ge-brush-preset-row">
|
||||
<select id="ge-brush-preset" aria-label="Brush preset"></select>
|
||||
<button type="button" class="ge-btn ge-btn-sm" id="ge-brush-preset-save" title="Save current brush preset">Save</button>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-icon-btn" id="ge-brush-preset-delete" title="Delete selected preset" aria-label="Delete selected preset">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ge-gradient-section" id="ge-gradient-section" style="display:none;">
|
||||
<div class="ge-section-title">Gradient</div>
|
||||
<div class="ge-control-row"><label for="ge-gradient-type">Type</label><select id="ge-gradient-type"><option value="linear-gradient">Linear</option><option value="radial-gradient">Radial</option></select></div>
|
||||
<div class="ge-text-grid">
|
||||
<label class="ge-text-field"><span>Start</span><input type="color" class="ge-color-picker" id="ge-gradient-start" value="${color}" /></label>
|
||||
<label class="ge-text-field"><span>End</span><input type="color" class="ge-color-picker" id="ge-gradient-end" value="#ffffff" /></label>
|
||||
</div>
|
||||
<div class="ge-control-row ge-gradient-mid-row">
|
||||
<span class="ge-gradient-mid-toggle"><span>Midpoint</span><label class="toggle-switch"><input type="checkbox" id="ge-gradient-mid-enabled" /><span class="toggle-slider"></span></label></span>
|
||||
<input type="color" class="ge-color-picker" id="ge-gradient-mid" value="#808080" title="Midpoint color" disabled />
|
||||
<input type="range" id="ge-gradient-mid-position" min="1" max="99" value="50" title="Midpoint position" disabled />
|
||||
<span id="ge-gradient-mid-position-label">50%</span>
|
||||
</div>
|
||||
<div class="ge-gradient-extra-stops" id="ge-gradient-extra-stops"></div>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-gradient-add-stop" id="ge-gradient-add-stop">Add stop</button>
|
||||
<div class="ge-control-row"><label for="ge-gradient-end-alpha">End opacity <span id="ge-gradient-end-alpha-label">100%</span></label><input type="range" id="ge-gradient-end-alpha" min="0" max="100" value="100" /></div>
|
||||
<div class="ge-control-row"><label for="ge-gradient-opacity">Opacity <span id="ge-gradient-opacity-label">100%</span></label><input type="range" id="ge-gradient-opacity" min="0" max="100" value="100" /></div>
|
||||
<p class="ge-section-hint">Drag across the active layer. Switch tools or press Esc to cancel.</p>
|
||||
</div>
|
||||
<div class="ge-eraser-section" id="ge-eyedropper-section" style="display:none;">
|
||||
<div class="ge-section-title">Eyedropper</div>
|
||||
<div class="ge-eyedropper-live" id="ge-eyedropper-live" aria-live="polite">
|
||||
<canvas class="ge-eyedropper-loupe" id="ge-eyedropper-loupe" width="84" height="84" aria-hidden="true"></canvas>
|
||||
<span class="ge-eyedropper-live-swatch" id="ge-eyedropper-live-swatch" aria-hidden="true"></span>
|
||||
<span class="ge-eyedropper-live-values">
|
||||
<code id="ge-eyedropper-live-value">Move over the canvas</code>
|
||||
<span id="ge-eyedropper-live-rgb">RGB --</span>
|
||||
<span id="ge-eyedropper-live-hsl">HSL --</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="ge-control-row"><label for="ge-eyedropper-sample">Sample</label><select id="ge-eyedropper-sample"><option value="composite">All layers</option><option value="layer">Active layer</option></select></div>
|
||||
</div>
|
||||
<div class="ge-text-section" id="ge-text-section" style="display:none;">
|
||||
<textarea id="ge-text-content" rows="3" placeholder="Type text..." aria-label="Text content"></textarea>
|
||||
<div class="ge-text-grid">
|
||||
<label class="ge-text-field ge-text-font-field"><span>Font</span>
|
||||
<select id="ge-text-font">
|
||||
<option value="Arial">Arial</option>
|
||||
<option value="Verdana">Verdana</option>
|
||||
<option value="Georgia">Georgia</option>
|
||||
<option value="Times New Roman">Times New Roman</option>
|
||||
<option value="Courier New">Courier New</option>
|
||||
<option value="Impact">Impact</option>
|
||||
<option value="system-ui">System</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="ge-text-field ge-text-size-field"><span>Size</span>
|
||||
<input id="ge-text-size" type="number" min="1" max="2000" value="48" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="ge-control-row ge-text-format-row">
|
||||
<input type="color" class="ge-color-picker" id="ge-text-color" value="${color}" title="Text color" aria-label="Text color" />
|
||||
<button type="button" class="ge-text-toggle" id="ge-text-bold" title="Bold" aria-pressed="false"><strong>B</strong></button>
|
||||
<button type="button" class="ge-text-toggle" id="ge-text-italic" title="Italic" aria-pressed="false"><em>I</em></button>
|
||||
<span class="ge-text-format-sep"></span>
|
||||
<button type="button" class="ge-text-toggle active" data-text-align="left" title="Align left" aria-pressed="true">⇤</button>
|
||||
<button type="button" class="ge-text-toggle" data-text-align="center" title="Align center" aria-pressed="false">↔</button>
|
||||
<button type="button" class="ge-text-toggle" data-text-align="right" title="Align right" aria-pressed="false">⇥</button>
|
||||
</div>
|
||||
<div class="ge-text-grid">
|
||||
<label class="ge-text-field"><span>Line</span>
|
||||
<input id="ge-text-line-height" type="number" min="0.5" max="5" step="0.1" value="1.2" />
|
||||
</label>
|
||||
<label class="ge-text-field"><span>Spacing</span>
|
||||
<input id="ge-text-letter-spacing" type="number" min="-100" max="500" step="0.5" value="0" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="ge-text-grid">
|
||||
<label class="ge-text-field"><span>Frame</span>
|
||||
<input id="ge-text-frame-width" type="number" min="1" max="10000" step="1" value="320" />
|
||||
</label>
|
||||
<label class="ge-text-field"><span>Height</span>
|
||||
<input id="ge-text-frame-height" type="number" min="0" max="10000" step="1" value="0" />
|
||||
</label>
|
||||
<label class="ge-text-field"><span>Vertical</span>
|
||||
<select id="ge-text-vertical-align"><option value="top">Top</option><option value="middle">Middle</option><option value="bottom">Bottom</option></select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="ge-text-grid">
|
||||
<label class="ge-text-field"><span>Stroke</span>
|
||||
<input id="ge-text-stroke-width" type="number" min="0" max="100" step="1" value="0" />
|
||||
</label>
|
||||
<input type="color" class="ge-color-picker" id="ge-text-stroke-color" value="#000000" title="Stroke color" aria-label="Stroke color" />
|
||||
</div>
|
||||
<label class="ge-text-auto-width-option"><span>Auto width</span><span class="toggle-switch"><input type="checkbox" id="ge-text-auto-width" /><span class="toggle-slider"></span></span></label>
|
||||
<button type="button" class="ge-btn ge-btn-sm" id="ge-text-rasterize">Rasterize</button>
|
||||
</div>
|
||||
<div class="ge-shape-section" id="ge-shape-section" style="display:none;">
|
||||
<div class="ge-section-title">Shape</div>
|
||||
<div class="ge-shape-types" role="group" aria-label="Shape type">
|
||||
<button type="button" class="ge-text-toggle active" data-shape-type="rectangle" title="Rectangle" aria-pressed="true"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="5" width="16" height="14" rx="1"/></svg></button>
|
||||
<button type="button" class="ge-text-toggle" data-shape-type="ellipse" title="Ellipse" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="12" rx="8" ry="7"/></svg></button>
|
||||
<button type="button" class="ge-text-toggle" data-shape-type="line" title="Line" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="4" y1="19" x2="20" y2="5"/></svg></button>
|
||||
<button type="button" class="ge-text-toggle" data-shape-type="polygon" title="Polygon" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m12 3 9 7-3.5 11h-11L3 10Z"/></svg></button>
|
||||
</div>
|
||||
<div class="ge-text-grid">
|
||||
<label class="ge-text-field"><span>Fill</span><input type="color" class="ge-color-picker" id="ge-shape-fill" value="${color}" /></label>
|
||||
<label class="ge-text-field"><span>Stroke</span><input type="color" class="ge-color-picker" id="ge-shape-stroke" value="#111111" /></label>
|
||||
</div>
|
||||
<div class="ge-text-grid">
|
||||
<label class="ge-text-field"><span>Fill type</span>
|
||||
<select id="ge-shape-fill-type"><option value="solid">Solid</option><option value="linear-gradient">Linear gradient</option></select>
|
||||
</label>
|
||||
<label class="ge-text-field ge-shape-gradient-angle-field" hidden><span>Angle</span><input id="ge-shape-gradient-angle" type="number" min="-36000" max="36000" step="1" value="0" /></label>
|
||||
</div>
|
||||
<div class="ge-text-grid ge-shape-gradient-fields" hidden>
|
||||
<label class="ge-text-field"><span>Start</span><input type="color" class="ge-color-picker" id="ge-shape-gradient-start" value="#ffffff" /></label>
|
||||
<label class="ge-text-field"><span>End</span><input type="color" class="ge-color-picker" id="ge-shape-gradient-end" value="#000000" /></label>
|
||||
<label class="ge-text-field"><span>Midpoint</span><input type="color" class="ge-color-picker" id="ge-shape-gradient-mid" value="#808080" /></label>
|
||||
<label class="ge-text-field"><span>Position</span><input id="ge-shape-gradient-mid-position" type="number" min="1" max="99" step="1" value="50" /></label>
|
||||
<label class="ge-text-field ge-shape-gradient-mid-enabled"><span>Use midpoint</span><input id="ge-shape-gradient-mid-enabled" type="checkbox" /></label>
|
||||
</div>
|
||||
<div class="ge-shape-gradient-extra-stops" id="ge-shape-gradient-extra-stops"></div>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-shape-gradient-add-stop" id="ge-shape-gradient-add-stop">Add stop</button>
|
||||
<div class="ge-text-grid">
|
||||
<label class="ge-text-field"><span>Width</span><input id="ge-shape-stroke-width" type="number" min="0" max="500" step="1" value="2" /></label>
|
||||
<label class="ge-text-field"><span>Radius</span><input id="ge-shape-radius" type="number" min="0" max="5000" step="1" value="0" /></label>
|
||||
</div>
|
||||
<label class="ge-text-field ge-shape-sides-field" hidden><span>Sides</span><input id="ge-shape-sides" type="number" min="3" max="24" step="1" value="5" /></label>
|
||||
<button type="button" class="ge-btn ge-btn-sm" id="ge-shape-rasterize">Rasterize</button>
|
||||
</div>
|
||||
<div class="ge-marquee-section" id="ge-marquee-section" style="display:none;">
|
||||
<div class="ge-control-row" style="display:flex;gap:4px;margin-bottom:4px;">
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-marquee-shape-btn active" data-marquee-shape="rectangle" title="Rectangular marquee" aria-pressed="true">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="5" width="16" height="14"/></svg>
|
||||
</button>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-marquee-shape-btn" data-marquee-shape="ellipse" title="Elliptical marquee" aria-pressed="false">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="12" rx="8" ry="7"/></svg>
|
||||
</button>
|
||||
<span style="flex:1"></span>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-wand-mode-btn active" data-wand-mode="replace" title="New selection">New</button>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-wand-mode-btn" data-wand-mode="add" title="Add to selection">+</button>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-wand-mode-btn" data-wand-mode="subtract" title="Subtract from selection">−</button>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-wand-mode-btn" data-wand-mode="intersect" title="Intersect selection">∩</button>
|
||||
</div>
|
||||
<div class="ge-marquee-constraint-row">
|
||||
<label for="ge-marquee-constraint">Style</label>
|
||||
<select id="ge-marquee-constraint" title="Marquee sizing style">
|
||||
<option value="free">Free</option>
|
||||
<option value="ratio">Fixed ratio</option>
|
||||
<option value="size">Fixed size</option>
|
||||
</select>
|
||||
<div class="ge-marquee-dimensions" hidden>
|
||||
<label>W <input type="number" id="ge-marquee-width" min="1" step="1" value="1" inputmode="decimal" /></label>
|
||||
<label>H <input type="number" id="ge-marquee-height" min="1" step="1" value="1" inputmode="decimal" /></label>
|
||||
<button type="button" class="ge-icon-btn" id="ge-marquee-swap" title="Swap width and height" aria-label="Swap width and height">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m16 3 4 4-4 4"/><path d="M4 7h16"/><path d="m8 21-4-4 4-4"/><path d="M20 17H4"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ge-control-row ge-actions" style="margin-top:4px;flex-wrap:wrap;">
|
||||
<button class="ge-btn ge-btn-sm ge-mask-vis-btn visible" id="ge-marquee-vis" title="Hide selection overlay" aria-label="Toggle selection overlay">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
|
||||
</button>
|
||||
<button class="ge-btn ge-btn-sm ge-btn-iconlabel" id="ge-marquee-clear" title="Clear selection">Clear</button>
|
||||
<button class="ge-btn ge-btn-sm ge-btn-iconlabel" id="ge-marquee-invert" title="Invert selection">Invert</button>
|
||||
<button class="ge-btn ge-btn-sm ge-btn-iconlabel" id="ge-marquee-delete" title="Erase selected pixels">Erase</button>
|
||||
<button class="ge-btn ge-btn-sm ge-btn-iconlabel" id="ge-marquee-copy" title="Copy selection to a new layer">Copy Layer</button>
|
||||
<button class="ge-btn ge-btn-sm ge-btn-iconlabel" id="ge-marquee-mask" title="Add selection to mask">To Mask</button>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-btn-iconlabel ge-quick-mask-toggle" title="Edit selection as Quick Mask (Q)" aria-pressed="false">Quick Mask</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ge-lasso-section" id="ge-lasso-section" style="display:none;">
|
||||
<div class="ge-control-row" style="display:flex;gap:4px;margin-bottom:4px;" title="How the next lasso combines with the current selection.">
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-wand-mode-btn active" data-wand-mode="replace" title="New selection">New</button>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-wand-mode-btn" data-wand-mode="add" title="Add to selection">+</button>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-wand-mode-btn" data-wand-mode="subtract" title="Subtract from selection">−</button>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-wand-mode-btn" data-wand-mode="intersect" title="Intersect selection">∩</button>
|
||||
</div>
|
||||
<div class="ge-control-row ge-eraser-row ge-sel-refine" id="ge-lasso-refine-feather" style="display:none;">
|
||||
<span class="ge-eraser-preview" id="ge-lasso-feather-preview" aria-hidden="true"></span>
|
||||
<label>Feather <span id="ge-lasso-feather-label">0px</span></label>
|
||||
@@ -50,6 +254,7 @@ export function controlsHTML({ color, brushSize, wandTolerance }) {
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.06 11.9l8.07-8.06a2.85 2.85 0 1 1 4.03 4.03l-8.06 8.08"/><path d="M7.07 14.94c-1.66 0-3 1.35-3 3.02 0 1.33-2.5 1.52-2 2.02 1.08 1.1 2.49 2.02 4 2.02 2.2 0 4-1.8 4-4.04a3.01 3.01 0 0 0-3-3.02z"/></svg>
|
||||
To Mask
|
||||
</button>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-btn-iconlabel ge-quick-mask-toggle" title="Edit selection as Quick Mask (Q)" aria-pressed="false">Quick Mask</button>
|
||||
</div>
|
||||
<p style="font-size:9px;opacity:0.4;margin:4px 0 0;">Draw a freehand selection. Esc to cancel.</p>
|
||||
</div>
|
||||
@@ -58,6 +263,7 @@ export function controlsHTML({ color, brushSize, wandTolerance }) {
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-wand-mode-btn active" data-wand-mode="replace" title="Replace selection on each click">New</button>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-wand-mode-btn" data-wand-mode="add" title="Add to selection (Shift)">+ Add</button>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-wand-mode-btn" data-wand-mode="subtract" title="Subtract from selection (Alt)">− Subtract</button>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-wand-mode-btn" data-wand-mode="intersect" title="Intersect selection">∩</button>
|
||||
</div>
|
||||
<div class="ge-control-row ge-eraser-row">
|
||||
<span class="ge-eraser-preview" id="ge-wand-tol-preview" aria-hidden="true"></span>
|
||||
@@ -99,6 +305,7 @@ export function controlsHTML({ color, brushSize, wandTolerance }) {
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.06 11.9l8.07-8.06a2.85 2.85 0 1 1 4.03 4.03l-8.06 8.08"/><path d="M7.07 14.94c-1.66 0-3 1.35-3 3.02 0 1.33-2.5 1.52-2 2.02 1.08 1.1 2.49 2.02 4 2.02 2.2 0 4-1.8 4-4.04a3.01 3.01 0 0 0-3-3.02z"/></svg>
|
||||
To Mask
|
||||
</button>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-btn-iconlabel ge-quick-mask-toggle" title="Edit selection as Quick Mask (Q)" aria-pressed="false">Quick Mask</button>
|
||||
</div>
|
||||
<p style="font-size:9px;opacity:0.4;margin:4px 0 0;">Click a region to select similar pixels. Shift+click to add, Alt+click to subtract. Esc to clear.</p>
|
||||
</div>
|
||||
@@ -108,6 +315,7 @@ export function controlsHTML({ color, brushSize, wandTolerance }) {
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-wand-mode-btn active" data-wand-mode="replace" title="Replace selection">New</button>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-wand-mode-btn" data-wand-mode="add" title="Add to selection">+ Add</button>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-wand-mode-btn" data-wand-mode="subtract" title="Subtract from selection">− Subtract</button>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-wand-mode-btn" data-wand-mode="intersect" title="Intersect selection">∩</button>
|
||||
</div>
|
||||
<div class="ge-control-row" style="display:flex;gap:6px;align-items:center;min-width:0;">
|
||||
<input type="text" class="ge-inpaint-prompt" id="ge-sam-query" placeholder="Object to select..." style="flex:1 1 auto;min-width:0;" />
|
||||
@@ -128,6 +336,7 @@ export function controlsHTML({ color, brushSize, wandTolerance }) {
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.06 11.9l8.07-8.06a2.85 2.85 0 1 1 4.03 4.03l-8.06 8.08"/><path d="M7.07 14.94c-1.66 0-3 1.35-3 3.02 0 1.33-2.5 1.52-2 2.02 1.08 1.1 2.49 2.02 4 2.02 2.2 0 4-1.8 4-4.04a3.01 3.01 0 0 0-3-3.02z"/></svg>
|
||||
To Mask
|
||||
</button>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-btn-iconlabel ge-quick-mask-toggle" title="Edit selection as Quick Mask (Q)" aria-pressed="false">Quick Mask</button>
|
||||
</div>
|
||||
<p style="font-size:9px;opacity:0.4;margin:4px 0 0;">Click an object, or type a neutral object label. Shift adds, Alt subtracts.</p>
|
||||
</div>
|
||||
@@ -230,6 +439,17 @@ export function controlsHTML({ color, brushSize, wandTolerance }) {
|
||||
<p class="ge-section-hint" style="margin-top:0;">
|
||||
<strong class="ge-clone-hint-desktop">Alt-click</strong><strong class="ge-clone-hint-mobile">Double-tap</strong> to set source · drag to paint
|
||||
</p>
|
||||
<div class="ge-clone-source-status" id="ge-clone-source-status" aria-live="polite">
|
||||
<span id="ge-clone-source-label">No source selected</span>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-icon-btn" id="ge-clone-source-clear" title="Clear sampled source" aria-label="Clear sampled source">×</button>
|
||||
</div>
|
||||
<div class="ge-control-row">
|
||||
<label for="ge-clone-sample-mode">Sample</label>
|
||||
<select id="ge-clone-sample-mode">
|
||||
<option value="active-layer">Active layer</option>
|
||||
<option value="composite">All visible layers</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ge-control-row ge-eraser-row">
|
||||
<span class="ge-eraser-preview" id="ge-clone-preview-opacity" aria-hidden="true"></span>
|
||||
<label>Opacity <span id="ge-clone-opacity-label">100%</span></label>
|
||||
@@ -247,7 +467,12 @@ export function controlsHTML({ color, brushSize, wandTolerance }) {
|
||||
</div>
|
||||
</div>
|
||||
<div class="ge-eraser-section" id="ge-brush-section" style="display:none;">
|
||||
<div class="ge-section-title">Brush</div>
|
||||
<div class="ge-section-title"><span>Brush</span></div>
|
||||
<div class="ge-control-row ge-eraser-row" id="ge-smudge-strength-row" style="display:none;">
|
||||
<span class="ge-eraser-preview" id="ge-smudge-preview-strength" aria-hidden="true"></span>
|
||||
<label>Strength <span id="ge-smudge-strength-label">65%</span></label>
|
||||
<input type="range" id="ge-smudge-strength" min="5" max="100" value="65" title="How strongly Smudge carries sampled pixels into the stroke." />
|
||||
</div>
|
||||
<div class="ge-control-row ge-eraser-row">
|
||||
<span class="ge-eraser-preview" id="ge-brush-preview-opacity" aria-hidden="true"></span>
|
||||
<label>Opacity <span id="ge-brush-opacity-label">100%</span></label>
|
||||
@@ -396,6 +621,48 @@ export function layerPanelHTML() {
|
||||
<button class="ge-btn ge-btn-sm ge-icon-btn" id="ge-flatten" title="Flatten copy (keeps originals)" aria-label="Flatten copy">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2 L4 6 L4 18 L12 22 L20 18 L20 6 Z"/><path d="M12 2 L12 22"/><path d="M4 6 L20 6"/><path d="M4 18 L20 18"/></svg>
|
||||
</button>
|
||||
<button class="ge-btn ge-btn-sm ge-icon-btn" id="ge-select-all-layers" title="Select all layers" aria-label="Select all layers">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="7" y="7" width="13" height="13" rx="1"/><path d="M4 16H3V3h13v1"/></svg>
|
||||
</button>
|
||||
<button class="ge-btn ge-btn-sm ge-icon-btn" id="ge-group-selected" title="Group selected layers" aria-label="Group selected layers">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h7l2 2h9v10H3z"/></svg>
|
||||
</button>
|
||||
<button class="ge-btn ge-btn-sm" id="ge-add-layer" title="Add empty layer">+ Add</button>
|
||||
</div><div class="ge-layers-list" id="ge-layers-list"></div>`;
|
||||
</div>
|
||||
<div class="ge-layer-blend-row">
|
||||
<label for="ge-layer-blend">Blend</label>
|
||||
<select id="ge-layer-blend" title="Active layer blend mode">
|
||||
<option value="source-over">Normal</option>
|
||||
<option value="multiply">Multiply</option>
|
||||
<option value="screen">Screen</option>
|
||||
<option value="overlay">Overlay</option>
|
||||
<option value="soft-light">Soft Light</option>
|
||||
<option value="hard-light">Hard Light</option>
|
||||
<option value="darken">Darken</option>
|
||||
<option value="lighten">Lighten</option>
|
||||
<option value="color-dodge">Color Dodge</option>
|
||||
<option value="color-burn">Color Burn</option>
|
||||
<option value="difference">Difference</option>
|
||||
<option value="exclusion">Exclusion</option>
|
||||
<option value="hue">Hue</option>
|
||||
<option value="saturation">Saturation</option>
|
||||
<option value="color">Color</option>
|
||||
<option value="luminosity">Luminosity</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="ge-layer-selection-bar" id="ge-layer-selection-bar" hidden>
|
||||
<span id="ge-layer-selection-count">2 layers</span>
|
||||
<button class="ge-icon-btn" id="ge-selected-visibility" title="Toggle selected visibility" aria-label="Toggle selected visibility">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
|
||||
</button>
|
||||
<button class="ge-icon-btn" id="ge-selected-lock" title="Toggle selected lock" aria-label="Toggle selected lock">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="10" width="16" height="11" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg>
|
||||
</button>
|
||||
<button class="ge-icon-btn" id="ge-selected-align" title="Align or distribute selected layers" aria-label="Align or distribute selected layers">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M4 5h16M7 12h10M4 19h16"/><path d="M12 3v18"/></svg>
|
||||
</button>
|
||||
<button class="ge-icon-btn danger" id="ge-selected-delete" title="Delete selected layers" aria-label="Delete selected layers">×</button>
|
||||
</div>
|
||||
<div class="ge-layers-list" id="ge-layers-list"></div>
|
||||
<div class="ge-layer-tools" id="ge-layer-tools" aria-label="Selected layer tools"></div>`;
|
||||
}
|
||||
|
||||
@@ -22,14 +22,15 @@ export function shortcutsPopupHTML() {
|
||||
<div class="ge-shortcuts-col">
|
||||
<h5>Tools</h5>
|
||||
<div><kbd>V</kbd> Move</div>
|
||||
<div><kbd>T</kbd> Transform</div>
|
||||
<div><kbd>H</kbd> Hand <span style="opacity:0.5">(hold Space temporarily)</span></div>
|
||||
<div><kbd>Arrow</kbd> Move 1 px <span style="opacity:0.5">(Shift = 10 px)</span></div>
|
||||
<div><kbd>T</kbd> Text</div>
|
||||
<div><kbd>B</kbd> Brush</div>
|
||||
<div><kbd>E</kbd> Eraser</div>
|
||||
<div><kbd>K</kbd> Clone Stamp <span style="opacity:0.5">(Alt-click = set source)</span></div>
|
||||
<div><kbd>L</kbd> Lasso</div>
|
||||
<div><kbd>W</kbd> Wand</div>
|
||||
<div><kbd>M</kbd> Inpaint</div>
|
||||
<div><kbd>E</kbd> Eraser</div>
|
||||
<div><kbd>C</kbd> Crop</div>
|
||||
<div><kbd>S</kbd> Sharpen</div>
|
||||
</div>
|
||||
@@ -40,6 +41,7 @@ export function shortcutsPopupHTML() {
|
||||
<div><kbd>Ctrl</kbd>+<kbd>S</kbd> Save</div>
|
||||
<div><kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>S</kbd> Save to Gallery</div>
|
||||
<div><kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>J</kbd> New Layer</div>
|
||||
<div><kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>G</kbd> Clipping Mask</div>
|
||||
<div><kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>T</kbd> Free Transform</div>
|
||||
<div><kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>T</kbd> Canvas size…</div>
|
||||
</div>
|
||||
@@ -89,8 +91,19 @@ export function historyPanelHTML(historyIcon) {
|
||||
export function canvasSizePromptHTML() {
|
||||
return `
|
||||
<div class="modal-content ge-canvas-prompt">
|
||||
<div class="modal-header"><h4 id="ge-canvas-prompt-title">New canvas</h4></div>
|
||||
<div class="modal-header"><h4 id="ge-canvas-prompt-title">New project</h4></div>
|
||||
<div class="modal-body">
|
||||
<div class="ge-canvas-prompt-section-label"><svg class="ge-canvas-prompt-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2"></rect><path d="M3 9h18M9 3v18"></path></svg><span>Resolution templates</span></div>
|
||||
<div class="ge-canvas-preset-grid" role="group" aria-label="Resolution templates">
|
||||
<button type="button" class="ge-canvas-preset" data-canvas-preset="1024x1024"><svg class="ge-canvas-preset-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><rect x="4" y="4" width="16" height="16" rx="1"></rect></svg><span>Square</span><strong>1024 × 1024</strong></button>
|
||||
<button type="button" class="ge-canvas-preset" data-canvas-preset="1920x1080"><svg class="ge-canvas-preset-icon" width="14" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><rect x="2" y="5" width="20" height="14" rx="1"></rect></svg><span>Landscape</span><strong>1920 × 1080</strong></button>
|
||||
<button type="button" class="ge-canvas-preset" data-canvas-preset="1080x1920"><svg class="ge-canvas-preset-icon" width="10" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><rect x="6" y="2" width="12" height="20" rx="1"></rect></svg><span>Story</span><strong>1080 × 1920</strong></button>
|
||||
<button type="button" class="ge-canvas-preset" data-canvas-preset="1080x1350"><svg class="ge-canvas-preset-icon" width="11" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><rect x="5" y="3" width="14" height="18" rx="1"></rect></svg><span>Portrait</span><strong>1080 × 1350</strong></button>
|
||||
<button type="button" class="ge-canvas-preset" data-canvas-preset="2480x3508"><svg class="ge-canvas-preset-icon" width="11" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M6 2h9l3 3v17H6zM15 2v4h4"></path></svg><span>A4</span><strong>2480 × 3508</strong></button>
|
||||
<button type="button" class="ge-canvas-preset" data-canvas-preset="3840x2160"><svg class="ge-canvas-preset-icon" width="14" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><rect x="2" y="4" width="20" height="16" rx="1"></rect><path d="M8 22h8M12 20v2"></path></svg><span>4K</span><strong>3840 × 2160</strong></button>
|
||||
</div>
|
||||
<div class="ge-canvas-prompt-section-label ge-canvas-prompt-custom-label">Custom size</div>
|
||||
<label class="ge-canvas-units-field"><span class="ge-canvas-control-label"><svg class="ge-canvas-prompt-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 4h16v16H4z"></path><path d="M8 4v16M16 4v16M4 8h16M4 16h16"></path></svg><span>Units</span></span><select id="ge-canvas-prompt-units"><option value="px">Pixels</option><option value="percent">Percent</option></select></label>
|
||||
<div class="ge-canvas-prompt-row">
|
||||
<label class="ge-canvas-prompt-field">
|
||||
<span>Width</span>
|
||||
@@ -102,9 +115,19 @@ export function canvasSizePromptHTML() {
|
||||
<input type="text" id="ge-canvas-prompt-h" inputmode="numeric" value="1024">
|
||||
</label>
|
||||
</div>
|
||||
<p class="ge-canvas-prompt-hint">Pixels, or type a ratio like 3x5 / 16:9 in either field.</p>
|
||||
<div class="ge-canvas-anchor-options">
|
||||
<div class="ge-canvas-anchor-label">Anchor</div>
|
||||
<div class="ge-canvas-anchor-grid" role="group" aria-label="Canvas anchor">
|
||||
${[['0,0','Top left'],['0.5,0','Top'],['1,0','Top right'],['0,0.5','Left'],['0.5,0.5','Center'],['1,0.5','Right'],['0,1','Bottom left'],['0.5,1','Bottom'],['1,1','Bottom right']].map(([value, label], index) => `<button type="button" class="ge-canvas-anchor${index === 0 ? ' active' : ''}" data-canvas-anchor="${value}" title="${label}" aria-label="${label}"></button>`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ge-canvas-resize-options">
|
||||
<label class="ge-canvas-lock-option"><input type="checkbox" id="ge-canvas-prompt-lock" /> <span>Keep proportions</span></label>
|
||||
<label class="ge-canvas-interpolation-field"><span class="ge-canvas-control-label"><svg class="ge-canvas-prompt-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 18 9 13l3 3 8-8"></path><path d="M15 8h5v5"></path></svg><span>Interpolation</span></span><select id="ge-canvas-prompt-interpolation"><option value="high">Smooth</option><option value="medium">Balanced</option><option value="low">Crisp</option></select></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="modal-footer ge-canvas-prompt-footer">
|
||||
<p class="ge-canvas-prompt-hint">Pixels, or type a ratio like 3x5 / 16:9 in either field.</p>
|
||||
<button class="confirm-btn confirm-btn-secondary" id="ge-canvas-prompt-cancel">Cancel</button>
|
||||
<button class="confirm-btn confirm-btn-primary" id="ge-canvas-prompt-ok">Create</button>
|
||||
</div>
|
||||
|
||||
@@ -77,6 +77,10 @@ export function buildRightPanel({ controlsHTML, layerPanelHTML }) {
|
||||
// docked inside the right panel above the layers list.
|
||||
if (window.innerWidth <= 700 && state.container) {
|
||||
state.container.appendChild(controls);
|
||||
// Start canvas-first on phones. The active tool button brings this
|
||||
// sheet back and minimizes Layers, so the two bottom sheets never
|
||||
// compete for the same pointer events on initial open.
|
||||
controls.classList.add('dismissed');
|
||||
}
|
||||
|
||||
// Move every slider-row's value chip out of its <label> and place
|
||||
@@ -102,6 +106,9 @@ export function buildRightPanel({ controlsHTML, layerPanelHTML }) {
|
||||
{
|
||||
const header = layerPanel.querySelector('.ge-layers-header');
|
||||
if (header) {
|
||||
const claimLayerSheet = () => {
|
||||
if (window.innerWidth <= 700) controls.classList.add('dismissed');
|
||||
};
|
||||
let sy = 0, sx = 0, dragging = false, didSwipe = false;
|
||||
header.addEventListener('touchstart', (e) => {
|
||||
if (window.innerWidth > 700) return;
|
||||
@@ -121,6 +128,7 @@ export function buildRightPanel({ controlsHTML, layerPanelHTML }) {
|
||||
// expanded → peek → minimized (swipe down)
|
||||
if (Math.abs(dy) > 20 && Math.abs(dy) > dx) {
|
||||
didSwipe = true;
|
||||
claimLayerSheet();
|
||||
const isExpanded = rightPanel.classList.contains('expanded');
|
||||
const isMinimized = rightPanel.classList.contains('minimized');
|
||||
if (dy < 0) {
|
||||
@@ -143,6 +151,7 @@ export function buildRightPanel({ controlsHTML, layerPanelHTML }) {
|
||||
if (window.innerWidth > 700) return;
|
||||
if (e.target.closest('button')) return;
|
||||
if (didSwipe) { didSwipe = false; return; }
|
||||
claimLayerSheet();
|
||||
// Click cycles between peek and expanded; minimized comes
|
||||
// back to peek (so a tap on the handle always reveals at
|
||||
// least the active layer row).
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* @param {{
|
||||
* currentTool: string,
|
||||
* onSelectTool: (toolId: string, btn: HTMLButtonElement, toolbar: HTMLDivElement) => void,
|
||||
* onClearSelection: (which: 'lasso'|'wand') => void,
|
||||
* onClearSelection: (which: 'marquee'|'lasso'|'wand') => void,
|
||||
* }} ctx
|
||||
* @returns {{ toolbar: HTMLDivElement, toolKeyMap: Record<string,string> }}
|
||||
*/
|
||||
@@ -18,13 +18,23 @@ export function buildToolbar({ currentTool, onSelectTool, onClearSelection }) {
|
||||
toolbar.className = 'ge-toolbar';
|
||||
const tools = [
|
||||
{ id: 'move', label: 'Move', icon: '✥', key: 'V' },
|
||||
{ id: 'hand', label: 'Hand', icon: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 11V6a2 2 0 0 0-4 0v4"/><path d="M14 10V4a2 2 0 0 0-4 0v6"/><path d="M10 10V5a2 2 0 0 0-4 0v9"/><path d="M6 13.5 4.5 12A2.1 2.1 0 0 0 2 15l4.5 5A6 6 0 0 0 11 22h2a7 7 0 0 0 7-7v-4a2 2 0 0 0-4 0v1"/></svg>', key: 'H' },
|
||||
{ id: 'crop', label: 'Crop', icon: '✂', key: 'C' },
|
||||
{ id: 'transform', label: 'Transform', icon: '⤢', key: 'T' },
|
||||
{ id: 'transform', label: 'Transform', icon: '⤢' },
|
||||
{ id: 'text', label: 'Text', icon: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7V4h16v3"/><path d="M9 20h6"/><path d="M12 4v16"/></svg>', key: 'T' },
|
||||
{ id: 'shape', label: 'Shape', icon: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="12" height="12" rx="1"/><circle cx="17" cy="15" r="4"/></svg>', key: 'U' },
|
||||
{ sep: true },
|
||||
{ id: 'brush', label: 'Brush', icon: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.06 11.9l8.07-8.06a2.85 2.85 0 1 1 4.03 4.03l-8.06 8.08"/><path d="M7.07 14.94c-1.66 0-3 1.35-3 3.02 0 1.33-2.5 1.52-2 2.02 1.08 1.1 2.49 2.02 4 2.02 2.2 0 4-1.8 4-4.04a3.01 3.01 0 0 0-3-3.02z"/></svg>', key: 'B' },
|
||||
{ id: 'gradient', label: 'Gradient', icon: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 20 20 4"/><path d="M6 18 18 6" opacity=".45"/><path d="M8 16 16 8" opacity=".2"/></svg>', key: 'G' },
|
||||
{ id: 'eraser', label: 'Eraser', icon: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19.4 14.6 14.6 19.4a2 2 0 0 1-2.83 0L4.6 12.23a2 2 0 0 1 0-2.83l7.17-7.17a2 2 0 0 1 2.83 0l4.8 4.8a2 2 0 0 1 0 2.83Z"/><line x1="22" y1="21" x2="7" y2="21"/><line x1="14" y1="3" x2="9" y2="8"/></svg>', key: 'E' },
|
||||
{ id: 'eyedropper', label: 'Eyedropper', icon: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m19 3 2 2-9.5 9.5-3-3Z"/><path d="m8.5 11.5-5 5V21h4.5l5-5"/></svg>', key: 'I' },
|
||||
{ id: 'smudge', label: 'Smudge', icon: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 19c2-3 4-5 6-6 2-1 4-3 4-6 0-2-1-4-3-4s-3 2-3 4v5"/><path d="M9 13c-2 0-4 1-5 3-.7 1.2-.1 3 1.4 3H18c1.7 0 3-1.3 3-3 0-1.1-.9-2-2-2h-4"/></svg>', key: 'Y' },
|
||||
{ sep: true },
|
||||
{ id: 'clone', label: 'Clone', icon: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="9" r="3"/><path d="M9 12l-3 4h12l-3-4"/><path d="M4 20h16"/></svg>', key: 'K' },
|
||||
{ id: 'heal', label: 'Healing', icon: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="m5 19 14-14"/><path d="M7 5h4M9 3v4M13 17h4M15 15v4"/></svg>', key: 'J' },
|
||||
{ id: 'dodge', label: 'Dodge', icon: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="10" cy="10" r="6"/><path d="m14.5 14.5 6 6"/></svg>', key: 'O' },
|
||||
{ id: 'burn', label: 'Burn', icon: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 22c4 0 7-3 7-7 0-5-4-8-7-13-3 5-7 8-7 13 0 4 3 7 7 7Z"/><path d="M9 16c1.5 1 4.5 1 6 0"/></svg>', key: 'D' },
|
||||
{ id: 'marquee', label: 'Marquee', icon: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="1" stroke-dasharray="3 3"/></svg>', key: 'R' },
|
||||
{ id: 'lasso', label: 'Lasso', icon: '⟡', key: 'L' },
|
||||
{ id: 'wand', label: 'Wand', icon: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 4V2"/><path d="M15 16v-2"/><path d="M8 9h2"/><path d="M20 9h2"/><path d="M17.8 11.8L19 13"/><path d="M15 9h0"/><path d="M17.8 6.2L19 5"/><path d="M3 21l9-9"/><path d="M12.2 6.2L11 5"/></svg>', key: 'W' },
|
||||
{ id: 'sam', label: 'SAM', ai: true, icon: '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7c3-3 13-3 16 0"/><path d="M4 17c3 3 13 3 16 0"/><circle cx="12" cy="12" r="3"/><path d="M12 2v3M12 19v3"/></svg>' },
|
||||
@@ -56,7 +66,7 @@ export function buildToolbar({ currentTool, onSelectTool, onClearSelection }) {
|
||||
// selection (lasso, wand). Inpaint masks are first-class sub-layers
|
||||
// now so they get their own delete-X in the layer panel.
|
||||
const clearTitle = t.id === 'sam' ? 'Open SAM prompt' : 'Clear selection';
|
||||
const clearBadge = (t.id === 'lasso' || t.id === 'wand' || t.id === 'sam')
|
||||
const clearBadge = (t.id === 'marquee' || t.id === 'lasso' || t.id === 'wand' || t.id === 'sam')
|
||||
? '<span class="ge-tool-clear" title="' + clearTitle + '" data-clear-tool="' + t.id + '">' +
|
||||
'<svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg>' +
|
||||
'</span>'
|
||||
|
||||
@@ -26,6 +26,10 @@ export function buildTopbar() {
|
||||
<span class="ge-stacked-glyph"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 4v6h6"/><path d="M3.51 15a9 9 0 1 0 2.13-9.36L1 10"/><polyline points="12 7 12 12 16 14"/></svg></span>
|
||||
<span class="ge-stacked-label">HISTORY</span>
|
||||
</button>
|
||||
<button class="ge-btn ge-btn-sm ge-stacked-btn" id="ge-compare-btn" title="Show the document before editing" aria-label="Show the document before editing" aria-pressed="false">
|
||||
<span class="ge-stacked-glyph"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v18"></path><path d="M5 7h14M5 17h14"></path></svg></span>
|
||||
<span class="ge-stacked-label">BEFORE</span>
|
||||
</button>
|
||||
<span class="ge-topbar-sep"></span>
|
||||
<button class="ge-btn ge-btn-sm" id="ge-zoom-out" title="Zoom out">−</button>
|
||||
<span class="ge-zoom-stack">
|
||||
@@ -47,13 +51,36 @@ export function buildTopbar() {
|
||||
<span class="ge-topbar-sep"></span>
|
||||
</div>
|
||||
<div class="ge-topbar-right">
|
||||
<span class="ge-draft-status" id="ge-draft-status" role="status" aria-live="polite" title="Draft status">Not saved</span>
|
||||
<span class="ge-canvas-size" id="ge-canvas-size" title="Canvas size" hidden></span>
|
||||
<div class="ge-view-wrap">
|
||||
<button class="ge-btn ge-btn-sm ge-stacked-btn" id="ge-view-menu-btn" title="Canvas view" aria-haspopup="true">
|
||||
<span class="ge-stacked-glyph"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg><span class="ge-stacked-caret">▾</span></span>
|
||||
<span class="ge-stacked-label">VIEW</span>
|
||||
</button>
|
||||
<div class="ge-view-menu dropdown" id="ge-view-menu" hidden>
|
||||
<button class="dropdown-item-compact ge-view-toggle" role="menuitemcheckbox" data-view-action="rulers"><span class="dropdown-icon ge-view-check">✓</span><span>Rulers</span></button>
|
||||
<button class="dropdown-item-compact ge-view-toggle" role="menuitemcheckbox" data-view-action="grid"><span class="dropdown-icon ge-view-check">✓</span><span>Grid</span></button>
|
||||
<label class="ge-view-grid-size"><span>Grid size</span><input id="ge-grid-size" type="number" min="2" max="1000" step="1" value="16"><span>px</span></label>
|
||||
<div class="dropdown-section-divider"></div>
|
||||
<button class="dropdown-item-compact ge-view-toggle" role="menuitemcheckbox" data-view-action="snap"><span class="dropdown-icon ge-view-check">✓</span><span>Snap</span></button>
|
||||
<button class="dropdown-item-compact ge-view-toggle" role="menuitemcheckbox" data-view-action="snap-grid"><span class="dropdown-icon ge-view-check">✓</span><span>Snap to grid</span></button>
|
||||
<button class="dropdown-item-compact" data-view-action="clear-guides"><span class="dropdown-icon">×</span><span>Clear guides</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ge-image-wrap">
|
||||
<button class="ge-btn ge-btn-sm" id="ge-image-menu-btn" title="Image actions" aria-haspopup="true">Image ▾</button>
|
||||
<button class="ge-btn ge-btn-sm ge-stacked-btn" id="ge-image-menu-btn" title="Image actions" aria-haspopup="true">
|
||||
<span class="ge-stacked-glyph"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg><span class="ge-stacked-caret">▾</span></span>
|
||||
<span class="ge-stacked-label">IMAGE</span>
|
||||
</button>
|
||||
<div class="ge-image-menu dropdown" id="ge-image-menu" hidden>
|
||||
<button class="dropdown-item-compact" data-image-action="resize">
|
||||
<button class="dropdown-item-compact" data-image-action="canvas-size">
|
||||
<span class="dropdown-icon">⤢</span>
|
||||
<span>Canvas…</span>
|
||||
<span>Canvas Size...</span>
|
||||
</button>
|
||||
<button class="dropdown-item-compact" data-image-action="image-size">
|
||||
<span class="dropdown-icon"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg></span>
|
||||
<span>Image Size...</span>
|
||||
</button>
|
||||
<div class="ge-filter-submenu-label">Transform</div>
|
||||
<button class="dropdown-item-compact" data-image-action="rotate-90">
|
||||
@@ -74,9 +101,61 @@ export function buildTopbar() {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ge-selection-wrap">
|
||||
<button class="ge-btn ge-btn-sm ge-stacked-btn" id="ge-selection-menu-btn" title="Selection actions" aria-haspopup="true">
|
||||
<span class="ge-stacked-glyph"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="1" stroke-dasharray="3 3"/></svg><span class="ge-stacked-caret">▾</span></span>
|
||||
<span class="ge-stacked-label">SELECT</span>
|
||||
</button>
|
||||
<div class="ge-selection-menu dropdown" id="ge-selection-menu" hidden>
|
||||
<button class="dropdown-item-compact" data-selection-action="all"><span>Select All</span><span class="dropdown-shortcut">Ctrl+Alt+A</span></button>
|
||||
<button class="dropdown-item-compact" data-selection-action="deselect"><span>Deselect</span><span class="dropdown-shortcut">Ctrl+Shift+D</span></button>
|
||||
<button class="dropdown-item-compact" data-selection-action="reselect"><span>Reselect</span></button>
|
||||
<button class="dropdown-item-compact" data-selection-action="invert"><span>Invert</span><span class="dropdown-shortcut">Ctrl+Alt+I</span></button>
|
||||
<button class="dropdown-item-compact" data-selection-action="transform"><span>Transform Selection</span></button>
|
||||
<button class="dropdown-item-compact" data-selection-action="refine"><span>Refine Selection…</span></button>
|
||||
<div class="dropdown-section-divider"></div>
|
||||
<div class="ge-selection-save-row">
|
||||
<input id="ge-selection-name" type="text" maxlength="100" placeholder="Selection name" aria-label="Saved selection name" />
|
||||
<button type="button" class="ge-icon-btn" data-selection-action="save" title="Save current selection" aria-label="Save current selection">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="ge-filter-submenu-label">Saved selections</div>
|
||||
<div id="ge-saved-selection-list" class="ge-saved-selection-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ge-filter-wrap">
|
||||
<button class="ge-btn ge-btn-sm" id="ge-filter-menu-btn" title="Filters" aria-haspopup="true">Filter ▾</button>
|
||||
<button class="ge-btn ge-btn-sm ge-stacked-btn" id="ge-filter-menu-btn" title="Filters" aria-haspopup="true">
|
||||
<span class="ge-stacked-glyph"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 5h16M7 12h10M10 19h4"/></svg><span class="ge-stacked-caret">▾</span></span>
|
||||
<span class="ge-stacked-label">FILTER</span>
|
||||
</button>
|
||||
<div class="ge-filter-menu dropdown" id="ge-filter-menu" hidden>
|
||||
<div class="ge-filter-submenu-label">Retained effects</div>
|
||||
<button class="dropdown-item-compact" data-filter-action="effect-blur-gaussian">
|
||||
<span class="dropdown-icon ge-blur-icon ge-blur-gaussian" aria-hidden="true"></span>
|
||||
<span>Gaussian Blur (retained)…</span>
|
||||
</button>
|
||||
<button class="dropdown-item-compact" data-filter-action="effect-sharpen">
|
||||
<span class="dropdown-icon" aria-hidden="true">◈</span>
|
||||
<span>Sharpen (retained)…</span>
|
||||
</button>
|
||||
<button class="dropdown-item-compact" data-filter-action="effect-color-overlay">
|
||||
<span class="dropdown-icon" aria-hidden="true">◐</span>
|
||||
<span>Color Overlay (retained)…</span>
|
||||
</button>
|
||||
<button class="dropdown-item-compact" data-filter-action="effect-drop-shadow">
|
||||
<span class="dropdown-icon" aria-hidden="true">◒</span>
|
||||
<span>Drop Shadow (retained)…</span>
|
||||
</button>
|
||||
<button class="dropdown-item-compact" data-filter-action="effect-stroke">
|
||||
<span class="dropdown-icon" aria-hidden="true">□</span>
|
||||
<span>Stroke (retained)…</span>
|
||||
</button>
|
||||
<div class="ge-filter-submenu-label">Presets</div>
|
||||
<button class="dropdown-item-compact" data-filter-action="effect-preset-soft-blur"><span>Soft Blur</span></button>
|
||||
<button class="dropdown-item-compact" data-filter-action="effect-preset-crisp-detail"><span>Crisp Detail</span></button>
|
||||
<button class="dropdown-item-compact" data-filter-action="effect-preset-soft-shadow"><span>Soft Shadow</span></button>
|
||||
<button class="dropdown-item-compact" data-filter-action="effect-preset-white-outline"><span>White Outline</span></button>
|
||||
<div class="ge-filter-submenu-label">Blur</div>
|
||||
<button class="dropdown-item-compact" data-filter-action="blur-gaussian">
|
||||
<span class="dropdown-icon ge-blur-icon ge-blur-gaussian" aria-hidden="true"></span>
|
||||
@@ -92,10 +171,14 @@ export function buildTopbar() {
|
||||
<button class="ge-btn ge-btn-sm" id="ge-shortcuts-btn" title="Keyboard shortcuts (?)" aria-label="Shortcuts">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="position:relative;top:2px;"><rect x="2" y="6" width="20" height="12" rx="2"/><path d="M6 10h.01M10 10h.01M14 10h.01M18 10h.01M7 14h10"/></svg>
|
||||
</button>
|
||||
<button class="ge-btn ge-btn-sm" id="ge-import-topbar" title="Import image as layer">+ Import</button>
|
||||
<button class="ge-btn ge-btn-sm ge-stacked-btn" id="ge-import-topbar" title="Import image as layer">
|
||||
<span class="ge-stacked-glyph"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12"/><polyline points="7 8 12 3 17 8"/><path d="M5 14v5h14v-5"/></svg></span>
|
||||
<span class="ge-stacked-label">IMPORT</span>
|
||||
</button>
|
||||
<div class="ge-save-wrap">
|
||||
<button class="ge-btn ge-btn-primary" id="ge-save-menu-btn" title="Save options" style="display:inline-flex;align-items:center;gap:4px;">Save
|
||||
<svg width="9" height="9" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" style="opacity:0.7"><polyline points="6 9 12 15 18 9"/></svg>
|
||||
<button class="ge-btn ge-btn-sm ge-btn-primary ge-stacked-btn" id="ge-save-menu-btn" title="Save options">
|
||||
<span class="ge-stacked-glyph"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg><span class="ge-stacked-caret">▾</span></span>
|
||||
<span class="ge-stacked-label">SAVE</span>
|
||||
</button>
|
||||
<div class="ge-save-menu dropdown" id="ge-save-menu" hidden>
|
||||
<div class="dropdown-section-label">Image</div>
|
||||
@@ -109,9 +192,9 @@ export function buildTopbar() {
|
||||
<span>Save as copy</span>
|
||||
<span class="dropdown-shortcut">Ctrl+Shift+S</span>
|
||||
</button>
|
||||
<button class="dropdown-item-compact" id="ge-download" title="Download PNG to your computer">
|
||||
<button class="dropdown-item-compact" id="ge-download" title="Export an image to your computer">
|
||||
<span class="dropdown-icon"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg></span>
|
||||
<span>Download PNG</span>
|
||||
<span>Export image...</span>
|
||||
</button>
|
||||
<div class="dropdown-section-divider"></div>
|
||||
<div class="dropdown-section-label">Project</div>
|
||||
|
||||
@@ -23,6 +23,23 @@ export function transformPopupHTML() {
|
||||
</span>
|
||||
</div>
|
||||
<div class="ge-transform-popup-body">
|
||||
<div class="ge-transform-field ge-transform-position-field">
|
||||
<label for="ge-transform-x" title="Transform center X">X</label>
|
||||
<input type="number" class="ge-transform-popup-input" id="ge-transform-x" step="1" aria-label="Transform center X" />
|
||||
<span class="ge-transform-spin" data-spin-for="ge-transform-x">
|
||||
<button type="button" data-spin="down" tabindex="-1" aria-label="Move left">−</button>
|
||||
<button type="button" data-spin="up" tabindex="-1" aria-label="Move right">+</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="ge-transform-field ge-transform-position-field">
|
||||
<label for="ge-transform-y" title="Transform center Y">Y</label>
|
||||
<input type="number" class="ge-transform-popup-input" id="ge-transform-y" step="1" aria-label="Transform center Y" />
|
||||
<span class="ge-transform-spin" data-spin-for="ge-transform-y">
|
||||
<button type="button" data-spin="down" tabindex="-1" aria-label="Move up">−</button>
|
||||
<button type="button" data-spin="up" tabindex="-1" aria-label="Move down">+</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="ge-row-break ge-transform-position-break"></div>
|
||||
<div class="ge-transform-field">
|
||||
<label>W</label>
|
||||
<input type="number" class="ge-transform-popup-input" id="ge-transform-w" step="1" />
|
||||
@@ -48,9 +65,24 @@ export function transformPopupHTML() {
|
||||
<button type="button" data-spin="up" tabindex="-1" aria-label="Rotate +1°">+</button>
|
||||
</span>
|
||||
</div>
|
||||
<span class="ge-transform-quick" aria-label="Transform actions">
|
||||
<button type="button" class="ge-transform-quick-btn" id="ge-transform-flip-h" title="Flip horizontally" aria-label="Flip horizontally">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v18"/><path d="m8 7-4 5 4 5"/><path d="m16 7 4 5-4 5"/></svg>
|
||||
</button>
|
||||
<button type="button" class="ge-transform-quick-btn" id="ge-transform-flip-v" title="Flip vertically" aria-label="Flip vertically">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12h18"/><path d="m7 8 5-4 5 4"/><path d="m7 16 5 4 5-4"/></svg>
|
||||
</button>
|
||||
<button type="button" class="ge-transform-quick-btn" id="ge-transform-rot-90" title="Rotate 90 degrees (Shift: counter-clockwise)" aria-label="Rotate 90 degrees">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 11a8 8 0 1 0-2.3 5.7"/><path d="M20 4v7h-7"/></svg>
|
||||
</button>
|
||||
</span>
|
||||
<button type="button" class="ge-btn ge-btn-sm" id="ge-transform-cancel-btn">Cancel</button>
|
||||
<button type="button" class="ge-btn ge-btn-sm ge-btn-primary" id="ge-transform-apply">Apply</button>
|
||||
</div>
|
||||
<label class="ge-transform-preserve-field">
|
||||
<input type="checkbox" id="ge-transform-preserve-source" />
|
||||
<span>Preserve source pixels</span>
|
||||
</label>
|
||||
<p class="ge-transform-popup-hint">Type <strong>-</strong> before W / H to flip.</p>
|
||||
`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user