mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-12 01:42:20 +02:00
Squash Odysseus development history
This commit is contained in:
Vendored
+304
-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,9 +305,41 @@ 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>
|
||||
<div class="ge-sam-section" id="ge-sam-section" style="display:none;">
|
||||
<div class="ge-section-title ge-section-title-with-help"><span>SAM</span><span class="ge-section-help" tabindex="0" role="img" aria-label="SAM selection help" title="Click an object for visual SAM selection, or type a neutral object label and use Find. The text is only used to locate a region before SAM creates the mask.">?</span></div>
|
||||
<div class="ge-control-row" style="display:flex;gap:4px;margin-bottom:4px;" title="How the next SAM selection combines with the current selection. Shift / Alt held during a click override this for one click.">
|
||||
<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;" />
|
||||
<button class="ge-btn ge-btn-sm ge-btn-ai" id="ge-sam-find" style="height:28px;display:inline-flex;align-items:center;gap:5px;" title="Find object and create a SAM mask">
|
||||
<span class="ge-btn-ai-mark" aria-hidden="true">✦</span>
|
||||
Find
|
||||
</button>
|
||||
</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-sam-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-sam-clear" title="Clear the selection">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/></svg>
|
||||
Clear
|
||||
</button>
|
||||
<button class="ge-btn ge-btn-sm ge-btn-iconlabel" id="ge-sam-mask" title="Add selection to the inpaint mask">
|
||||
<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>
|
||||
<div class="ge-inpaint-section" id="ge-inpaint-section" style="display:none;">
|
||||
<div class="ge-inpaint-popover-head" data-inpaint-drag>
|
||||
<div class="ge-section-title ge-section-title-with-help ge-inpaint-popover-title"><span>INPAINT</span><span class="ge-section-help" tabindex="0" role="img" aria-label="How inpaint works" title="Brush the area you want the AI to redraw — the red preview marks the mask region. Use Paint to add, Erase to subtract (or hold Ctrl+Alt to flip for one stroke). Generate fills with what your prompt describes; Remove fills with the surrounding background.">?</span></div>
|
||||
@@ -189,12 +427,29 @@ export function controlsHTML({ color, brushSize, wandTolerance }) {
|
||||
<label>Edge stroke <span id="ge-edgestroke-label">0px</span></label>
|
||||
<input type="range" id="ge-edgestroke-slider" min="-80" max="80" value="0" title="Expand (+) or contract (−) the inpaint layer's edge before feathering. Uses the AI buffer generated around your brush." />
|
||||
</div>
|
||||
<div class="ge-control-row ge-actions" id="ge-inpaint-automatch-row" style="display:none;margin-top:6px;">
|
||||
<button class="ge-btn ge-btn-sm ge-btn-iconlabel ge-btn-ai" id="ge-inpaint-automatch" style="width:100%;justify-content:center;" title="Match the latest inpaint result to the surrounding colour and lighting using an adjustment layer.">
|
||||
<span class="ge-btn-ai-mark" aria-hidden="true">✦</span>
|
||||
Auto match color
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ge-eraser-section" id="ge-clone-section" style="display:none;">
|
||||
<div class="ge-section-title ge-section-title-with-help"><span>Clone</span><span class="ge-section-help" tabindex="0" role="img" aria-label="How clone works" title="Alt-click (desktop) or double-tap (mobile) somewhere on the canvas to set the sample source. Then drag elsewhere to clone those pixels onto the active layer. The source point moves with your brush so the offset stays constant. Size / Opacity / Flow / Softness come from the Brush panel.">?</span></div>
|
||||
<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>
|
||||
@@ -212,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>
|
||||
@@ -361,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,15 +18,26 @@ 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>' },
|
||||
{ sep: true },
|
||||
{ id: 'inpaint', label: 'Inpaint', 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="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: 'M' },
|
||||
{ id: 'rembg', ai: true, label: 'Bg Remove', icon: '✄' },
|
||||
@@ -54,8 +65,9 @@ export function buildToolbar({ currentTool, onSelectTool, onClearSelection }) {
|
||||
// Selection-clear badge — rendered only for tools that can hold a
|
||||
// selection (lasso, wand). Inpaint masks are first-class sub-layers
|
||||
// now so they get their own delete-X in the layer panel.
|
||||
const clearBadge = (t.id === 'lasso' || t.id === 'wand')
|
||||
? '<span class="ge-tool-clear" title="Clear selection" data-clear-tool="' + t.id + '">' +
|
||||
const clearTitle = t.id === 'sam' ? 'Open SAM prompt' : 'Clear selection';
|
||||
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