@netless/fastboard-ui 1.0.0-canary.1 → 1.0.0-canary.11

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.
Files changed (45) hide show
  1. package/dist/index.css +888 -0
  2. package/dist/index.d.ts +95 -69
  3. package/dist/index.js +7315 -1969
  4. package/dist/index.mjs +7317 -1969
  5. package/dist/index.svelte.mjs +9976 -3731
  6. package/package.json +6 -3
  7. package/src/actions/scroll.ts +1 -1
  8. package/src/actions/tippy.ts +9 -5
  9. package/src/behaviors/apps.ts +1 -1
  10. package/src/components/Button/Button.svelte +4 -1
  11. package/src/components/Button/Button.svelte.d.ts +2 -2
  12. package/src/components/Fastboard/Fastboard.scss +2 -3
  13. package/src/components/Fastboard/Fastboard.svelte +3 -3
  14. package/src/components/Fastboard/{Fastboard.svelte.ts → Fastboard.svelte.d.ts} +1 -1
  15. package/src/components/Fastboard/ReplayFastboard.svelte +5 -2
  16. package/src/components/Fastboard/{ReplayFastboard.svelte.ts → ReplayFastboard.svelte.d.ts} +2 -1
  17. package/src/components/Icons/Curve.svelte +10 -0
  18. package/src/components/Icons/CurveDashed.svelte +16 -0
  19. package/src/components/Icons/Eraser.svelte +35 -1
  20. package/src/components/Icons/EraserFilled.svelte +2 -2
  21. package/src/components/Icons/PencilEraser.svelte +16 -0
  22. package/src/components/Icons/PencilEraserFilled.svelte +16 -0
  23. package/src/components/Icons/index.ts +11 -0
  24. package/src/components/PageControl/PageControl.svelte +2 -2
  25. package/src/components/Toolbar/README.md +1 -1
  26. package/src/components/Toolbar/Toolbar.scss +5 -5
  27. package/src/components/Toolbar/Toolbar.svelte +24 -10
  28. package/src/components/Toolbar/Toolbar.svelte.d.ts +18 -1
  29. package/src/components/Toolbar/components/Contents.scss +14 -3
  30. package/src/components/Toolbar/components/Contents.svelte +191 -22
  31. package/src/components/Toolbar/components/PencilEraserSize.svelte +27 -0
  32. package/src/components/Toolbar/components/Shapes.svelte +1 -0
  33. package/src/components/Toolbar/components/Slider.svelte +0 -1
  34. package/src/components/Toolbar/components/StrokeColor.svelte +1 -0
  35. package/src/components/Toolbar/components/TextColor.svelte +1 -0
  36. package/src/components/Toolbar/components/constants.ts +32 -4
  37. package/src/components/Toolbar/components/helper.ts +1 -1
  38. package/src/components/theme.scss +11 -4
  39. package/src/helpers/index.ts +72 -48
  40. package/src/index.ts +8 -4
  41. package/src/style.scss +4 -0
  42. package/src/typings.ts +16 -6
  43. package/dist/index.js.map +0 -1
  44. package/dist/index.mjs.map +0 -1
  45. package/dist/index.svelte.mjs.map +0 -1
@@ -3,22 +3,23 @@
3
3
  import type { Writable } from "svelte/store";
4
4
  import type { Placement } from "tippy.js";
5
5
  import type { Language, Theme } from "../../../typings";
6
- import type { Shape } from "./constants";
7
- import { applianceShapes, shapesIcon, shapesIconActive } from "./constants";
6
+ import type { Shape, Eraser } from "./constants";
7
+ import { eraserIcon, eraserIconActive, applianceShapes, shapesIcon, shapesIconActive } from "./constants";
8
8
  import { writable } from "svelte/store";
9
9
  import { scrollHeight } from "../../../actions/height";
10
10
  import { scrollTop } from "../../../actions/scroll";
11
+ import { tippy_hide_all } from "../../../actions/tippy";
11
12
  import { clamp } from "../../helpers";
12
13
  import { i18n } from "./constants";
13
- import { apps } from "../../../behaviors";
14
+ import { stockedApps } from "../../../behaviors";
14
15
  import { tooltip } from "./helper";
15
16
  import Icons from "../../Icons";
16
17
  import Button from "../../Button";
17
18
  import StrokeWidth from "./StrokeWidth.svelte";
18
19
  import StrokeColor from "./StrokeColor.svelte";
20
+ import PencilEraserSize from "./PencilEraserSize.svelte";
19
21
  import TextColor from "./TextColor.svelte";
20
22
  import Shapes from "./Shapes.svelte";
21
- import { tippy_hide_all } from "../../../actions/tippy";
22
23
 
23
24
  export let app: FastboardApp | null | undefined = null;
24
25
  export let theme: Theme = "light";
@@ -27,15 +28,19 @@
27
28
  export let scroll_height: Writable<number>;
28
29
  export let computed_height = 0;
29
30
  export let scrollable = false;
31
+ export let hide_dotted = false;
30
32
  export let hide_apps = false;
33
+ export let eraser_type: "delete" | "pencil" | "both" = "both";
31
34
 
32
35
  const name = "fastboard-toolbar";
33
36
 
34
37
  let last_shape: Shape = "rectangle";
38
+ let last_eraser: Eraser = "eraser";
35
39
 
36
40
  let pencil_panel: HTMLDivElement;
37
41
  let text_panel: HTMLDivElement;
38
42
  let shapes_panel: HTMLDivElement;
43
+ let eraser_panel: HTMLDivElement;
39
44
  let apps_panel: HTMLDivElement;
40
45
 
41
46
  let btn_props: { name: string; theme: Theme; disabled: boolean; placement: Placement };
@@ -47,10 +52,14 @@
47
52
  selector: tooltip(t.selector, hotkeys?.changeToSelector),
48
53
  pencil: tooltip(t.pencil, hotkeys?.changeToPencil),
49
54
  eraser: tooltip(t.eraser, hotkeys?.changeToEraser),
55
+ pencilEraser: tooltip(t.pencilEraser, hotkeys?.changeToPencilEraser),
56
+ eraserForPanel: tooltip(t.eraser, hotkeys?.changeToEraser),
57
+ pencilEraserForPanel: tooltip(t.pencilEraser, hotkeys?.changeToPencilEraser),
50
58
  text: tooltip(t.text, hotkeys?.changeToText),
51
59
  };
52
60
  $: memberState = app?.memberState;
53
61
  $: appliance = $memberState?.currentApplianceName;
62
+ $: dotted = $memberState?.dottedLine;
54
63
  $: shape = $memberState?.shapeType;
55
64
  $: status = app?.appsStatus;
56
65
 
@@ -60,6 +69,10 @@
60
69
  last_shape = shape;
61
70
  }
62
71
 
72
+ $: if (["pencilEraser", "eraser"].includes(appliance as Appliance)) {
73
+ last_eraser = appliance as "pencilEraser" | "eraser";
74
+ }
75
+
63
76
  $: max_scroll = scrollable ? $scroll_height + (32 + 8) * 2 - computed_height : 0;
64
77
 
65
78
  let top = writable(0);
@@ -83,6 +96,12 @@
83
96
  function text() {
84
97
  app?.setAppliance("text");
85
98
  }
99
+ function set_dotted() {
100
+ app?.toggleDottedLine(true);
101
+ }
102
+ function unset_dotted() {
103
+ app?.toggleDottedLine(false);
104
+ }
86
105
  function select_last_shape() {
87
106
  if (applianceShapes.includes(last_shape as Appliance)) {
88
107
  app?.setAppliance(last_shape as Appliance);
@@ -90,9 +109,15 @@
90
109
  app?.setAppliance("shape", last_shape as Exclude<Shape, Appliance>);
91
110
  }
92
111
  }
93
- function eraser() {
112
+ function select_last_eraser() {
113
+ app?.setAppliance(last_eraser);
114
+ }
115
+ function select_eraser() {
94
116
  app?.setAppliance("eraser");
95
117
  }
118
+ function select_pencil_eraser() {
119
+ app?.setAppliance("pencilEraser");
120
+ }
96
121
  function clear() {
97
122
  app?.cleanCurrentScene();
98
123
  }
@@ -104,51 +129,120 @@
104
129
  </Button>
105
130
  {/if}
106
131
  <div class="{name}-scrollable" class:scrollable use:scrollHeight={scroll_height} use:scrollTop={top}>
107
- <Button class="clicker" {...btn_props} on:click={clicker} content={c.clicker}>
132
+ <Button
133
+ class="clicker"
134
+ active={appliance === "clicker"}
135
+ {...btn_props}
136
+ on:click={clicker}
137
+ content={c.clicker}
138
+ >
108
139
  {#if appliance === "clicker"}
109
140
  <Icons.ClickFilled {theme} active />
110
141
  {:else}
111
142
  <Icons.Click {theme} />
112
143
  {/if}
113
144
  </Button>
114
- <Button class="selector" {...btn_props} on:click={selector} content={c.selector}>
145
+ <Button
146
+ class="selector"
147
+ active={appliance === "selector"}
148
+ {...btn_props}
149
+ on:click={selector}
150
+ content={c.selector}
151
+ >
115
152
  {#if appliance === "selector"}
116
153
  <Icons.SelectorFilled {theme} active />
117
154
  {:else}
118
155
  <Icons.Selector {theme} />
119
156
  {/if}
120
157
  </Button>
121
- <Button class="pencil" {...btn_props} on:click={pencil} content={c.pencil} menu={pencil_panel}>
158
+ <Button
159
+ class="pencil"
160
+ active={appliance === "pencil"}
161
+ {...btn_props}
162
+ on:click={pencil}
163
+ content={c.pencil}
164
+ menu={pencil_panel}
165
+ >
122
166
  {#if appliance === "pencil"}
123
167
  <Icons.PencilFilled {theme} active />
124
168
  {:else}
125
169
  <Icons.Pencil {theme} />
126
170
  {/if}
127
171
  </Button>
128
- <Button class="text" {...btn_props} on:click={text} content={c.text} menu={text_panel}>
172
+ <Button
173
+ class="text"
174
+ active={appliance === "text"}
175
+ {...btn_props}
176
+ on:click={text}
177
+ content={c.text}
178
+ menu={text_panel}
179
+ >
129
180
  {#if appliance === "text"}
130
181
  <Icons.TextFilled {theme} active />
131
182
  {:else}
132
183
  <Icons.Text {theme} />
133
184
  {/if}
134
185
  </Button>
135
- <Button class="shapes" {...btn_props} on:click={select_last_shape} content={t.shapes} menu={shapes_panel}>
186
+ <Button
187
+ class="shapes"
188
+ active={appliance === last_shape || (appliance === "shape" && shape === last_shape)}
189
+ {...btn_props}
190
+ on:click={select_last_shape}
191
+ content={t.shapes}
192
+ menu={shapes_panel}
193
+ >
136
194
  {#if appliance === last_shape || (appliance === "shape" && shape === last_shape)}
137
195
  <svelte:component this={shapesIconActive[last_shape]} {theme} active />
138
196
  {:else}
139
197
  <svelte:component this={shapesIcon[last_shape]} {theme} />
140
198
  {/if}
141
199
  </Button>
142
- <Button class="eraser" {...btn_props} on:click={eraser} content={c.eraser}>
143
- {#if appliance === "eraser"}
144
- <Icons.EraserFilled {theme} active />
145
- {:else}
146
- <Icons.Eraser {theme} />
147
- {/if}
148
- </Button>
149
- <Button class="clear" {...btn_props} on:click={clear} content={t.clear}>
150
- <Icons.Clear {theme} />
151
- </Button>
200
+ {#if eraser_type === "delete"}
201
+ <Button
202
+ class="eraser"
203
+ active={appliance === "eraser"}
204
+ {...btn_props}
205
+ on:click={select_eraser}
206
+ content={c.eraser}
207
+ menu={eraser_panel}
208
+ >
209
+ {#if appliance === "eraser"}
210
+ <Icons.EraserFilled {theme} active />
211
+ {:else}
212
+ <Icons.Eraser {theme} />
213
+ {/if}
214
+ </Button>
215
+ {:else if eraser_type === "pencil"}
216
+ <Button
217
+ class="eraser"
218
+ active={appliance === "pencilEraser"}
219
+ {...btn_props}
220
+ on:click={select_pencil_eraser}
221
+ content={c.pencilEraser}
222
+ menu={eraser_panel}
223
+ >
224
+ {#if appliance === "pencilEraser"}
225
+ <Icons.PencilEraserFilled {theme} active />
226
+ {:else}
227
+ <Icons.PencilEraser {theme} />
228
+ {/if}
229
+ </Button>
230
+ {:else}
231
+ <Button
232
+ class="eraser"
233
+ active={appliance === last_eraser}
234
+ {...btn_props}
235
+ on:click={select_last_eraser}
236
+ content={t[last_eraser]}
237
+ menu={eraser_panel}
238
+ >
239
+ {#if appliance === last_eraser}
240
+ <svelte:component this={eraserIconActive[last_eraser]} {theme} active />
241
+ {:else}
242
+ <svelte:component this={eraserIcon[last_eraser]} {theme} />
243
+ {/if}
244
+ </Button>
245
+ {/if}
152
246
  {#if !hide_apps}
153
247
  <Button class="apps" {...btn_props} content={t.apps} menu={apps_panel} menu_placement="right-end">
154
248
  <Icons.Apps {theme} />
@@ -163,6 +257,31 @@
163
257
 
164
258
  <div class="{name}-panel-wrapper" style="display:none">
165
259
  <div class="{name}-panel pencil" bind:this={pencil_panel}>
260
+ {#if !hide_dotted}
261
+ <div class="{name}-panel-btns">
262
+ <Button
263
+ class="pencil"
264
+ active={appliance === "pencil" && !dotted}
265
+ {...btn_props}
266
+ on:click={unset_dotted}
267
+ placement="top"
268
+ content={t.solid}
269
+ >
270
+ <Icons.Curve {theme} active={appliance === "pencil" && !dotted} />
271
+ </Button>
272
+ <Button
273
+ class="pencil"
274
+ active={appliance === "pencil" && dotted}
275
+ {...btn_props}
276
+ on:click={set_dotted}
277
+ placement="top"
278
+ content={t.dashed}
279
+ >
280
+ <Icons.CurveDashed {theme} active={appliance === "pencil" && dotted} />
281
+ </Button>
282
+ </div>
283
+ <div class="{name}-panel-divider" />
284
+ {/if}
166
285
  <StrokeWidth {app} {theme} {disabled} />
167
286
  <div class="{name}-panel-divider" />
168
287
  <StrokeColor {app} {theme} {disabled} />
@@ -177,8 +296,58 @@
177
296
  <div class="{name}-panel-divider" />
178
297
  <StrokeColor {app} {theme} {disabled} />
179
298
  </div>
180
- <div class="{name}-panel apps" style="--n:{$apps.length}" bind:this={apps_panel}>
181
- {#each $apps as netless_app}
299
+ <div class="{name}-panel eraser" bind:this={eraser_panel}>
300
+ {#if eraser_type === "both"}
301
+ <div class="{name}-panel-btns">
302
+ <Button
303
+ class="eraser"
304
+ active={appliance === "eraser"}
305
+ {...btn_props}
306
+ on:click={select_eraser}
307
+ placement="top"
308
+ content={c.eraserForPanel}
309
+ >
310
+ {#if appliance === "eraser"}
311
+ <Icons.EraserFilled {theme} active />
312
+ {:else}
313
+ <Icons.Eraser {theme} />
314
+ {/if}
315
+ </Button>
316
+ <Button
317
+ class="eraser"
318
+ active={appliance === "pencilEraser"}
319
+ {...btn_props}
320
+ on:click={select_pencil_eraser}
321
+ placement="top"
322
+ content={c.pencilEraserForPanel}
323
+ >
324
+ {#if appliance === "pencilEraser"}
325
+ <Icons.PencilEraserFilled {theme} active />
326
+ {:else}
327
+ <Icons.PencilEraser {theme} />
328
+ {/if}
329
+ </Button>
330
+ <Button class="clear" {...btn_props} on:click={clear} placement="top" content={t.clear}>
331
+ <Icons.Clear {theme} />
332
+ </Button>
333
+ </div>
334
+ {#if appliance === "pencilEraser"}
335
+ <div class="{name}-panel-divider" />
336
+ <PencilEraserSize {app} {theme} {disabled} />
337
+ {/if}
338
+ {:else}
339
+ <div class="{name}-panel-btns">
340
+ <Button class="clear" {...btn_props} on:click={clear} placement="top" content={t.clear}>
341
+ <Icons.Clear {theme} />
342
+ </Button>
343
+ </div>
344
+ {#if eraser_type === "pencil"}
345
+ <PencilEraserSize {app} {theme} {disabled} />
346
+ {/if}
347
+ {/if}
348
+ </div>
349
+ <div class="{name}-panel apps" style="--n:{$stockedApps.length}" bind:this={apps_panel}>
350
+ {#each $stockedApps as netless_app}
182
351
  {@const { icon, label, kind, onClick } = netless_app}
183
352
  {@const state = $status && $status[kind]}
184
353
  {@const on_click = () => {
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ import type { FastboardApp } from "@netless/fastboard-core";
3
+ import type { Theme } from "../../../typings";
4
+ import Slider from "./Slider.svelte";
5
+
6
+ export let app: FastboardApp | null | undefined = null;
7
+ export let theme: Theme = "light";
8
+ export let disabled = false;
9
+
10
+ $: memberState = app?.memberState;
11
+ $: value = $memberState?.pencilEraserSize ?? 1;
12
+
13
+ $: props = { value, theme, disabled };
14
+
15
+ function set_pencil_eraser_size({ detail: value }: CustomEvent<number>) {
16
+ app?.setPencilEraserSize(value);
17
+ }
18
+ </script>
19
+
20
+ <Slider
21
+ class="fastboard-toolbar-slider pencil-eraser-size"
22
+ {...props}
23
+ min={1}
24
+ max={3}
25
+ step={1}
26
+ on:change={set_pencil_eraser_size}
27
+ />
@@ -65,6 +65,7 @@
65
65
  }
66
66
  </script>
67
67
 
68
+ <!-- svelte-ignore a11y-click-events-have-key-events -->
68
69
  <div class="fastboard-toolbar-shapes {theme}" on:click={set_appliance_or_shape}>
69
70
  {#each shapes as key (key)}
70
71
  {@const is_selected = appliance === "shape" ? shape === key : appliance === key}
@@ -39,7 +39,6 @@
39
39
  class="{name}-track {theme}"
40
40
  class:grabbing
41
41
  type="range"
42
- role="slider"
43
42
  {disabled}
44
43
  {min}
45
44
  {max}
@@ -25,6 +25,7 @@
25
25
  }
26
26
  </script>
27
27
 
28
+ <!-- svelte-ignore a11y-click-events-have-key-events -->
28
29
  <div class="fastboard-toolbar-colors {theme}" on:click={set_stroke_color}>
29
30
  {#each colorKeys as key (key)}
30
31
  <button
@@ -25,6 +25,7 @@
25
25
  }
26
26
  </script>
27
27
 
28
+ <!-- svelte-ignore a11y-click-events-have-key-events -->
28
29
  <div class="fastboard-toolbar-colors {theme}" on:click={set_stroke_color}>
29
30
  {#each colorKeys as key (key)}
30
31
  <button
@@ -24,11 +24,10 @@ export const shapes = [
24
24
  "speechBalloon",
25
25
  ] as const;
26
26
 
27
- export type Shape = typeof shapes[number];
27
+ export type Shape = (typeof shapes)[number];
28
28
 
29
29
  export const applianceShapes = shapes.slice(0, 4) as Appliance[];
30
30
 
31
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
31
  export const shapesIcon: Record<Shape, any> = {
33
32
  rectangle: Icons.Rectangle,
34
33
  ellipse: Icons.Circle,
@@ -40,7 +39,6 @@ export const shapesIcon: Record<Shape, any> = {
40
39
  speechBalloon: Icons.Balloon,
41
40
  };
42
41
 
43
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
42
  export const shapesIconActive: Record<Shape, any> = {
45
43
  rectangle: Icons.RectangleBolded,
46
44
  ellipse: Icons.CircleBolded,
@@ -52,28 +50,58 @@ export const shapesIconActive: Record<Shape, any> = {
52
50
  speechBalloon: Icons.BalloonBolded,
53
51
  };
54
52
 
53
+ export const erasers = ["eraser", "pencilEraser"] as const;
54
+
55
+ export type Eraser = (typeof erasers)[number];
56
+
57
+ export const eraserIcon: Record<Eraser, any> = {
58
+ eraser: Icons.Eraser,
59
+ pencilEraser: Icons.PencilEraser,
60
+ };
61
+
62
+ export const eraserIconActive: Record<Eraser, any> = {
63
+ eraser: Icons.EraserFilled,
64
+ pencilEraser: Icons.PencilEraserFilled,
65
+ };
66
+
55
67
  export const i18n: I18nData<
56
- "clicker" | "selector" | "pencil" | "text" | "shapes" | "eraser" | "clear" | "apps"
68
+ | "clicker"
69
+ | "selector"
70
+ | "pencil"
71
+ | "pencilEraser"
72
+ | "text"
73
+ | "shapes"
74
+ | "eraser"
75
+ | "clear"
76
+ | "apps"
77
+ | "solid"
78
+ | "dashed"
57
79
  > = {
58
80
  en: {
59
81
  clicker: "clicker",
60
82
  selector: "selector",
61
83
  pencil: "pencil",
62
84
  eraser: "eraser",
85
+ pencilEraser: "eraser",
63
86
  text: "text",
64
87
  shapes: "shapes",
65
88
  clear: "clear",
66
89
  apps: "apps",
90
+ solid: "solid",
91
+ dashed: "dashed",
67
92
  },
68
93
  "zh-CN": {
69
94
  clicker: "点击",
70
95
  selector: "选择",
71
96
  pencil: "铅笔",
72
97
  eraser: "橡皮",
98
+ pencilEraser: "板擦",
73
99
  text: "文字",
74
100
  shapes: "形状",
75
101
  clear: "清屏",
76
102
  apps: "Apps",
103
+ solid: "实线",
104
+ dashed: "虚线",
77
105
  },
78
106
  };
79
107
 
@@ -1,5 +1,5 @@
1
1
  import type { HotKey } from "@netless/fastboard-core";
2
- import { element, attr, append } from "svelte/internal";
2
+ import { append, attr, element } from "svelte/internal";
3
3
 
4
4
  /**
5
5
  * ```svelte
@@ -7,6 +7,7 @@ $themes: (
7
7
  active-color: $blue-6,
8
8
  bg-color: color.change(white, $alpha: 0.9),
9
9
  hover-bg-color: $blue-1,
10
+ active-bg-color: $blue-1,
10
11
  border-color: $grey-1,
11
12
  ),
12
13
  dark: (
@@ -14,6 +15,7 @@ $themes: (
14
15
  active-color: $blue-7,
15
16
  bg-color: color.change($grey-10, $alpha: 0.9),
16
17
  hover-bg-color: $grey-8,
18
+ active-bg-color: $grey-8,
17
19
  border-color: $grey-8,
18
20
  ),
19
21
  );
@@ -58,7 +60,7 @@ $themes: (
58
60
  }
59
61
  }
60
62
 
61
- @mixin btn($size: 24px, $padding: 0) {
63
+ @mixin btn($size: 24px, $padding: 0, $radius: 4px) {
62
64
  appearance: none;
63
65
  cursor: pointer;
64
66
  margin: 0;
@@ -67,7 +69,7 @@ $themes: (
67
69
  width: $size;
68
70
  height: $size;
69
71
  background-color: transparent;
70
- border-radius: 4px;
72
+ border-radius: $radius;
71
73
  font-size: 0;
72
74
  line-height: 1;
73
75
  flex-shrink: 0;
@@ -85,8 +87,13 @@ $themes: (
85
87
  }
86
88
 
87
89
  @each $name, $theme in $themes {
88
- &.#{$name}:not(:disabled):hover {
89
- background-color: read($theme, "hover-bg-color");
90
+ &.#{$name}:not(:disabled) {
91
+ &:hover {
92
+ background-color: read($theme, "hover-bg-color");
93
+ }
94
+ &.is-active {
95
+ background-color: read($theme, "active-bg-color");
96
+ }
90
97
  }
91
98
  }
92
99
  }