mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-11-04 04:02:21 +01:00
feat(photo-editor): Ajax Image Editor client (Photoshop-parity tools)
Photoshop-parity editor client: layers, selection/masking, brushes,
smart filters, LUT import, Quick Edit, mobile gestures, workspaces,
batch operations, and the service-worker versioning (v978->v1006).
Source: vendor/photocraft (ad1cff44) photo-editor client files, merged
onto upstream/dev (a8c147b2). Conflict resolution documented in
pr_work/A_conflicts.md; editor files taken from vendor/photocraft
(upstream's editor is an earlier snapshot of the same feature).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c74f475679
commit
5fac693c1c
@@ -13,7 +13,7 @@
|
||||
* values).
|
||||
*
|
||||
* @param {{
|
||||
* controlsHTML: (ctx: {color, brushSize, wandTolerance}) => string,
|
||||
* controlsHTML: (ctx: {color, brushSize, wandTolerance, bucketTolerance, cropAspectPreset}) => string,
|
||||
* layerPanelHTML: () => string,
|
||||
* }} build
|
||||
*
|
||||
@@ -22,13 +22,19 @@
|
||||
* controls: HTMLDivElement,
|
||||
* layerPanel: HTMLDivElement,
|
||||
* panelResize: HTMLDivElement,
|
||||
* dock: {dock: HTMLDivElement, show: (id: string) => void}|null,
|
||||
* }}
|
||||
*/
|
||||
import { state } from '../state.js';
|
||||
import { buildDock } from './dock.js';
|
||||
|
||||
export function buildRightPanel({ controlsHTML, layerPanelHTML }) {
|
||||
export function buildRightPanel({ controlsHTML, layerPanelHTML, extraPanels = [] }) {
|
||||
const rightPanel = document.createElement('div');
|
||||
rightPanel.className = 'ge-right-panel';
|
||||
// Desktop: present Tool Options + Layers as tabs in a dock. Mobile
|
||||
// (≤ 700 px) keeps the existing bottom-sheet model, where the two
|
||||
// panels are independent sheets, so the dock is bypassed entirely.
|
||||
const useDock = window.innerWidth > 700;
|
||||
|
||||
// Controls section.
|
||||
const controls = document.createElement('div');
|
||||
@@ -66,8 +72,11 @@ export function buildRightPanel({ controlsHTML, layerPanelHTML }) {
|
||||
color: state.color,
|
||||
brushSize: state.brushSize,
|
||||
wandTolerance: state.wandTolerance,
|
||||
bucketTolerance: state.bucketTolerance,
|
||||
cropAspectPreset: state.cropAspectPreset,
|
||||
eyedropperSampleSize: state.eyedropperSampleSize,
|
||||
});
|
||||
rightPanel.appendChild(controls);
|
||||
if (!useDock) rightPanel.appendChild(controls);
|
||||
// Mobile only (≤ 700 px — matches the .ge-editor-body column-stack
|
||||
// breakpoint): the right panel becomes a transformed bottom-sheet,
|
||||
// so any position:fixed descendant gets trapped by the transform
|
||||
@@ -99,7 +108,7 @@ export function buildRightPanel({ controlsHTML, layerPanelHTML }) {
|
||||
const layerPanel = document.createElement('div');
|
||||
layerPanel.className = 'ge-layers';
|
||||
layerPanel.innerHTML = layerPanelHTML();
|
||||
rightPanel.appendChild(layerPanel);
|
||||
if (!useDock) rightPanel.appendChild(layerPanel);
|
||||
// Mobile: tap the header grab handle or swipe up/down to toggle
|
||||
// the layers sheet between peek and expanded. The peek state
|
||||
// always shows the active layer so users never lose access to it.
|
||||
@@ -164,6 +173,77 @@ export function buildRightPanel({ controlsHTML, layerPanelHTML }) {
|
||||
}
|
||||
}
|
||||
|
||||
// Mobile panel access (≤ 700 px). The desktop dock is bypassed on
|
||||
// phones, which left the whole studio panel set (Swatches, Gradients,
|
||||
// Patterns, Properties, Adjustments, Histogram, Info, Channels, Paths,
|
||||
// Character, Navigator, History, Actions) unreachable — there was no way
|
||||
// to add an adjustment or pick a colour from the sheet. Mount the same
|
||||
// entries in a compact `<select>` overlay reachable from a "Panels"
|
||||
// button in the layers header, without touching the peek/expand model.
|
||||
let mobilePanels = null;
|
||||
if (!useDock && Array.isArray(extraPanels) && extraPanels.length) {
|
||||
const mDock = buildDock({
|
||||
entries: extraPanels,
|
||||
persistKey: 'ge-dock-active',
|
||||
defaultId: null,
|
||||
compact: true,
|
||||
});
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'ge-mobile-panels';
|
||||
overlay.hidden = true;
|
||||
const head = document.createElement('div');
|
||||
head.className = 'ge-mobile-panels-head';
|
||||
const title = document.createElement('span');
|
||||
title.className = 'ge-mobile-panels-title';
|
||||
title.textContent = 'Panels';
|
||||
const closeBtn = document.createElement('button');
|
||||
closeBtn.type = 'button';
|
||||
closeBtn.className = 'ge-mobile-panels-close';
|
||||
closeBtn.setAttribute('aria-label', 'Close panels');
|
||||
closeBtn.textContent = '✕';
|
||||
head.append(title, closeBtn);
|
||||
overlay.append(head, mDock.dock);
|
||||
rightPanel.appendChild(overlay);
|
||||
closeBtn.addEventListener('click', () => { overlay.hidden = true; });
|
||||
|
||||
const hdr = layerPanel.querySelector('.ge-layers-header');
|
||||
if (hdr) {
|
||||
const panelsBtn = document.createElement('button');
|
||||
panelsBtn.type = 'button';
|
||||
panelsBtn.className = 'ge-layers-panels-btn';
|
||||
panelsBtn.textContent = 'Panels';
|
||||
panelsBtn.title = 'Show panels';
|
||||
panelsBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
overlay.hidden = !overlay.hidden;
|
||||
});
|
||||
hdr.appendChild(panelsBtn);
|
||||
}
|
||||
mobilePanels = overlay;
|
||||
}
|
||||
|
||||
// Desktop dock: Tool Options + Layers as tabs (PhotoCraft-parity).
|
||||
// The panels are only re-parented, so every existing selector and
|
||||
// listener keeps working; switching tabs just toggles `hidden`.
|
||||
let dock = null;
|
||||
if (useDock) { rightPanel.classList.add('ge-right-panel--dock');
|
||||
dock = buildDock({
|
||||
entries: [
|
||||
...(Array.isArray(extraPanels) ? extraPanels : []),
|
||||
],
|
||||
defaultId: (Array.isArray(extraPanels) && extraPanels[0]?.id) || null,
|
||||
persistKey: 'ge-dock-active',
|
||||
// Narrow desktop columns get the compact selector fallback; the
|
||||
// multi-group stack needs a bit more room to be usable.
|
||||
compact: window.innerWidth <= 820,
|
||||
});
|
||||
rightPanel.appendChild(dock.dock);
|
||||
// Layers stays pinned below the dock so the layer list is always
|
||||
// visible while editing (Photoshop-style), regardless of the active
|
||||
// dock tab. Only the tool options + studio panels live in tabs.
|
||||
rightPanel.appendChild(layerPanel);
|
||||
}
|
||||
|
||||
// Horizontal drag handle on the LEFT edge of the right panel — drag
|
||||
// left to widen, right to narrow. Persists chosen width in
|
||||
// localStorage so it survives reopens. (Earlier version was a
|
||||
@@ -205,5 +285,5 @@ export function buildRightPanel({ controlsHTML, layerPanelHTML }) {
|
||||
} catch {}
|
||||
});
|
||||
|
||||
return { rightPanel, controls, layerPanel, panelResize };
|
||||
return { rightPanel, controls, layerPanel, panelResize, dock };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user