Skip to content

Commit fc5427d

Browse files
committed
implement basic toolbar
1 parent a274b10 commit fc5427d

File tree

9 files changed

+222
-67
lines changed

9 files changed

+222
-67
lines changed

packages/svelte/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,6 @@
146146
},
147147
"devDependencies": {
148148
"@jridgewell/trace-mapping": "^0.3.25",
149-
"@neodrag/svelte": "^2.3.2",
150149
"@playwright/test": "^1.46.1",
151150
"@rollup/plugin-commonjs": "^28.0.1",
152151
"@rollup/plugin-node-resolve": "^15.3.0",
Lines changed: 134 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,144 @@
11
<script>
2-
import { draggable } from '@neodrag/svelte';
2+
import { mount, onMount, tick, unmount } from 'svelte';
33
import Icon from './Icon.svelte';
4+
import { SvelteMap } from 'svelte/reactivity';
45
56
let {
67
/** @type import('./public.d.ts').ResolvedConfig */
78
config
89
} = $props();
910
let open = $state(true); // todo change this to false
1011
11-
/** @type {string[]} */
12-
let active_tool_names = $state([]);
12+
/** @type {SvelteMap<string, Record<string, any>>} */
13+
let active_tools = $state(new SvelteMap());
14+
/** @type {HTMLElement} */
15+
let toolbar;
16+
/** @type {HTMLElement} */
17+
let toolbarPanels;
18+
19+
let dragOffsetX = 0;
20+
let dragOffsetY = 0;
21+
22+
onMount(() => {
23+
recalculate_toolbar_panel_position();
24+
});
1325
1426
/**
1527
* @param {import('./public').Tool} tool
1628
*/
1729
function toggle_tool(tool) {
18-
const active = active_tool_names.includes(tool.name);
30+
const active = active_tools.has(tool.name);
1931
if (!active) {
20-
active_tool_names.push(tool.name);
32+
let mounted_component;
33+
if (tool.component) mounted_component = mountTool(tool.component, tool.name, { tool });
34+
35+
active_tools.set(tool.name, mounted_component);
2136
tool.activate();
2237
} else {
23-
active_tool_names.splice(active_tool_names.indexOf(tool.name), 1);
38+
const mounted_component = active_tools.get(tool.name);
39+
if (tool.component && mounted_component) unmountTool(mounted_component, tool.name);
40+
2441
tool.deactivate();
42+
active_tools.delete(tool.name);
43+
}
44+
}
45+
46+
/**
47+
* @param {import('svelte').Component} component
48+
* @param {string} id
49+
* @param {Record<string, any>} props
50+
*/
51+
function mountTool(component, id, props) {
52+
if (document.getElementById(id) != null) {
53+
throw Error(`${id} already exists, skipping`);
2554
}
2655
27-
console.log(active_tool_names);
56+
const el = document.createElement('div');
57+
el.setAttribute('id', `svelte-toolbar-${id}`);
58+
toolbarPanels.appendChild(el);
59+
const mounted_component = mount(component, { target: el, props });
60+
61+
return mounted_component;
62+
}
63+
64+
/**
65+
* @param {string} id
66+
* @param {Record<string, any>} component
67+
*/
68+
async function unmountTool(component, id) {
69+
await unmount(component);
70+
71+
const el = document.getElementById(`svelte-toolbar-${id}`);
72+
if (el) el.remove();
73+
}
74+
75+
/**
76+
* @param {DragEvent} event
77+
*/
78+
function drag_start(event) {
79+
const rect = toolbar.getBoundingClientRect();
80+
dragOffsetX = event.clientX - rect.x;
81+
dragOffsetY = event.clientY - rect.y;
82+
}
83+
84+
/**
85+
* @param {DragEvent} event
86+
*/
87+
function drag(event) {
88+
if (event.clientX === 0 || event.clientY === 0) return;
89+
90+
const rect = toolbar.getBoundingClientRect();
91+
92+
const x = window.innerWidth - event.clientX + dragOffsetX - rect.width;
93+
const y = window.innerHeight - event.clientY + dragOffsetY - rect.height;
94+
toolbar.style.right = x + 'px';
95+
toolbar.style.bottom = y + 'px';
96+
97+
recalculate_toolbar_panel_position();
98+
}
99+
100+
async function toggle_toolbar() {
101+
open = !open;
102+
103+
// need to wait here, so that the toolbar can close first
104+
await tick();
105+
106+
recalculate_toolbar_panel_position();
107+
}
108+
109+
function recalculate_toolbar_panel_position() {
110+
const rect = toolbar.getBoundingClientRect();
111+
toolbarPanels.style.right = toolbar.style.right;
112+
toolbarPanels.style.bottom = parseFloat(toolbar.style.bottom ?? 0) + rect.height + 'px';
28113
}
29114
</script>
30115
31-
<div class="toolbar" use:draggable={{ bounds: document.body }}>
116+
<svelte:window onresize={recalculate_toolbar_panel_position} />
117+
118+
<div
119+
class="toolbar"
120+
bind:this={toolbar}
121+
draggable="true"
122+
ondrag={drag}
123+
ondragstart={drag_start}
124+
role="toolbar"
125+
tabindex="-1"
126+
>
32127
{#if open}
33128
<ul class="tools">
34129
{#each config.tools as tool}
35-
<li class:active={active_tool_names.includes(tool.name)}>
36-
<button onclick={() => toggle_tool(tool)}>{tool.name}</button>
130+
<li class:active={active_tools.has(tool.name)}>
131+
<button onclick={() => toggle_tool(tool)} aria-label={tool.name}>{@html tool.icon}</button
132+
>
37133
</li>
38134
{/each}
39135
</ul>
40136
{/if}
41-
<button type="button" class="toolbar-selector" onclick={() => (open = !open)}>
137+
<button type="button" class="toolbar-selector" onclick={toggle_toolbar}>
42138
<Icon />
43139
</button>
44140
</div>
141+
<div class="toolbar-panels" bind:this={toolbarPanels}></div>
45142
46143
<style>
47144
.toolbar-selector {
@@ -54,7 +151,6 @@
54151
}
55152
56153
.tools {
57-
background-color: #666; /* TODO: consider dark / light mode */
58154
list-style: none;
59155
margin: 0;
60156
padding: 0;
@@ -68,21 +164,42 @@
68164
border: #111 1px solid;
69165
border-radius: 50%;
70166
margin: 0 10px;
71-
padding: 10px;
72-
height: 30px;
167+
height: 50px;
168+
width: 50px;
73169
}
74170
75171
.tools li.active {
76172
border-color: #ff3e00;
77173
}
78174
175+
.tools li button {
176+
padding: 0;
177+
display: flex;
178+
align-items: center;
179+
justify-content: center;
180+
width: 100%;
181+
height: 100%;
182+
}
183+
184+
.tools li button :global(svg) {
185+
height: 30px;
186+
width: 30px;
187+
}
188+
79189
.toolbar {
80190
display: inline-flex;
191+
background-color: #666; /* TODO: consider dark / light mode */
81192
color: white;
82-
position: static;
193+
position: fixed;
194+
right: 0;
195+
bottom: 0;
83196
}
84197
85-
.toolbar > * {
86-
/* display: inline-block; */
198+
.toolbar-panels {
199+
position: fixed;
200+
background-color: #999;
201+
right: 0;
202+
bottom: 0;
203+
display: flex;
87204
}
88205
</style>
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import {mountUI} from './runtime.svelte.js';
1+
import { mountUI } from './runtime.svelte.js';
22
import { configureSvelte } from './configure.svelte.js';
33
import { svelte_inspector } from './tools/inspector/index.js';
4+
import { svelte_config } from './tools/config/index.js';
45
export * from './configure.svelte.js';
56

6-
7-
configureSvelte({tools:[svelte_inspector]});
7+
configureSvelte({ tools: [svelte_inspector, svelte_config] });
88
mountUI();

packages/svelte/src/toolbar/public.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { Component } from 'svelte';
2+
13
export * from './index.js';
24

35
export interface Tool {
@@ -7,6 +9,7 @@ export interface Tool {
79
deactivate: () => void;
810
keyCombo?: string;
911
disabled?: boolean;
12+
component?: Component;
1013
}
1114
type ToolFn = () => Tool;
1215

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import ToolBar from './ToolBar.svelte';
2-
import { mount } from 'svelte';
3-
import {getConfig} from './configure.svelte.js';
2+
import { mount, unmount } from 'svelte';
3+
import { getConfig } from './configure.svelte.js';
44

55
export function mountUI() {
6-
if(typeof window !== 'undefined') {
6+
if (typeof window !== 'undefined') {
77
const id = 'svelte-toolbar-host';
88
if (document.getElementById(id) != null) {
99
console.debug('svelte-toolbar-host already exists, skipping');
10-
return
10+
return;
1111
}
12-
const props = $state({config: getConfig()})
12+
const props = $state({ config: getConfig() });
1313

1414
const el = document.createElement('div');
1515
el.setAttribute('id', id);
1616
// appending to documentElement adds it outside of body
1717
document.documentElement.appendChild(el);
18-
mount(ToolBar, { target: el,props });
18+
mount(ToolBar, { target: el, props });
1919
}
2020
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<div>
2+
<label for="position"
3+
>Position
4+
5+
<select id="position">
6+
<option value="top-left">top left</option>
7+
<option value="top-left">top right</option>
8+
<option value="top-left">bottom right</option>
9+
<option value="top-left">bottom left</option>
10+
</select>
11+
</label>
12+
</div>
13+
<div>
14+
<label for="position"
15+
>Position
16+
17+
<select id="position">
18+
<option value="top-left">top left</option>
19+
<option value="top-left">top right</option>
20+
<option value="top-left">bottom right</option>
21+
<option value="top-left">bottom left</option>
22+
</select>
23+
</label>
24+
</div>
25+
<div>
26+
<label for="position"
27+
>Position
28+
29+
<select id="position">
30+
<option value="top-left">top left</option>
31+
<option value="top-left">top right</option>
32+
<option value="top-left">bottom right</option>
33+
<option value="top-left">bottom left</option>
34+
</select>
35+
</label>
36+
</div>
37+
<div>
38+
<label for="position"
39+
>Position
40+
41+
<select id="position">
42+
<option value="top-left">top left</option>
43+
<option value="top-left">top right</option>
44+
<option value="top-left">bottom right</option>
45+
<option value="top-left">bottom left</option>
46+
</select>
47+
</label>
48+
</div>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import Config from './Config.svelte'
2+
3+
const icon = `
4+
<svg fill="#000000" height="200px" width="200px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 612.004 612.004" xml:space="preserve"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g> <path d="M593.676,241.87h-48.719c-5.643-21.066-13.982-41.029-24.649-59.482l34.459-34.459c7.158-7.154,7.158-18.755,0-25.912 l-64.783-64.783c-7.154-7.156-18.757-7.156-25.909,0l-34.461,34.461c-18.453-10.667-38.414-19.005-59.482-24.647V18.325 c0-10.121-8.201-18.324-18.324-18.324h-91.616c-10.123,0-18.324,8.203-18.324,18.324V67.05c-21.068,5.64-41.027,13.98-59.48,24.647 l-34.459-34.459c-7.158-7.158-18.755-7.158-25.912,0l-64.785,64.781c-7.158,7.156-7.158,18.755,0,25.913l34.461,34.461 C81.03,200.845,72.69,220.804,67.051,241.87H18.326C8.205,241.87,0,250.073,0,260.193v91.618c0,10.121,8.205,18.324,18.326,18.324 h48.725c5.64,21.066,13.98,41.027,24.645,59.478l-34.461,34.461c-7.158,7.154-7.158,18.757,0,25.911l64.781,64.783 c7.16,7.158,18.759,7.158,25.916,0l34.459-34.459c18.451,10.665,38.412,19.005,59.48,24.645v48.727 c0,10.119,8.201,18.324,18.324,18.324h91.616c10.123,0,18.324-8.205,18.324-18.324v-48.727c21.068-5.64,41.029-13.98,59.482-24.647 l34.461,34.459c7.154,7.158,18.755,7.158,25.913,0l64.781-64.781c7.158-7.158,7.158-18.759,0-25.913l-34.459-34.459 c10.667-18.453,19.007-38.414,24.649-59.479h48.721c10.123,0,18.324-8.203,18.324-18.324v-91.618 C612,250.073,603.799,241.87,593.676,241.87z M306.002,397.619c-50.601,0-91.616-41.021-91.616-91.616 c0-50.597,41.017-91.616,91.616-91.616s91.616,41.019,91.616,91.616C397.616,356.598,356.601,397.619,306.002,397.619z"></path> </g> </g></svg>
5+
`;
6+
7+
/** @return {import('../../public.d.ts').ResolvedConfig.tools[0]} */
8+
export function svelte_config() {
9+
/** @type {Record<string, any>} */
10+
11+
return {
12+
name: 'config',
13+
icon,
14+
keyCombo: 'ctrl-c',
15+
component: Config,
16+
activate: () => {
17+
console.log("activate config")
18+
},
19+
deactivate: () => {
20+
console.log("de-activate config")
21+
}
22+
}
23+
}
24+

packages/svelte/types/index.d.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2549,13 +2549,15 @@ declare module 'svelte/events' {
25492549
}
25502550

25512551
declare module 'svelte/toolbar' {
2552+
import type { Component } from 'svelte';
25522553
export interface Tool {
25532554
name: string;
25542555
icon: string; // url or svg
25552556
activate: () => void;
25562557
deactivate: () => void;
25572558
keyCombo?: string;
25582559
disabled?: boolean;
2560+
component?: Component;
25592561
}
25602562
type ToolFn = () => Tool;
25612563

@@ -2565,12 +2567,11 @@ declare module 'svelte/toolbar' {
25652567
}
25662568

25672569
export interface ResolvedConfig extends Config {
2568-
tools: Tool[]
2570+
tools: Tool[];
25692571
}
2570-
export function configure(options: Partial<Config>): void;
2572+
export function configureSvelte(options: Partial<Config>): void;
25712573

25722574
export function getConfig(): ResolvedConfig;
2573-
export function mountUI(): void;
25742575

25752576
export {};
25762577
}

0 commit comments

Comments
 (0)