@dickpy/dsh-imagegen 1.5.9 → 1.5.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +1319 -804
- package/lib/client.js.map +1 -1
- package/lib/index.js +1 -1
- package/package.json +82 -82
- package/src/client/CanvasBackgrounds.tsx +1 -19
- package/src/client/CanvasWorkspace.tsx +141 -53
- package/src/client/GooeyNav.module.css +224 -0
- package/src/client/GooeyNav.tsx +185 -0
- package/src/client/ImageGenPanel.tsx +203 -100
- package/src/client/canvas-workspace.module.css +63 -24
- package/src/client/locales.ts +1578 -1542
- package/src/client/panel.module.css +86 -11
- package/src/client/surface-theme.ts +20 -0
- package/src/protocol.ts +583 -580
package/lib/index.js
CHANGED
|
@@ -41,7 +41,7 @@ function installSettingsSectionCompat(ctx, ns, schema, entry, hooks) {
|
|
|
41
41
|
/** Settings namespace this plugin owns (host settings seam + bridge). */
|
|
42
42
|
const IMAGEGEN_SETTINGS_NAMESPACE = "dsh-imagegen";
|
|
43
43
|
/** Published package version shared by the host updater and the client UI. */
|
|
44
|
-
const PLUGIN_VERSION = "1.5.
|
|
44
|
+
const PLUGIN_VERSION = "1.5.10";
|
|
45
45
|
/** Same-origin route family (loopback-only, mirroring the dsh-ssh fence). */
|
|
46
46
|
const SETTINGS_API = {
|
|
47
47
|
describe: "/api/dsh-imagegen/settings/describe",
|
package/package.json
CHANGED
|
@@ -1,82 +1,82 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@dickpy/dsh-imagegen",
|
|
3
|
-
"description": "AI image generation plugin for the dsh web GUI: text-to-image and image-to-image through configurable provider channels (gpt-image-2 / grok-imagine-image / nanobanana series / seedream-5.0-pro / dall-e-3, with native xAI Grok Imagine, Google Nano Banana and ByteDance Seedream request shaping), with per-channel model catalogs and a New Session / Image Generation tab entry opening a three-column studio beside the native conversation.",
|
|
4
|
-
"version": "1.5.
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "lib/index.js",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": "./lib/index.js",
|
|
9
|
-
"./client": "./lib/client.js",
|
|
10
|
-
"./package.json": "./package.json"
|
|
11
|
-
},
|
|
12
|
-
"dsh": {
|
|
13
|
-
"bundle": {
|
|
14
|
-
"patch": "./cordis.patch.yml"
|
|
15
|
-
},
|
|
16
|
-
"client": {
|
|
17
|
-
"inject": [
|
|
18
|
-
"@deepseek-ai/dsh-client-store",
|
|
19
|
-
"@deepseek-ai/dsh-api-remotes",
|
|
20
|
-
"@deepseek-ai/dsh-api-session-controller",
|
|
21
|
-
"@deepseek-ai/dsh-client-connection",
|
|
22
|
-
"@deepseek-ai/dsh-client-ui-conversation",
|
|
23
|
-
"@deepseek-ai/dsh-client-ui-renderer",
|
|
24
|
-
"@deepseek-ai/dsh-client-ui-settings",
|
|
25
|
-
"@deepseek-ai/dsh-client-ui-tool"
|
|
26
|
-
],
|
|
27
|
-
"platform": "web"
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
"dependencies": {
|
|
31
|
-
"lucide-react": "^1.41.0",
|
|
32
|
-
"schemastery": "^3.18.0"
|
|
33
|
-
},
|
|
34
|
-
"devDependencies": {
|
|
35
|
-
"@deepseek-ai/cordis": "^4.0.2",
|
|
36
|
-
"@deepseek-ai/dsh-api-remotes": "0.1.2-alpha.2",
|
|
37
|
-
"@deepseek-ai/dsh-api-session-controller": "0.1.2-alpha.2",
|
|
38
|
-
"@deepseek-ai/dsh-attachment": "0.1.2-alpha.2",
|
|
39
|
-
"@deepseek-ai/dsh-client-connection": "0.1.2-alpha.2",
|
|
40
|
-
"@deepseek-ai/dsh-client-locale": "0.1.2-alpha.2",
|
|
41
|
-
"@deepseek-ai/dsh-client-store": "0.1.2-alpha.2",
|
|
42
|
-
"@deepseek-ai/dsh-client-ui-conversation": "0.1.2-alpha.2",
|
|
43
|
-
"@deepseek-ai/dsh-client-ui-primitives": "0.1.2-alpha.2",
|
|
44
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.1.2-alpha.2",
|
|
45
|
-
"@deepseek-ai/dsh-client-ui-settings": "0.1.2-alpha.2",
|
|
46
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.1.2-alpha.2",
|
|
47
|
-
"@deepseek-ai/dsh-commands": "0.1.2-alpha.2",
|
|
48
|
-
"@deepseek-ai/dsh-host-webserver": "0.1.2-alpha.2",
|
|
49
|
-
"@deepseek-ai/dsh-llm": "0.1.2-alpha.2",
|
|
50
|
-
"@deepseek-ai/dsh-settings": "0.1.2-alpha.2",
|
|
51
|
-
"@deepseek-ai/dsh-system-prompt": "0.1.2-alpha.2",
|
|
52
|
-
"@deepseek-ai/dsh-tools": "0.1.2-alpha.2",
|
|
53
|
-
"@types/node": "^22.20.0",
|
|
54
|
-
"@types/react": "~18.3.1",
|
|
55
|
-
"@types/react-dom": "^18.3.5",
|
|
56
|
-
"jsdom": "25.0.1",
|
|
57
|
-
"lightningcss": "1.32.0",
|
|
58
|
-
"react": "^18.3.1",
|
|
59
|
-
"react-dom": "^18.3.1",
|
|
60
|
-
"schemastery": "^3.18.0",
|
|
61
|
-
"tsdown": "0.22.2",
|
|
62
|
-
"typescript": "~5.7.2"
|
|
63
|
-
},
|
|
64
|
-
"files": [
|
|
65
|
-
"lib",
|
|
66
|
-
"src",
|
|
67
|
-
"cordis.patch.yml",
|
|
68
|
-
"docs",
|
|
69
|
-
"README.md"
|
|
70
|
-
],
|
|
71
|
-
"license": "Apache-2.0",
|
|
72
|
-
"repository": {
|
|
73
|
-
"type": "git",
|
|
74
|
-
"url": "git+https://github.com/dickpy/dsh-imagegen.git"
|
|
75
|
-
},
|
|
76
|
-
"scripts": {
|
|
77
|
-
"build": "tsdown",
|
|
78
|
-
"watch": "tsdown --watch",
|
|
79
|
-
"typecheck": "tsc --noEmit",
|
|
80
|
-
"fetch-templates": "node scripts/fetch-templates.mjs"
|
|
81
|
-
}
|
|
82
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@dickpy/dsh-imagegen",
|
|
3
|
+
"description": "AI image generation plugin for the dsh web GUI: text-to-image and image-to-image through configurable provider channels (gpt-image-2 / grok-imagine-image / nanobanana series / seedream-5.0-pro / dall-e-3, with native xAI Grok Imagine, Google Nano Banana and ByteDance Seedream request shaping), with per-channel model catalogs and a New Session / Image Generation tab entry opening a three-column studio beside the native conversation.",
|
|
4
|
+
"version": "1.5.10",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.js",
|
|
9
|
+
"./client": "./lib/client.js",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"dsh": {
|
|
13
|
+
"bundle": {
|
|
14
|
+
"patch": "./cordis.patch.yml"
|
|
15
|
+
},
|
|
16
|
+
"client": {
|
|
17
|
+
"inject": [
|
|
18
|
+
"@deepseek-ai/dsh-client-store",
|
|
19
|
+
"@deepseek-ai/dsh-api-remotes",
|
|
20
|
+
"@deepseek-ai/dsh-api-session-controller",
|
|
21
|
+
"@deepseek-ai/dsh-client-connection",
|
|
22
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
23
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
24
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
25
|
+
"@deepseek-ai/dsh-client-ui-tool"
|
|
26
|
+
],
|
|
27
|
+
"platform": "web"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"lucide-react": "^1.41.0",
|
|
32
|
+
"schemastery": "^3.18.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
36
|
+
"@deepseek-ai/dsh-api-remotes": "0.1.2-alpha.2",
|
|
37
|
+
"@deepseek-ai/dsh-api-session-controller": "0.1.2-alpha.2",
|
|
38
|
+
"@deepseek-ai/dsh-attachment": "0.1.2-alpha.2",
|
|
39
|
+
"@deepseek-ai/dsh-client-connection": "0.1.2-alpha.2",
|
|
40
|
+
"@deepseek-ai/dsh-client-locale": "0.1.2-alpha.2",
|
|
41
|
+
"@deepseek-ai/dsh-client-store": "0.1.2-alpha.2",
|
|
42
|
+
"@deepseek-ai/dsh-client-ui-conversation": "0.1.2-alpha.2",
|
|
43
|
+
"@deepseek-ai/dsh-client-ui-primitives": "0.1.2-alpha.2",
|
|
44
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.1.2-alpha.2",
|
|
45
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.1.2-alpha.2",
|
|
46
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.1.2-alpha.2",
|
|
47
|
+
"@deepseek-ai/dsh-commands": "0.1.2-alpha.2",
|
|
48
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.2-alpha.2",
|
|
49
|
+
"@deepseek-ai/dsh-llm": "0.1.2-alpha.2",
|
|
50
|
+
"@deepseek-ai/dsh-settings": "0.1.2-alpha.2",
|
|
51
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.2-alpha.2",
|
|
52
|
+
"@deepseek-ai/dsh-tools": "0.1.2-alpha.2",
|
|
53
|
+
"@types/node": "^22.20.0",
|
|
54
|
+
"@types/react": "~18.3.1",
|
|
55
|
+
"@types/react-dom": "^18.3.5",
|
|
56
|
+
"jsdom": "25.0.1",
|
|
57
|
+
"lightningcss": "1.32.0",
|
|
58
|
+
"react": "^18.3.1",
|
|
59
|
+
"react-dom": "^18.3.1",
|
|
60
|
+
"schemastery": "^3.18.0",
|
|
61
|
+
"tsdown": "0.22.2",
|
|
62
|
+
"typescript": "~5.7.2"
|
|
63
|
+
},
|
|
64
|
+
"files": [
|
|
65
|
+
"lib",
|
|
66
|
+
"src",
|
|
67
|
+
"cordis.patch.yml",
|
|
68
|
+
"docs",
|
|
69
|
+
"README.md"
|
|
70
|
+
],
|
|
71
|
+
"license": "Apache-2.0",
|
|
72
|
+
"repository": {
|
|
73
|
+
"type": "git",
|
|
74
|
+
"url": "git+https://github.com/dickpy/dsh-imagegen.git"
|
|
75
|
+
},
|
|
76
|
+
"scripts": {
|
|
77
|
+
"build": "tsdown",
|
|
78
|
+
"watch": "tsdown --watch",
|
|
79
|
+
"typecheck": "tsc --noEmit",
|
|
80
|
+
"fetch-templates": "node scripts/fetch-templates.mjs"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* receives pointer events, so every listener observes the viewport element. */
|
|
4
4
|
|
|
5
5
|
import { useEffect, useRef } from 'react'
|
|
6
|
+
import { detectLightSurface } from './surface-theme.ts'
|
|
6
7
|
import css from './canvas-workspace.module.css'
|
|
7
8
|
|
|
8
9
|
/** Interactive flowmap-style dot field ("fluid distortion"): the pointer's
|
|
@@ -632,25 +633,6 @@ const SHADER_QUAD_VERTEX = `
|
|
|
632
633
|
}
|
|
633
634
|
`
|
|
634
635
|
|
|
635
|
-
/** Walk up from the grid layer to the first opaque surface and decide
|
|
636
|
-
* light vs dark, so the shader backdrops can pick their lightMode ink. */
|
|
637
|
-
function detectLightSurface(element: HTMLElement): boolean {
|
|
638
|
-
let node: HTMLElement | null = element
|
|
639
|
-
while (node !== null) {
|
|
640
|
-
const color = getComputedStyle(node).backgroundColor
|
|
641
|
-
const match = color.match(/rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)(?:[\s,]+([\d.]+))?\s*\)/)
|
|
642
|
-
if (match !== null) {
|
|
643
|
-
const alpha = match[4] !== undefined ? Number(match[4]) : 1
|
|
644
|
-
if (alpha >= 0.5) {
|
|
645
|
-
const luminance = (0.2126 * Number(match[1]) + 0.7152 * Number(match[2]) + 0.0722 * Number(match[3])) / 255
|
|
646
|
-
return luminance > 0.55
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
node = node.parentElement
|
|
650
|
-
}
|
|
651
|
-
return false
|
|
652
|
-
}
|
|
653
|
-
|
|
654
636
|
/** Shared engine for the fullscreen-shader backdrops: compiles one program,
|
|
655
637
|
* owns the DPR-sized canvas, the rAF loop, viewport resize and smoothed
|
|
656
638
|
* pointer tracking. `frame` sets uniforms and calls draw(). WebGL2 is
|
|
@@ -351,6 +351,95 @@ export function CanvasWorkspace(props: CanvasWorkspaceProps): React.JSX.Element
|
|
|
351
351
|
const [libraryOpen, setLibraryOpen] = useState(false)
|
|
352
352
|
const [pickerTab, setPickerTab] = useState<'upload' | 'history' | 'gallery' | 'generate'>('upload')
|
|
353
353
|
const backgroundFileRef = useRef<HTMLInputElement>(null)
|
|
354
|
+
|
|
355
|
+
/** reactbits.dev "Dock" port: each tile spring-scales by its distance to the
|
|
356
|
+
* pointer (width/height, so neighbours part like the macOS dock) and the
|
|
357
|
+
* panel breathes taller while hovered to make room for the labels. */
|
|
358
|
+
const dockOuterRef = useRef<HTMLDivElement>(null)
|
|
359
|
+
const dockRef = useRef<HTMLDivElement>(null)
|
|
360
|
+
useEffect(() => {
|
|
361
|
+
const outer = dockOuterRef.current
|
|
362
|
+
const dock = dockRef.current
|
|
363
|
+
if (outer === null || dock === null) return
|
|
364
|
+
if (typeof window.requestAnimationFrame !== 'function') return
|
|
365
|
+
const clockNow = (): number => (typeof performance !== 'undefined' ? performance.now() : Date.now())
|
|
366
|
+
const BASE = 34
|
|
367
|
+
const MAGNIFIED = 50
|
|
368
|
+
const DISTANCE = 150
|
|
369
|
+
const REST_HEIGHT = 42
|
|
370
|
+
const HOVER_HEIGHT = MAGNIFIED + MAGNIFIED / 2 + 4
|
|
371
|
+
const STIFFNESS = 170
|
|
372
|
+
const DAMPING = 16
|
|
373
|
+
const MASS = 0.5
|
|
374
|
+
const tiles = [...dock.querySelectorAll<HTMLElement>('[data-dock-item]')].map(el => ({ el, size: BASE, velocity: 0 }))
|
|
375
|
+
let outerSize = REST_HEIGHT
|
|
376
|
+
let outerVelocity = 0
|
|
377
|
+
let mouseX = Number.POSITIVE_INFINITY
|
|
378
|
+
let hovered = false
|
|
379
|
+
let raf = 0
|
|
380
|
+
let running = false
|
|
381
|
+
let last = clockNow()
|
|
382
|
+
const step = (now: number): void => {
|
|
383
|
+
const dt = Math.min(0.05, (now - last) / 1000)
|
|
384
|
+
last = now
|
|
385
|
+
let settled = true
|
|
386
|
+
for (const tile of tiles) {
|
|
387
|
+
let target = BASE
|
|
388
|
+
if (hovered) {
|
|
389
|
+
const rect = tile.el.getBoundingClientRect()
|
|
390
|
+
const distance = Math.abs(mouseX - (rect.left + rect.width / 2))
|
|
391
|
+
target = BASE + (MAGNIFIED - BASE) * Math.max(0, 1 - distance / DISTANCE)
|
|
392
|
+
}
|
|
393
|
+
tile.velocity += ((STIFFNESS * (target - tile.size) - DAMPING * tile.velocity) / MASS) * dt
|
|
394
|
+
tile.size += tile.velocity * dt
|
|
395
|
+
if (Math.abs(target - tile.size) > 0.15 || Math.abs(tile.velocity) > 2) settled = false
|
|
396
|
+
else {
|
|
397
|
+
tile.size = target
|
|
398
|
+
tile.velocity = 0
|
|
399
|
+
}
|
|
400
|
+
tile.el.style.width = `${tile.size.toFixed(2)}px`
|
|
401
|
+
tile.el.style.height = `${tile.size.toFixed(2)}px`
|
|
402
|
+
}
|
|
403
|
+
const outerTarget = hovered ? HOVER_HEIGHT : REST_HEIGHT
|
|
404
|
+
outerVelocity += ((STIFFNESS * (outerTarget - outerSize) - DAMPING * outerVelocity) / MASS) * dt
|
|
405
|
+
outerSize += outerVelocity * dt
|
|
406
|
+
if (Math.abs(outerTarget - outerSize) > 0.25 || Math.abs(outerVelocity) > 3) settled = false
|
|
407
|
+
else {
|
|
408
|
+
outerSize = outerTarget
|
|
409
|
+
outerVelocity = 0
|
|
410
|
+
}
|
|
411
|
+
outer.style.height = `${outerSize.toFixed(2)}px`
|
|
412
|
+
if (settled) {
|
|
413
|
+
running = false
|
|
414
|
+
return
|
|
415
|
+
}
|
|
416
|
+
raf = window.requestAnimationFrame(step)
|
|
417
|
+
}
|
|
418
|
+
const wake = (): void => {
|
|
419
|
+
if (running) return
|
|
420
|
+
running = true
|
|
421
|
+
last = clockNow()
|
|
422
|
+
raf = window.requestAnimationFrame(step)
|
|
423
|
+
}
|
|
424
|
+
const onPointerMove = (event: PointerEvent): void => {
|
|
425
|
+
mouseX = event.clientX
|
|
426
|
+
hovered = true
|
|
427
|
+
wake()
|
|
428
|
+
}
|
|
429
|
+
const onPointerLeave = (): void => {
|
|
430
|
+
hovered = false
|
|
431
|
+
mouseX = Number.POSITIVE_INFINITY
|
|
432
|
+
wake()
|
|
433
|
+
}
|
|
434
|
+
dock.addEventListener('pointermove', onPointerMove)
|
|
435
|
+
dock.addEventListener('pointerleave', onPointerLeave)
|
|
436
|
+
wake()
|
|
437
|
+
return () => {
|
|
438
|
+
window.cancelAnimationFrame(raf)
|
|
439
|
+
dock.removeEventListener('pointermove', onPointerMove)
|
|
440
|
+
dock.removeEventListener('pointerleave', onPointerLeave)
|
|
441
|
+
}
|
|
442
|
+
}, [])
|
|
354
443
|
const imageFileRef = useRef<HTMLInputElement>(null)
|
|
355
444
|
const [renamingTitle, setRenamingTitle] = useState(false)
|
|
356
445
|
const [confirmDeleteProject, setConfirmDeleteProject] = useState(false)
|
|
@@ -1869,59 +1958,58 @@ export function CanvasWorkspace(props: CanvasWorkspaceProps): React.JSX.Element
|
|
|
1869
1958
|
{emptyState}
|
|
1870
1959
|
</div>
|
|
1871
1960
|
|
|
1872
|
-
<div
|
|
1873
|
-
className={`${css.dock} ${cursorClass}`}
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
label={tt('canvas.
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
<IconButton name="trash" size={18} label={tt('canvas.delete')} disabled={selectedIds.size === 0 && selectedConnectionId === null} onClick={deleteSelection} />
|
|
1961
|
+
<div className={css.dockOuter} data-canvas-no-zoom="" ref={dockOuterRef}>
|
|
1962
|
+
<div className={`${css.dock} ${cursorClass}`} ref={dockRef} data-canvas-no-zoom="" role="toolbar">
|
|
1963
|
+
<div className={css.dockItem} data-dock-item="" data-label={tt('canvas.toolSelect')}>
|
|
1964
|
+
<IconButton name="select" size={18} label={tt('canvas.toolSelect')} active={tool === 'select'} onClick={() => setTool('select')} />
|
|
1965
|
+
</div>
|
|
1966
|
+
<div className={css.dockItem} data-dock-item="" data-label={tt('canvas.toolPan')}>
|
|
1967
|
+
<IconButton name="pan" size={18} label={tt('canvas.toolPan')} active={tool === 'pan'} onClick={() => setTool('pan')} />
|
|
1968
|
+
</div>
|
|
1969
|
+
<span className={css.dockDivider} aria-hidden="true" />
|
|
1970
|
+
<div className={css.dockItem} data-dock-item="" data-label={tt('canvas.addImage')}>
|
|
1971
|
+
<IconButton
|
|
1972
|
+
name="image"
|
|
1973
|
+
size={18}
|
|
1974
|
+
label={tt('canvas.addImage')}
|
|
1975
|
+
active={imageMenu !== null}
|
|
1976
|
+
onClick={event => openDockMenu('image', event.currentTarget)}
|
|
1977
|
+
onMouseEnter={event => openDockMenu('image', event.currentTarget)}
|
|
1978
|
+
onMouseLeave={scheduleMenuClose}
|
|
1979
|
+
/>
|
|
1980
|
+
</div>
|
|
1981
|
+
<div className={css.dockItem} data-dock-item="" data-label={tt('canvas.addText')}>
|
|
1982
|
+
<IconButton name="text" size={18} label={tt('canvas.addText')} onClick={() => placeNewNode(createTextNode())} />
|
|
1983
|
+
</div>
|
|
1984
|
+
<div className={css.dockItem} data-dock-item="" data-label={tt('canvas.addConfigNode')}>
|
|
1985
|
+
<IconButton name="sparkle" size={18} label={tt('canvas.addConfigNode')} onClick={() => placeNewNode(createConfigNode())} />
|
|
1986
|
+
</div>
|
|
1987
|
+
<div className={css.dockItem} data-dock-item="" data-label={tt('canvas.templateLibrary')}>
|
|
1988
|
+
<IconButton name="template" size={18} label={tt('canvas.templateLibrary')} active={libraryOpen} onClick={() => setLibraryOpen(previous => !previous)} />
|
|
1989
|
+
</div>
|
|
1990
|
+
<span className={css.dockDivider} aria-hidden="true" />
|
|
1991
|
+
<div className={css.dockItem} data-dock-item="" data-label={tt('canvas.background')}>
|
|
1992
|
+
<IconButton
|
|
1993
|
+
name="background"
|
|
1994
|
+
size={18}
|
|
1995
|
+
label={tt('canvas.background')}
|
|
1996
|
+
active={backgroundMenu !== null}
|
|
1997
|
+
onClick={event => openDockMenu('background', event.currentTarget)}
|
|
1998
|
+
onMouseEnter={event => openDockMenu('background', event.currentTarget)}
|
|
1999
|
+
onMouseLeave={scheduleMenuClose}
|
|
2000
|
+
/>
|
|
2001
|
+
</div>
|
|
2002
|
+
<div className={css.dockItem} data-dock-item="" data-label={tt('canvas.undo')}>
|
|
2003
|
+
<IconButton name="undo" size={18} label={tt('canvas.undo')} disabled={pastRef.current.length === 0} onClick={undo} />
|
|
2004
|
+
</div>
|
|
2005
|
+
<div className={css.dockItem} data-dock-item="" data-label={tt('canvas.redo')}>
|
|
2006
|
+
<IconButton name="redo" size={18} label={tt('canvas.redo')} disabled={futureRef.current.length === 0} onClick={redo} />
|
|
2007
|
+
</div>
|
|
2008
|
+
<span className={css.dockDivider} aria-hidden="true" />
|
|
2009
|
+
<div className={css.dockItem} data-dock-item="" data-label={tt('canvas.delete')}>
|
|
2010
|
+
<IconButton name="trash" size={18} label={tt('canvas.delete')} disabled={selectedIds.size === 0 && selectedConnectionId === null} onClick={deleteSelection} />
|
|
2011
|
+
</div>
|
|
2012
|
+
</div>
|
|
1925
2013
|
</div>
|
|
1926
2014
|
|
|
1927
2015
|
{imageMenu !== null ? <div
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/* reactbits.dev "GooeyNav" port, theme-following. The gooey merge uses an SVG
|
|
2
|
+
* alpha-contrast filter (blur + feColorMatrix) instead of the CSS
|
|
3
|
+
* blur+contrast trick, so colored particles survive on any surface: the pill
|
|
4
|
+
* and ink follow the host theme via [data-theme]. */
|
|
5
|
+
|
|
6
|
+
.container {
|
|
7
|
+
--gooey-time: 1500ms;
|
|
8
|
+
--gooey-color-1: #5227ff;
|
|
9
|
+
--gooey-color-2: #ff9ffc;
|
|
10
|
+
--gooey-color-3: #b19eef;
|
|
11
|
+
--gooey-color-4: #22d3ee;
|
|
12
|
+
/* light surface (default): dark ink pill, white text */
|
|
13
|
+
--gooey-pill: #17171c;
|
|
14
|
+
--gooey-pill-ink: #ffffff;
|
|
15
|
+
--gooey-ink: #5b5e6b;
|
|
16
|
+
position: relative;
|
|
17
|
+
font-size: 13px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.container[data-theme='dark'] {
|
|
21
|
+
--gooey-pill: #ffffff;
|
|
22
|
+
--gooey-pill-ink: #17171c;
|
|
23
|
+
--gooey-ink: #ffffff;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.gooSvg {
|
|
27
|
+
position: absolute;
|
|
28
|
+
width: 0;
|
|
29
|
+
height: 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.container nav {
|
|
33
|
+
display: flex;
|
|
34
|
+
position: relative;
|
|
35
|
+
transform: translate3d(0, 0, 0.01px);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.container nav ul {
|
|
39
|
+
display: flex;
|
|
40
|
+
gap: 6px;
|
|
41
|
+
list-style: none;
|
|
42
|
+
padding: 0 4px;
|
|
43
|
+
margin: 0;
|
|
44
|
+
position: relative;
|
|
45
|
+
z-index: 3;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.container nav li {
|
|
49
|
+
border-radius: 100vw;
|
|
50
|
+
position: relative;
|
|
51
|
+
transition:
|
|
52
|
+
background-color 0.3s ease,
|
|
53
|
+
color 0.3s ease,
|
|
54
|
+
box-shadow 0.3s ease;
|
|
55
|
+
color: var(--gooey-ink);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.container nav li button {
|
|
59
|
+
display: inline-block;
|
|
60
|
+
padding: 5px 13px;
|
|
61
|
+
font: inherit;
|
|
62
|
+
color: inherit;
|
|
63
|
+
background: none;
|
|
64
|
+
border: none;
|
|
65
|
+
cursor: pointer;
|
|
66
|
+
white-space: nowrap;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.container nav li.active {
|
|
70
|
+
color: var(--gooey-pill-ink);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.container nav li::after {
|
|
74
|
+
content: '';
|
|
75
|
+
position: absolute;
|
|
76
|
+
inset: 0;
|
|
77
|
+
border-radius: 10px;
|
|
78
|
+
background: var(--gooey-pill);
|
|
79
|
+
opacity: 0;
|
|
80
|
+
transform: scale(0);
|
|
81
|
+
transition: all 0.3s ease;
|
|
82
|
+
z-index: -1;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.container nav li.active::after {
|
|
86
|
+
opacity: 1;
|
|
87
|
+
transform: scale(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.effect {
|
|
91
|
+
position: absolute;
|
|
92
|
+
left: 0;
|
|
93
|
+
top: 0;
|
|
94
|
+
width: 0;
|
|
95
|
+
height: 0;
|
|
96
|
+
opacity: 1;
|
|
97
|
+
pointer-events: none;
|
|
98
|
+
display: grid;
|
|
99
|
+
place-items: center;
|
|
100
|
+
z-index: 1;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.effectText {
|
|
104
|
+
color: var(--gooey-ink);
|
|
105
|
+
transition: color 0.3s ease;
|
|
106
|
+
font-size: 13px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.effectText.active {
|
|
110
|
+
color: var(--gooey-pill-ink);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.effectFilter {
|
|
114
|
+
filter: url(#dsh-gooey-nav-goo);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.effectFilter::after {
|
|
118
|
+
content: '';
|
|
119
|
+
position: absolute;
|
|
120
|
+
inset: 0;
|
|
121
|
+
background: var(--gooey-pill);
|
|
122
|
+
transform: scale(0);
|
|
123
|
+
opacity: 0;
|
|
124
|
+
z-index: -1;
|
|
125
|
+
border-radius: 100vw;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.effectFilter.active::after {
|
|
129
|
+
animation: gooeyPill 0.3s ease both;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@keyframes gooeyPill {
|
|
133
|
+
to {
|
|
134
|
+
transform: scale(1);
|
|
135
|
+
opacity: 1;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.particle,
|
|
140
|
+
.point {
|
|
141
|
+
display: block;
|
|
142
|
+
opacity: 0;
|
|
143
|
+
width: 14px;
|
|
144
|
+
height: 14px;
|
|
145
|
+
border-radius: 100%;
|
|
146
|
+
transform-origin: center;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.particle {
|
|
150
|
+
--time: 1500ms;
|
|
151
|
+
position: absolute;
|
|
152
|
+
top: calc(50% - 7px);
|
|
153
|
+
left: calc(50% - 7px);
|
|
154
|
+
animation: gooeyParticle calc(var(--time)) ease 1 -350ms;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.point {
|
|
158
|
+
background: var(--color);
|
|
159
|
+
opacity: 1;
|
|
160
|
+
animation: gooeyPoint calc(var(--time)) ease 1 -350ms;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@keyframes gooeyParticle {
|
|
164
|
+
0% {
|
|
165
|
+
transform: rotate(0deg) translate(calc(var(--start-x)), calc(var(--start-y)));
|
|
166
|
+
opacity: 1;
|
|
167
|
+
animation-timing-function: cubic-bezier(0.55, 0, 1, 0.45);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
70% {
|
|
171
|
+
transform: rotate(calc(var(--rotate) * 0.5)) translate(calc(var(--end-x) * 1.2), calc(var(--end-y) * 1.2));
|
|
172
|
+
opacity: 1;
|
|
173
|
+
animation-timing-function: ease;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
85% {
|
|
177
|
+
transform: rotate(calc(var(--rotate) * 0.66)) translate(calc(var(--end-x)), calc(var(--end-y)));
|
|
178
|
+
opacity: 1;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
100% {
|
|
182
|
+
transform: rotate(calc(var(--rotate) * 1.2)) translate(calc(var(--end-x) * 0.5), calc(var(--end-y) * 0.5));
|
|
183
|
+
opacity: 1;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@keyframes gooeyPoint {
|
|
188
|
+
0% {
|
|
189
|
+
transform: scale(0);
|
|
190
|
+
opacity: 0;
|
|
191
|
+
animation-timing-function: cubic-bezier(0.55, 0, 1, 0.45);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
25% {
|
|
195
|
+
transform: scale(calc(var(--scale) * 0.25));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
38% {
|
|
199
|
+
opacity: 1;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
65% {
|
|
203
|
+
transform: scale(var(--scale));
|
|
204
|
+
opacity: 1;
|
|
205
|
+
animation-timing-function: ease;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
85% {
|
|
209
|
+
transform: scale(var(--scale));
|
|
210
|
+
opacity: 1;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
100% {
|
|
214
|
+
transform: scale(0);
|
|
215
|
+
opacity: 0;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
@media (prefers-reduced-motion: reduce) {
|
|
220
|
+
.particle,
|
|
221
|
+
.point {
|
|
222
|
+
animation: none;
|
|
223
|
+
}
|
|
224
|
+
}
|