@gradio/imageeditor 0.12.14 → 0.14.0

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 (131) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/Example.svelte +1 -1
  3. package/IMAGE_EDITOR_OVERVIEW.md +224 -0
  4. package/ImageEditor.stories.svelte +13 -5
  5. package/Index.svelte +46 -45
  6. package/{shared/InteractiveImageEditor.svelte → InteractiveImageEditor.svelte} +65 -103
  7. package/README.md +2 -0
  8. package/dist/Example.svelte.d.ts +1 -1
  9. package/dist/Index.svelte +35 -33
  10. package/dist/Index.svelte.d.ts +25 -16
  11. package/dist/{shared/InteractiveImageEditor.svelte → InteractiveImageEditor.svelte} +54 -88
  12. package/dist/{shared/InteractiveImageEditor.svelte.d.ts → InteractiveImageEditor.svelte.d.ts} +17 -15
  13. package/dist/shared/Anchor.svelte +311 -0
  14. package/dist/shared/Anchor.svelte.d.ts +18 -0
  15. package/dist/shared/Controls.svelte +169 -18
  16. package/dist/shared/Controls.svelte.d.ts +9 -2
  17. package/dist/shared/{tools/IconButton.svelte → IconButton.svelte} +19 -3
  18. package/dist/shared/{tools/IconButton.svelte.d.ts → IconButton.svelte.d.ts} +4 -1
  19. package/dist/shared/ImageEditor.svelte +572 -289
  20. package/dist/shared/ImageEditor.svelte.d.ts +54 -56
  21. package/dist/shared/Layers.svelte +260 -0
  22. package/dist/shared/Layers.svelte.d.ts +37 -0
  23. package/dist/shared/Resize.svelte +218 -0
  24. package/dist/shared/Resize.svelte.d.ts +29 -0
  25. package/dist/shared/SecondaryToolbar.svelte +55 -0
  26. package/dist/shared/SecondaryToolbar.svelte.d.ts +37 -0
  27. package/dist/shared/Toolbar.svelte +337 -0
  28. package/dist/shared/Toolbar.svelte.d.ts +42 -0
  29. package/dist/shared/{tools → brush}/BrushOptions.svelte +68 -64
  30. package/dist/shared/{tools → brush}/BrushOptions.svelte.d.ts +9 -9
  31. package/dist/shared/{tools → brush}/BrushSize.svelte +2 -27
  32. package/dist/shared/{tools → brush}/ColorField.svelte +2 -1
  33. package/dist/shared/{tools → brush}/ColorField.svelte.d.ts +1 -0
  34. package/dist/shared/{tools → brush}/ColorPicker.svelte +81 -9
  35. package/dist/shared/{tools → brush}/ColorPicker.svelte.d.ts +1 -0
  36. package/dist/shared/{tools → brush}/ColorSwatch.svelte +78 -38
  37. package/dist/shared/{tools → brush}/ColorSwatch.svelte.d.ts +6 -2
  38. package/dist/shared/brush/brush-cursor.d.ts +101 -0
  39. package/dist/shared/brush/brush-cursor.js +327 -0
  40. package/dist/shared/brush/brush-textures.d.ts +65 -0
  41. package/dist/shared/brush/brush-textures.js +385 -0
  42. package/dist/shared/brush/brush-utils.d.ts +32 -0
  43. package/dist/shared/brush/brush-utils.js +59 -0
  44. package/dist/shared/brush/brush.d.ts +152 -0
  45. package/dist/shared/brush/brush.js +397 -0
  46. package/dist/shared/brush/types.d.ts +37 -0
  47. package/dist/shared/brush/types.js +1 -0
  48. package/dist/shared/core/editor.d.ts +261 -0
  49. package/dist/shared/core/editor.js +1264 -0
  50. package/dist/shared/crop/crop.d.ts +201 -0
  51. package/dist/shared/crop/crop.js +727 -0
  52. package/dist/shared/image/image.d.ts +89 -0
  53. package/dist/shared/image/image.js +183 -0
  54. package/dist/shared/resize/resize.d.ts +270 -0
  55. package/dist/shared/resize/resize.js +1413 -0
  56. package/dist/shared/types.d.ts +16 -0
  57. package/dist/shared/utils/pixi.d.ts +16 -57
  58. package/dist/shared/utils/pixi.js +15 -113
  59. package/dist/shared/zoom/zoom.d.ts +136 -0
  60. package/dist/shared/zoom/zoom.js +548 -0
  61. package/package.json +8 -7
  62. package/shared/Anchor.svelte +384 -0
  63. package/shared/Controls.svelte +178 -37
  64. package/shared/{tools/IconButton.svelte → IconButton.svelte} +21 -4
  65. package/shared/ImageEditor.svelte +712 -350
  66. package/shared/Layers.svelte +282 -0
  67. package/shared/Resize.svelte +239 -0
  68. package/shared/SecondaryToolbar.svelte +67 -0
  69. package/shared/Toolbar.svelte +386 -0
  70. package/shared/brush/BRUSH_TOOL.md +134 -0
  71. package/shared/brush/BrushOptions.svelte +200 -0
  72. package/shared/{tools → brush}/BrushSize.svelte +2 -30
  73. package/shared/{tools → brush}/ColorField.svelte +3 -1
  74. package/shared/{tools → brush}/ColorPicker.svelte +86 -9
  75. package/shared/{tools → brush}/ColorSwatch.svelte +95 -42
  76. package/shared/brush/brush-cursor.ts +408 -0
  77. package/shared/brush/brush-textures.ts +485 -0
  78. package/shared/brush/brush-utils.ts +76 -0
  79. package/shared/brush/brush.ts +510 -0
  80. package/shared/brush/types.ts +41 -0
  81. package/shared/core/EDITOR.md +182 -0
  82. package/shared/core/editor.ts +1788 -0
  83. package/shared/crop/CROP.md +139 -0
  84. package/shared/crop/crop.ts +1027 -0
  85. package/shared/image/IMAGE.md +135 -0
  86. package/shared/image/image.ts +298 -0
  87. package/shared/resize/resize.ts +1894 -0
  88. package/shared/types.ts +19 -0
  89. package/shared/utils/pixi.ts +19 -246
  90. package/shared/zoom/zoom.ts +729 -0
  91. package/dist/shared/layers/Layers.svelte +0 -231
  92. package/dist/shared/layers/Layers.svelte.d.ts +0 -18
  93. package/dist/shared/layers/utils.d.ts +0 -73
  94. package/dist/shared/layers/utils.js +0 -156
  95. package/dist/shared/tools/Brush.svelte +0 -215
  96. package/dist/shared/tools/Brush.svelte.d.ts +0 -29
  97. package/dist/shared/tools/Crop.svelte +0 -155
  98. package/dist/shared/tools/Crop.svelte.d.ts +0 -16
  99. package/dist/shared/tools/Cropper.svelte +0 -271
  100. package/dist/shared/tools/Cropper.svelte.d.ts +0 -41
  101. package/dist/shared/tools/Handle.svelte +0 -260
  102. package/dist/shared/tools/Handle.svelte.d.ts +0 -29
  103. package/dist/shared/tools/Sources.svelte +0 -257
  104. package/dist/shared/tools/Sources.svelte.d.ts +0 -34
  105. package/dist/shared/tools/Tools.svelte +0 -102
  106. package/dist/shared/tools/Tools.svelte.d.ts +0 -48
  107. package/dist/shared/tools/brush.d.ts +0 -62
  108. package/dist/shared/tools/brush.js +0 -133
  109. package/dist/shared/tools/crop.d.ts +0 -15
  110. package/dist/shared/tools/crop.js +0 -154
  111. package/dist/shared/tools/index.d.ts +0 -5
  112. package/dist/shared/tools/index.js +0 -5
  113. package/dist/shared/tools/sources.d.ts +0 -46
  114. package/dist/shared/tools/sources.js +0 -121
  115. package/dist/shared/tools/types.d.ts +0 -1
  116. package/shared/layers/Layers.svelte +0 -250
  117. package/shared/layers/utils.ts +0 -294
  118. package/shared/tools/Brush.svelte +0 -280
  119. package/shared/tools/BrushOptions.svelte +0 -180
  120. package/shared/tools/Crop.svelte +0 -189
  121. package/shared/tools/Cropper.svelte +0 -346
  122. package/shared/tools/Handle.svelte +0 -289
  123. package/shared/tools/Sources.svelte +0 -283
  124. package/shared/tools/Tools.svelte +0 -162
  125. package/shared/tools/brush.ts +0 -247
  126. package/shared/tools/crop.ts +0 -213
  127. package/shared/tools/index.ts +0 -5
  128. package/shared/tools/sources.ts +0 -195
  129. package/shared/tools/types.ts +0 -1
  130. /package/dist/shared/{tools → brush}/BrushSize.svelte.d.ts +0 -0
  131. /package/dist/shared/{tools/types.js → types.js} +0 -0
@@ -3,45 +3,43 @@
3
3
  <script>import { createEventDispatcher } from "svelte";
4
4
  import {} from "@gradio/utils";
5
5
  import { prepare_files } from "@gradio/client";
6
- import {} from "./utils/commands";
7
- import ImageEditor from "./ImageEditor.svelte";
8
- import Layers from "./layers/Layers.svelte";
9
- import {} from "./tools/Brush.svelte";
10
- import {} from "./tools/Brush.svelte";
11
- import { Tools, Crop, Brush, Sources } from "./tools";
6
+ import {} from "./shared/utils/commands";
7
+ import ImageEditor from "./shared/ImageEditor.svelte";
8
+ import {} from "./shared/brush/types";
9
+ import {} from "./shared/Toolbar.svelte";
12
10
  import { BlockLabel } from "@gradio/atoms";
13
11
  import { Image as ImageIcon } from "@gradio/icons";
14
- import { inject } from "./utils/parse_placeholder";
12
+ import { inject } from "./shared/utils/parse_placeholder";
13
+ import {
14
+ } from "./shared/types";
15
15
  export let brush;
16
16
  export let eraser;
17
17
  export let sources;
18
- export let crop_size = null;
19
18
  export let i18n;
20
19
  export let root;
21
20
  export let label = void 0;
22
21
  export let show_label;
23
22
  export let changeable = false;
24
- export let value = {
25
- background: null,
26
- layers: [],
27
- composite: null
28
- };
29
- export let transforms = ["crop"];
23
+ export let theme_mode;
30
24
  export let layers;
25
+ export let composite;
26
+ export let background;
27
+ export let layer_options;
28
+ export let transforms;
31
29
  export let accept_blobs;
32
- export let status = "complete";
33
30
  export let canvas_size;
34
31
  export let fixed_canvas = false;
35
32
  export let realtime;
36
33
  export let upload;
37
- export let stream_handler;
38
- export let dragging;
34
+ export let is_dragging;
39
35
  export let placeholder = void 0;
40
- export let dynamic_height = void 0;
41
- export let height;
36
+ export let border_region;
42
37
  export let full_history = null;
38
+ export let webcam_options;
39
+ export let show_download_button = false;
43
40
  const dispatch = createEventDispatcher();
44
41
  let editor;
42
+ let has_drawn = false;
45
43
  function is_not_null(o) {
46
44
  return !!o;
47
45
  }
@@ -49,7 +47,7 @@ function is_file_data(o) {
49
47
  return !!o;
50
48
  }
51
49
  $:
52
- if (bg)
50
+ if (background_image)
53
51
  dispatch("upload");
54
52
  export async function get_data() {
55
53
  let blobs;
@@ -58,45 +56,41 @@ export async function get_data() {
58
56
  } catch (e) {
59
57
  return { background: null, layers: [], composite: null };
60
58
  }
61
- const bg2 = blobs.background ? upload(
59
+ const bg = blobs.background ? upload(
62
60
  await prepare_files([new File([blobs.background], "background.png")]),
63
61
  root
64
62
  ) : Promise.resolve(null);
65
63
  const layers2 = blobs.layers.filter(is_not_null).map(
66
64
  async (blob, i) => upload(await prepare_files([new File([blob], `layer_${i}.png`)]), root)
67
65
  );
68
- const composite = blobs.composite ? upload(
66
+ const composite2 = blobs.composite ? upload(
69
67
  await prepare_files([new File([blobs.composite], "composite.png")]),
70
68
  root
71
69
  ) : Promise.resolve(null);
72
- const [background, composite_, ...layers_] = await Promise.all([
73
- bg2,
74
- composite,
70
+ const [background2, composite_, ...layers_] = await Promise.all([
71
+ bg,
72
+ composite2,
75
73
  ...layers2
76
74
  ]);
77
75
  return {
78
- background: Array.isArray(background) ? background[0] : background,
76
+ background: Array.isArray(background2) ? background2[0] : background2,
79
77
  layers: layers_.flatMap((layer) => Array.isArray(layer) ? layer : [layer]).filter(is_file_data),
80
78
  composite: Array.isArray(composite_) ? composite_[0] : composite_
81
79
  };
82
80
  }
83
- function handle_value(value2) {
81
+ function handle_value(value) {
84
82
  if (!editor)
85
83
  return;
86
- if (value2 == null) {
84
+ if (value == null) {
87
85
  editor.handle_remove();
88
86
  dispatch("receive_null");
89
87
  }
90
88
  }
91
89
  $:
92
- handle_value(value);
93
- $:
94
- crop_constraint = crop_size;
95
- let bg = false;
90
+ handle_value({ layers, composite, background });
91
+ let background_image = false;
96
92
  let history = false;
97
93
  export let image_id = null;
98
- $:
99
- editor && editor.set_tool && (sources && sources.length ? editor.set_tool("bg") : editor.set_tool("draw"));
100
94
  function nextframe() {
101
95
  return new Promise((resolve) => setTimeout(() => resolve(), 30));
102
96
  }
@@ -155,13 +149,9 @@ async function handle_change(e) {
155
149
  handle_change(e);
156
150
  }
157
151
  }
158
- let active_mode = null;
159
- let editor_height = height - 100;
160
- let _dynamic_height;
161
- $:
162
- dynamic_height = _dynamic_height;
163
152
  $:
164
153
  [heading, paragraph] = placeholder ? inject(placeholder) : [false, false];
154
+ let current_tool;
165
155
  </script>
166
156
 
167
157
  <BlockLabel
@@ -170,60 +160,38 @@ $:
170
160
  label={label || i18n("image.image")}
171
161
  />
172
162
  <ImageEditor
163
+ {transforms}
164
+ {composite}
165
+ {layers}
166
+ {background}
173
167
  on:history
174
168
  {canvas_size}
175
- crop_size={Array.isArray(crop_size) ? crop_size : undefined}
176
169
  bind:this={editor}
177
- bind:height={editor_height}
178
- bind:canvas_height={_dynamic_height}
179
- parent_height={height}
180
170
  {changeable}
181
171
  on:save
182
172
  on:change={handle_change}
183
173
  on:clear={() => dispatch("clear")}
184
- bind:history
185
- bind:bg
174
+ on:download_error
186
175
  {sources}
187
- crop_constraint={!!crop_constraint}
188
176
  {full_history}
177
+ bind:background_image
178
+ bind:current_tool
179
+ brush_options={brush}
180
+ eraser_options={eraser}
181
+ {fixed_canvas}
182
+ {border_region}
183
+ {layer_options}
184
+ {i18n}
185
+ {root}
186
+ {upload}
187
+ bind:is_dragging
188
+ bind:has_drawn
189
+ {webcam_options}
190
+ {show_download_button}
191
+ {theme_mode}
189
192
  >
190
- <Tools {i18n}>
191
- <Layers layer_files={value?.layers || null} enable_layers={layers} />
192
-
193
- <Sources
194
- bind:dragging
195
- {i18n}
196
- {root}
197
- {sources}
198
- {upload}
199
- {stream_handler}
200
- {canvas_size}
201
- bind:bg
202
- bind:active_mode
203
- background_file={value?.background || value?.composite || null}
204
- {fixed_canvas}
205
- ></Sources>
206
-
207
- {#if transforms.includes("crop")}
208
- <Crop {crop_constraint} />
209
- {/if}
210
- {#if brush}
211
- <Brush
212
- color_mode={brush.color_mode}
213
- default_color={brush.default_color}
214
- default_size={brush.default_size}
215
- colors={brush.colors}
216
- mode="draw"
217
- />
218
- {/if}
219
-
220
- {#if brush && eraser}
221
- <Brush default_size={eraser.default_size} mode="erase" />
222
- {/if}
223
- </Tools>
224
-
225
- {#if !bg && !history && active_mode !== "webcam" && status !== "error"}
226
- <div class="empty wrap" style:height={`${editor_height}px`}>
193
+ {#if !background_image && current_tool === "image" && !has_drawn}
194
+ <div class="empty wrap">
227
195
  {#if sources && sources.length}
228
196
  {#if heading || paragraph}
229
197
  {#if heading}
@@ -268,10 +236,10 @@ $:
268
236
  left: 0;
269
237
  right: 0;
270
238
  margin: auto;
271
- z-index: var(--layer-top);
239
+ z-index: var(--layer-1);
272
240
  text-align: center;
273
- color: var(--body-text-color);
274
- top: var(--size-8);
241
+ color: var(--color-grey-500) !important;
242
+ cursor: pointer;
275
243
  }
276
244
 
277
245
  .wrap {
@@ -279,10 +247,8 @@ $:
279
247
  flex-direction: column;
280
248
  justify-content: center;
281
249
  align-items: center;
282
- color: var(--block-label-text-color);
283
250
  line-height: var(--line-md);
284
251
  font-size: var(--text-md);
285
- pointer-events: none;
286
252
  }
287
253
 
288
254
  .or {
@@ -11,41 +11,43 @@ export interface ImageBlobs {
11
11
  }
12
12
  import { type I18nFormatter } from "@gradio/utils";
13
13
  import { type FileData, type Client } from "@gradio/client";
14
- import { type CommandNode } from "./utils/commands";
15
- import { type Brush as IBrush } from "./tools/Brush.svelte";
16
- import { type Eraser } from "./tools/Brush.svelte";
14
+ import { type CommandNode } from "./shared/utils/commands";
15
+ import { type Brush as IBrush, type Eraser } from "./shared/brush/types";
16
+ import { type LayerOptions, type Transform, type Source, type WebcamOptions } from "./shared/types";
17
17
  declare const __propDef: {
18
18
  props: {
19
- brush: IBrush | null;
20
- eraser: Eraser | null;
21
- sources: ("clipboard" | "webcam" | "upload")[];
22
- crop_size?: ([number, number] | `${string}:${string}` | null) | undefined;
19
+ brush: IBrush;
20
+ eraser: Eraser;
21
+ sources: Source[];
23
22
  i18n: I18nFormatter;
24
23
  root: string;
25
24
  label?: string | undefined;
26
25
  show_label: boolean;
27
26
  changeable?: boolean | undefined;
28
- value?: (EditorData | null) | undefined;
29
- transforms?: "crop"[] | undefined;
30
- layers: boolean;
27
+ theme_mode: "dark" | "light";
28
+ layers: FileData[];
29
+ composite: FileData | null;
30
+ background: FileData | null;
31
+ layer_options: LayerOptions;
32
+ transforms: Transform[];
31
33
  accept_blobs: (a: any) => void;
32
- status?: ("pending" | "complete" | "error" | "generating" | "streaming") | undefined;
33
34
  canvas_size: [number, number];
34
35
  fixed_canvas?: boolean | undefined;
35
36
  realtime: boolean;
36
37
  upload: Client["upload"];
37
- stream_handler: Client["stream"];
38
- dragging: boolean;
38
+ is_dragging: boolean;
39
39
  placeholder?: string | undefined;
40
- dynamic_height?: number | undefined;
41
- height: any;
40
+ border_region: number;
42
41
  full_history?: (CommandNode | null) | undefined;
42
+ webcam_options: WebcamOptions;
43
+ show_download_button?: boolean | undefined;
43
44
  get_data?: (() => Promise<ImageBlobs>) | undefined;
44
45
  image_id?: (null | string) | undefined;
45
46
  };
46
47
  events: {
47
48
  history: CustomEvent<import("svelte/store").Writable<CommandNode>>;
48
49
  save: CustomEvent<void>;
50
+ download_error: CustomEvent<string>;
49
51
  clear?: CustomEvent<undefined> | undefined;
50
52
  upload?: CustomEvent<undefined> | undefined;
51
53
  change?: CustomEvent<undefined> | undefined;
@@ -0,0 +1,311 @@
1
+ <script>import { spring } from "svelte/motion";
2
+ import { Image } from "@gradio/icons";
3
+ import { onMount, createEventDispatcher } from "svelte";
4
+ const positions = [
5
+ "top-left",
6
+ "top",
7
+ "top-right",
8
+ "left",
9
+ "center",
10
+ "right",
11
+ "bottom-left",
12
+ "bottom",
13
+ "bottom-right"
14
+ ];
15
+ const dispatch = createEventDispatcher();
16
+ const spring_opt = {
17
+ stiffness: 0.1,
18
+ precision: 0.5
19
+ };
20
+ const pos = spring([5, 5], spring_opt);
21
+ const init = [(120 - 10) / 3, (120 - 10) / 3];
22
+ const dimensions = spring(init, spring_opt);
23
+ const arrow_spring = spring([], spring_opt);
24
+ let last_i = 0;
25
+ let expanded = true;
26
+ const box_size = (120 - 5 * 2) / 3;
27
+ async function handle_box_hover(i) {
28
+ expanded = false;
29
+ last_i = i;
30
+ const y = Math.floor(i / 3);
31
+ const x = i % 3;
32
+ pos.set([x * box_size + 5 * x + 5, y * box_size + 5 * y + 5]);
33
+ dimensions.set(init);
34
+ }
35
+ let last_pos = 0;
36
+ async function handle_box_click(i, stagger = false) {
37
+ if (expanded && stagger)
38
+ return;
39
+ const y = Math.floor(i / 3);
40
+ const x = i % 3;
41
+ const [initial_arrows, eventual_arrows] = get_valid_offsets({
42
+ x,
43
+ y
44
+ });
45
+ if (stagger && last_i !== i) {
46
+ await Promise.all([
47
+ pos.set([x * box_size + 5 * x + 5, y * box_size + 5 * y + 5]),
48
+ arrow_spring.set(initial_arrows, { hard: true }),
49
+ dimensions.set(init)
50
+ ]);
51
+ } else {
52
+ await arrow_spring.set(initial_arrows, { hard: true });
53
+ }
54
+ last_pos = i;
55
+ expanded = true;
56
+ pos.set([0, 0]);
57
+ dimensions.set([120 + 10, 120 + 10]);
58
+ arrow_spring.set(eventual_arrows);
59
+ dispatch("position", positions[i]);
60
+ }
61
+ function get_valid_offsets(anchorPoint) {
62
+ const destination_points = [
63
+ { x: 0, y: 0 },
64
+ { x: 1, y: 0 },
65
+ { x: 2, y: 0 },
66
+ { x: 0, y: 1 },
67
+ { x: 1, y: 1 },
68
+ { x: 2, y: 1 },
69
+ { x: 0, y: 2 },
70
+ { x: 1, y: 2 },
71
+ { x: 2, y: 2 }
72
+ ];
73
+ const offsets = destination_points.map((dest) => ({
74
+ x: dest.x - anchorPoint.x,
75
+ y: dest.y - anchorPoint.y,
76
+ dest
77
+ })).filter((offset) => !(offset.x === 0 && offset.y === 0));
78
+ const normalized_offsets = offsets.map((offset) => {
79
+ if (offset.x === 0 || offset.y === 0) {
80
+ const magnitude = Math.max(Math.abs(offset.x), Math.abs(offset.y));
81
+ return {
82
+ magnitude,
83
+ x: offset.x / magnitude,
84
+ y: offset.y / magnitude,
85
+ dest: offset.dest,
86
+ original: offset,
87
+ type: "cardinal"
88
+ };
89
+ }
90
+ if (Math.abs(offset.x) === Math.abs(offset.y)) {
91
+ const magnitude = Math.abs(offset.x);
92
+ return {
93
+ magnitude,
94
+ x: offset.x / magnitude,
95
+ y: offset.y / magnitude,
96
+ dest: offset.dest,
97
+ original: offset,
98
+ type: "ordinal"
99
+ };
100
+ }
101
+ return null;
102
+ }).filter((offset) => offset !== null);
103
+ const directions = /* @__PURE__ */ new Map();
104
+ normalized_offsets.forEach((offset) => {
105
+ if (directions.has(`${offset.x}-${offset.y}`)) {
106
+ directions.get(`${offset.x}-${offset.y}`)?.push(offset);
107
+ } else {
108
+ directions.set(`${offset.x}-${offset.y}`, [offset]);
109
+ }
110
+ });
111
+ const unique_directions = Array.from(directions.values()).map(
112
+ (direction) => {
113
+ return direction.sort((a, b) => b.magnitude - a.magnitude)[0];
114
+ }
115
+ );
116
+ const eventual_arrows = unique_directions.map((arrow) => ({
117
+ x: arrow.dest.x * box_size + 14 * arrow.dest.x + 14 + 10 + (arrow.type === "ordinal" ? 0 : 12 * arrow.x),
118
+ y: arrow.dest.y * box_size + 14 * arrow.dest.y + 14 + 10 + (arrow.type === "ordinal" ? 0 : 12 * arrow.y),
119
+ x_dir: arrow.x,
120
+ y_dir: arrow.y,
121
+ rotation: Math.atan2(arrow.original.y, arrow.original.x) * (180 / Math.PI) + 180,
122
+ type: arrow.type === "ordinal" ? 1 : 2
123
+ }));
124
+ const initial_arrows = eventual_arrows.map((arrow) => ({
125
+ ...arrow,
126
+ x: anchorPoint.x * box_size + 14 * anchorPoint.x + // gaps
127
+ 14 + // padding
128
+ 10 + // arrow head
129
+ arrow.x_dir * box_size / 2 + arrow.x_dir * (arrow.type === 1 ? 0 : 10),
130
+ // offset based on direction
131
+ y: anchorPoint.y * box_size + 14 * anchorPoint.y + 14 + 10 + arrow.y_dir * box_size / 2 + arrow.y_dir * (arrow.type === 1 ? 0 : 10)
132
+ }));
133
+ return [initial_arrows, eventual_arrows];
134
+ }
135
+ onMount(() => {
136
+ handle_box_click(0);
137
+ });
138
+ </script>
139
+
140
+ <div class="wrap" class:expanded>
141
+ <div
142
+ class="box-wrap"
143
+ role="grid"
144
+ tabindex="0"
145
+ on:mouseleave={() => handle_box_click(last_pos, true)}
146
+ >
147
+ {#each { length: 9 } as _, i}
148
+ <button
149
+ class="box"
150
+ class:active-box={last_pos === i}
151
+ on:mouseenter={() => handle_box_hover(i)}
152
+ on:click={() => handle_box_click(i)}
153
+ >
154
+ <span class:active={last_pos === i} class="icon-wrap">
155
+ <Image />
156
+ </span>
157
+ </button>
158
+ {/each}
159
+ </div>
160
+ <div
161
+ class="expanding-box"
162
+ style:width="{$dimensions[0]}px"
163
+ style:height="{$dimensions[1]}px"
164
+ style:transform="translate({$pos[0]}px, {$pos[1]}px)"
165
+ ></div>
166
+ <svg viewBox="0 0 150 150">
167
+ {#each $arrow_spring as arrow}
168
+ <line
169
+ x1={(last_pos % 3) * box_size +
170
+ 14 * (last_pos % 3) +
171
+ 14 +
172
+ 10 +
173
+ (arrow.x_dir * box_size) / 2 +
174
+ arrow.x_dir * (arrow.type === 1 ? 0 : 10)}
175
+ x2={arrow.x}
176
+ y1={Math.floor(last_pos / 3) * box_size +
177
+ 14 * Math.floor(last_pos / 3) +
178
+ 14 +
179
+ 10 +
180
+ (arrow.y_dir * box_size) / 2 +
181
+ arrow.y_dir * (arrow.type === 1 ? 0 : 10)}
182
+ y2={arrow.y}
183
+ />
184
+ {/each}
185
+ {#each $arrow_spring as arrow}
186
+ <g style:transform="translate(-75px, -75px)">
187
+ <g style:transform="translate({arrow.x}px, {arrow.y}px)">
188
+ <path
189
+ d="M 70,75 80,69 80,81 Z"
190
+ style:transform="rotate({arrow.rotation}deg)"
191
+ style:transform-origin="75 75"
192
+ />
193
+ </g>
194
+ </g>
195
+ {/each}
196
+ </svg>
197
+ </div>
198
+
199
+ <style>
200
+ .wrap {
201
+ position: relative;
202
+ margin: 10px 0;
203
+ height: 130px;
204
+ width: 130px;
205
+ padding: 5px;
206
+ }
207
+
208
+ .box-wrap {
209
+ width: 100%;
210
+ height: 100%;
211
+ display: grid;
212
+ grid-template-columns: repeat(3, 1fr);
213
+ grid-template-rows: repeat(3, 1fr);
214
+ gap: 5px;
215
+
216
+ background-color: var(--block-background-fill);
217
+ }
218
+
219
+ .box {
220
+ cursor: pointer;
221
+ border: none;
222
+ border: #ccc dashed 1px;
223
+ border-radius: 4px;
224
+ transition: 0.1s;
225
+ display: flex;
226
+ justify-content: center;
227
+ align-items: center;
228
+ }
229
+
230
+ .box::after {
231
+ background: none !important;
232
+ }
233
+
234
+ .expanding-box {
235
+ --mod: 3;
236
+ --border: 2;
237
+ border: orange solid 2px;
238
+ border-radius: 4px;
239
+ position: absolute;
240
+ top: 0px;
241
+ left: 0px;
242
+ width: 100%;
243
+ height: 100%;
244
+ pointer-events: none;
245
+
246
+ width: calc(100% / var(--mod));
247
+ height: calc(100% / var(--mod));
248
+ transform-origin: 50% 0;
249
+ box-sizing: border-box;
250
+ }
251
+
252
+ svg {
253
+ position: absolute;
254
+ top: 0px;
255
+ left: 0px;
256
+ width: 100%;
257
+ height: 100%;
258
+
259
+ pointer-events: none;
260
+ }
261
+
262
+ .icon-wrap {
263
+ opacity: 0.2;
264
+ height: 50%;
265
+ width: 50%;
266
+
267
+ display: flex;
268
+ justify-content: center;
269
+ align-items: center;
270
+ border-color: #ccc;
271
+ transition: 0.1s;
272
+ padding-top: 1px;
273
+ }
274
+
275
+ .wrap.expanded .icon-wrap {
276
+ opacity: 0;
277
+ }
278
+
279
+ .wrap.expanded .box {
280
+ border-color: #fff;
281
+ }
282
+
283
+ .icon-wrap.active {
284
+ opacity: 1 !important;
285
+ }
286
+
287
+ svg {
288
+ opacity: 0;
289
+ padding: 5px;
290
+ transition: 0.1s;
291
+ }
292
+ .expanded svg {
293
+ opacity: 1;
294
+ }
295
+
296
+ svg line {
297
+ stroke: #999;
298
+ stroke-width: 3px;
299
+ stroke-linecap: round;
300
+ }
301
+
302
+ svg path {
303
+ stroke-width: 3;
304
+ stroke: #999;
305
+ stroke-linejoin: round;
306
+ stroke-linecap: round;
307
+ fill: #999;
308
+ /* transition: 0.5s; */
309
+ transform-origin: 75px 75px;
310
+ }
311
+ </style>
@@ -0,0 +1,18 @@
1
+ import { SvelteComponent } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ [x: string]: never;
5
+ };
6
+ events: {
7
+ position: CustomEvent<"center" | "left" | "right" | "top-left" | "top" | "top-right" | "bottom-left" | "bottom" | "bottom-right">;
8
+ } & {
9
+ [evt: string]: CustomEvent<any>;
10
+ };
11
+ slots: {};
12
+ };
13
+ export type AnchorProps = typeof __propDef.props;
14
+ export type AnchorEvents = typeof __propDef.events;
15
+ export type AnchorSlots = typeof __propDef.slots;
16
+ export default class Anchor extends SvelteComponent<AnchorProps, AnchorEvents, AnchorSlots> {
17
+ }
18
+ export {};