triiiceratops 0.10.5 → 0.11.1

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 (40) hide show
  1. package/dist/{ArrowCounterClockwise-C2bPi1fL.js → ArrowCounterClockwise-CN8KGaI0.js} +1 -1
  2. package/dist/{X-Hy7z8RKI.js → X-i_EmjXwW.js} +376 -359
  3. package/dist/actions/tooltip.d.ts +10 -0
  4. package/dist/actions/tooltip.js +107 -0
  5. package/dist/{annotation_tool_point-CVRtOCN5.js → annotation_tool_point-BpZXtX5D.js} +1 -1
  6. package/dist/components/CanvasNavigation.svelte +57 -21
  7. package/dist/components/DemoHeader.svelte +81 -40
  8. package/dist/components/OSDViewer.svelte +2 -1
  9. package/dist/components/Toolbar.svelte +295 -0
  10. package/dist/components/Toolbar.svelte.d.ts +3 -0
  11. package/dist/components/TriiiceratopsViewer.svelte +7 -13
  12. package/dist/{image_filters_reset-BOxhPxhP.js → image_filters_reset-CyWg622b.js} +1 -1
  13. package/dist/paraglide/messages/_index.d.ts +6 -3
  14. package/dist/paraglide/messages/_index.js +6 -3
  15. package/dist/paraglide/messages/{settings_toggle_right_menu.d.ts → open_menu.d.ts} +1 -1
  16. package/dist/paraglide/messages/open_menu.js +33 -0
  17. package/dist/paraglide/messages/{settings_submenu_right_menu_items.d.ts → settings_submenu_toolbar.d.ts} +1 -1
  18. package/dist/paraglide/messages/{settings_toggle_right_menu.js → settings_submenu_toolbar.js} +9 -9
  19. package/dist/paraglide/messages/settings_toggle_show_toggle.d.ts +4 -0
  20. package/dist/paraglide/messages/{settings_submenu_right_menu_items.js → settings_toggle_show_toggle.js} +9 -9
  21. package/dist/paraglide/messages/settings_toggle_zoom_controls.d.ts +4 -0
  22. package/dist/paraglide/messages/settings_toggle_zoom_controls.js +34 -0
  23. package/dist/paraglide/messages/settings_toolbar_open.d.ts +4 -0
  24. package/dist/paraglide/messages/settings_toolbar_open.js +34 -0
  25. package/dist/paraglide/messages/{settings_toggle_left_menu.d.ts → settings_toolbar_position.d.ts} +1 -1
  26. package/dist/paraglide/messages/{settings_toggle_left_menu.js → settings_toolbar_position.js} +9 -9
  27. package/dist/plugins/annotation-editor.js +3 -3
  28. package/dist/plugins/image-manipulation.js +3 -3
  29. package/dist/state/viewer.svelte.d.ts +7 -2
  30. package/dist/state/viewer.svelte.js +26 -5
  31. package/dist/triiiceratops-bundle.js +2768 -2533
  32. package/dist/triiiceratops-element.iife.js +17 -17
  33. package/dist/triiiceratops.css +1 -1
  34. package/dist/types/config.d.ts +24 -18
  35. package/dist/utils/annotationAdapter.test.js +32 -29
  36. package/package.json +1 -1
  37. package/dist/components/FloatingMenu.svelte +0 -208
  38. package/dist/components/FloatingMenu.svelte.d.ts +0 -3
  39. package/dist/components/LeftFab.svelte +0 -81
  40. package/dist/components/LeftFab.svelte.d.ts +0 -3
@@ -68,7 +68,7 @@ export interface AnnotationsConfig {
68
68
  */
69
69
  visible?: boolean;
70
70
  }
71
- export interface MenuConfig {
71
+ export interface ToolbarConfig {
72
72
  /**
73
73
  * Whether the Search button is shown in this menu.
74
74
  * @default true
@@ -97,28 +97,15 @@ export interface MenuConfig {
97
97
  }
98
98
  export interface ViewerConfig {
99
99
  /**
100
- * Whether the right floating menu is visible.
101
- * @default true
102
- */
103
- showRightMenu?: boolean;
104
- /**
105
- * Configuration for the right menu items.
106
- */
107
- rightMenu?: MenuConfig;
108
- /**
109
- * Whether the left FAB (plugins menu) is visible.
100
+ * Whether to show the canvas navigation arrows/controls.
110
101
  * @default true
111
102
  */
112
- showLeftMenu?: boolean;
113
- /**
114
- * Configuration for the left menu items.
115
- */
116
- leftMenu?: MenuConfig;
103
+ showCanvasNav?: boolean;
117
104
  /**
118
- * Whether to show the canvas navigation arrows/controls.
105
+ * Whether to show the zoom controls in the canvas navigation.
119
106
  * @default true
120
107
  */
121
- showCanvasNav?: boolean;
108
+ showZoomControls?: boolean;
122
109
  /**
123
110
  * Configuration for the thumbnail gallery pane.
124
111
  */
@@ -140,4 +127,23 @@ export interface ViewerConfig {
140
127
  * @default false
141
128
  */
142
129
  transparentBackground?: boolean;
130
+ /**
131
+ * Whether the toolbar open/close toggle button is visible.
132
+ * @default true
133
+ */
134
+ showToggle?: boolean;
135
+ /**
136
+ * Whether the toolbar is currently expanded/open.
137
+ * @default false
138
+ */
139
+ toolbarOpen?: boolean;
140
+ /**
141
+ * Which side the toolbar should appear on.
142
+ * @default 'left'
143
+ */
144
+ toolbarPosition?: 'left' | 'right' | 'top';
145
+ /**
146
+ * Configuration for the toolbar items.
147
+ */
148
+ toolbar?: ToolbarConfig;
143
149
  }
@@ -1,22 +1,22 @@
1
- import { describe, it, expect } from "vitest";
2
- import { parseAnnotation } from "./annotationAdapter";
3
- describe("annotationAdapter", () => {
4
- describe("parseAnnotation", () => {
5
- it("should correctly parse a simple xywh string target", () => {
1
+ import { describe, it, expect } from 'vitest';
2
+ import { parseAnnotation } from './annotationAdapter';
3
+ describe('annotationAdapter', () => {
4
+ describe('parseAnnotation', () => {
5
+ it('should correctly parse a simple xywh string target', () => {
6
6
  const annotation = {
7
- "@id": "http://example.org/anno1",
8
- on: "http://example.org/image1#xywh=10,20,100,200",
9
- label: "Test Annotation",
7
+ '@id': 'http://example.org/anno1',
8
+ on: 'http://example.org/image1#xywh=10,20,100,200',
9
+ label: 'Test Annotation',
10
10
  };
11
11
  const result = parseAnnotation(annotation, 0);
12
12
  expect(result).not.toBeNull();
13
13
  if (!result)
14
14
  return;
15
- expect(result.geometry.type).toBe("RECTANGLE");
15
+ expect(result.geometry.type).toBe('RECTANGLE');
16
16
  const geometry = result.geometry;
17
- if ("x" in geometry) {
17
+ if ('x' in geometry) {
18
18
  expect(geometry).toEqual({
19
- type: "RECTANGLE",
19
+ type: 'RECTANGLE',
20
20
  x: 10,
21
21
  y: 20,
22
22
  w: 100,
@@ -24,15 +24,15 @@ describe("annotationAdapter", () => {
24
24
  });
25
25
  }
26
26
  else {
27
- throw new Error("Geometry should be RECTANGLE type with x, y, w, h");
27
+ throw new Error('Geometry should be RECTANGLE type with x, y, w, h');
28
28
  }
29
29
  });
30
- it("should extract SVG selector geometry", () => {
30
+ it('should extract SVG selector geometry', () => {
31
31
  const annotation = {
32
- "@id": "http://example.org/anno2",
32
+ '@id': 'http://example.org/anno2',
33
33
  on: {
34
34
  selector: {
35
- type: "SvgSelector",
35
+ type: 'SvgSelector',
36
36
  value: '<svg><polygon points="10,10 50,10 50,50 10,50" /></svg>',
37
37
  },
38
38
  },
@@ -41,9 +41,9 @@ describe("annotationAdapter", () => {
41
41
  expect(result).not.toBeNull();
42
42
  if (!result)
43
43
  return;
44
- expect(result.geometry.type).toBe("POLYGON");
44
+ expect(result.geometry.type).toBe('POLYGON');
45
45
  const geometry = result.geometry;
46
- if ("points" in geometry) {
46
+ if ('points' in geometry) {
47
47
  expect(geometry.points).toHaveLength(4);
48
48
  expect(geometry.points).toEqual([
49
49
  [10, 10],
@@ -53,36 +53,39 @@ describe("annotationAdapter", () => {
53
53
  ]);
54
54
  }
55
55
  else {
56
- throw new Error("Geometry should be POLYGON type with points");
56
+ throw new Error('Geometry should be POLYGON type with points');
57
57
  }
58
58
  });
59
- it("should handle Manifesto-style getTarget and getId methods", () => {
59
+ it('should handle Manifesto-style getTarget and getId methods', () => {
60
60
  const mockManifestoAnno = {
61
- getId: () => "http://example.org/manifesto-anno",
62
- getTarget: () => "http://example.org/canvas1#xywh=5,5,50,50",
61
+ getId: () => 'http://example.org/manifesto-anno',
62
+ getTarget: () => 'http://example.org/canvas1#xywh=5,5,50,50',
63
63
  getBody: () => [
64
- { getValue: () => "Manifesto Body", getFormat: () => "text/plain" },
64
+ {
65
+ getValue: () => 'Manifesto Body',
66
+ getFormat: () => 'text/plain',
67
+ },
65
68
  ],
66
69
  };
67
70
  // @ts-ignore - mocking complex object
68
71
  const result = parseAnnotation(mockManifestoAnno, 2);
69
- expect(result?.id).toBe("http://example.org/manifesto-anno");
72
+ expect(result?.id).toBe('http://example.org/manifesto-anno');
70
73
  const geometry = result?.geometry;
71
- if (geometry && "x" in geometry) {
74
+ if (geometry && 'x' in geometry) {
72
75
  expect(geometry).toMatchObject({
73
- type: "RECTANGLE",
76
+ type: 'RECTANGLE',
74
77
  x: 5,
75
78
  y: 5,
76
79
  w: 50,
77
80
  h: 50,
78
81
  });
79
82
  }
80
- expect(result?.body.value).toBe("Manifesto Body");
83
+ expect(result?.body[0].value).toBe('Manifesto Body');
81
84
  });
82
- it("should return null for invalid annotations with no geometry", () => {
85
+ it('should return null for invalid annotations with no geometry', () => {
83
86
  const invalidAnno = {
84
- "@id": "bad-anno",
85
- on: "http://example.org/canvas1", // No media fragment or selector
87
+ '@id': 'bad-anno',
88
+ on: 'http://example.org/canvas1', // No media fragment or selector
86
89
  };
87
90
  const result = parseAnnotation(invalidAnno, 3);
88
91
  expect(result).toBeNull();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "triiiceratops",
3
- "version": "0.10.5",
3
+ "version": "0.11.1",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,208 +0,0 @@
1
- <script lang="ts">
2
- import { getContext } from 'svelte';
3
- import ChatCenteredText from 'phosphor-svelte/lib/ChatCenteredText';
4
- import CornersIn from 'phosphor-svelte/lib/CornersIn';
5
- import CornersOut from 'phosphor-svelte/lib/CornersOut';
6
- import X from 'phosphor-svelte/lib/X';
7
- import Info from 'phosphor-svelte/lib/Info';
8
- import MagnifyingGlass from 'phosphor-svelte/lib/MagnifyingGlass';
9
- import List from 'phosphor-svelte/lib/List';
10
- import Slideshow from 'phosphor-svelte/lib/Slideshow';
11
- import { VIEWER_STATE_KEY, type ViewerState } from '../state/viewer.svelte';
12
- import { m } from '../state/i18n.svelte';
13
-
14
- const viewerState = getContext<ViewerState>(VIEWER_STATE_KEY);
15
-
16
- const menuConfig = $derived(viewerState.config.rightMenu || {});
17
-
18
- const showSearch = $derived(menuConfig.showSearch !== false);
19
- const showGallery = $derived(menuConfig.showGallery !== false);
20
- const showFullscreen = $derived(menuConfig.showFullscreen !== false);
21
- const showAnnotations = $derived(menuConfig.showAnnotations !== false);
22
- const showInfo = $derived(menuConfig.showInfo !== false);
23
-
24
- const activeButtons = $derived(
25
- [
26
- showSearch ? 'search' : null,
27
- showGallery ? 'gallery' : null,
28
- showFullscreen ? 'fullscreen' : null,
29
- showAnnotations ? 'annotations' : null,
30
- showInfo ? 'info' : null,
31
- ].filter((b): b is string => b !== null),
32
- );
33
- </script>
34
-
35
- {#snippet searchBtn()}
36
- <button
37
- aria-label={m.toggle_search()}
38
- class={[
39
- 'btn btn-circle btn-lg shadow-lg',
40
- viewerState.showSearchPanel ? 'btn-primary' : 'btn-neutral',
41
- ]}
42
- onclick={() => viewerState.toggleSearchPanel()}
43
- >
44
- <MagnifyingGlass size={28} weight="bold" />
45
- </button>
46
- {/snippet}
47
-
48
- {#snippet galleryBtn()}
49
- <button
50
- aria-label={viewerState.showThumbnailGallery
51
- ? m.hide_gallery()
52
- : m.show_gallery()}
53
- class="btn btn-lg btn-circle shadow-lg {viewerState.showThumbnailGallery
54
- ? 'btn-info'
55
- : 'btn-neutral'}"
56
- onclick={() => viewerState.toggleThumbnailGallery()}
57
- >
58
- <Slideshow size={28} weight="bold" />
59
- </button>
60
- {/snippet}
61
-
62
- {#snippet fullscreenBtn()}
63
- <button
64
- class="btn btn-circle btn-lg shadow-lg transition-all duration-300 ease-out {viewerState.isFullScreen
65
- ? 'btn-warning'
66
- : 'btn-neutral'}"
67
- onclick={() => viewerState.toggleFullScreen()}
68
- >
69
- {#if viewerState.isFullScreen}
70
- <CornersIn size={28} weight="bold" />
71
- {:else}
72
- <CornersOut size={28} weight="bold" />
73
- {/if}
74
- </button>
75
- {/snippet}
76
-
77
- {#snippet annotationsBtn()}
78
- <button
79
- aria-label={m.toggle_annotations()}
80
- class="btn btn-lg btn-circle shadow-lg {viewerState.showAnnotations
81
- ? 'btn-error'
82
- : 'btn-neutral'}"
83
- onclick={() => viewerState.toggleAnnotations()}
84
- >
85
- <ChatCenteredText size={28} weight="bold" />
86
- </button>
87
- {/snippet}
88
-
89
- {#snippet infoBtn()}
90
- <button
91
- aria-label={m.toggle_metadata()}
92
- class="btn btn-lg btn-circle shadow-lg {viewerState.showMetadataDialog
93
- ? 'btn-info'
94
- : 'btn-neutral'}"
95
- onclick={() => viewerState.toggleMetadataDialog()}
96
- >
97
- <Info size={28} weight="bold" />
98
- </button>
99
- {/snippet}
100
-
101
- {#if activeButtons.length === 1}
102
- <div class="absolute z-600 bottom-6 right-6">
103
- {#if showSearch}
104
- <div class="tooltip tooltip-left" data-tip={m.search()}>
105
- {@render searchBtn()}
106
- </div>
107
- {/if}
108
- {#if showGallery}
109
- <div
110
- class="tooltip tooltip-left"
111
- data-tip={viewerState.showThumbnailGallery
112
- ? m.hide_gallery()
113
- : m.show_gallery()}
114
- >
115
- {@render galleryBtn()}
116
- </div>
117
- {/if}
118
- {#if showFullscreen}
119
- <div
120
- class="tooltip tooltip-left"
121
- data-tip={viewerState.isFullScreen
122
- ? m.exit_full_screen()
123
- : m.enter_full_screen()}
124
- >
125
- {@render fullscreenBtn()}
126
- </div>
127
- {/if}
128
- {#if showAnnotations}
129
- <div
130
- class="tooltip tooltip-top"
131
- data-tip={viewerState.showAnnotations
132
- ? m.hide_annotations()
133
- : m.show_annotations()}
134
- >
135
- {@render annotationsBtn()}
136
- </div>
137
- {/if}
138
- {#if showInfo}
139
- <div class="tooltip tooltip-top" data-tip={m.metadata()}>
140
- {@render infoBtn()}
141
- </div>
142
- {/if}
143
- </div>
144
- {:else if activeButtons.length > 1}
145
- <div
146
- class="fab fab-flower fab-top-left absolute z-600 pointer-events-auto bottom-6 right-6"
147
- >
148
- <!-- Trigger button: focusable div (Safari bug workaround) - hidden when FAB is open -->
149
- <div
150
- tabindex="0"
151
- role="button"
152
- class="btn btn-lg btn-primary btn-circle shadow-xl"
153
- >
154
- <span class="sr-only">{m.menu()}</span>
155
- <List size={32} weight="bold" />
156
- </div>
157
-
158
- <!-- fab-main-action: replaces the trigger button when FAB is open -->
159
- <div class="fab-main-action tooltip tooltip-top" data-tip={m.search()}>
160
- {@render searchBtn()}
161
- </div>
162
-
163
- <!-- buttons that show up when FAB is open (max 4 for flower layout) -->
164
-
165
- <!-- Gallery Toggle -->
166
- {#if showGallery}
167
- <div
168
- class="tooltip tooltip-left"
169
- data-tip={viewerState.showThumbnailGallery
170
- ? m.hide_gallery()
171
- : m.show_gallery()}
172
- >
173
- {@render galleryBtn()}
174
- </div>
175
- {/if}
176
-
177
- <!-- Full Screen Toggle -->
178
- {#if showFullscreen}
179
- <div
180
- class="tooltip tooltip-left"
181
- data-tip={viewerState.isFullScreen
182
- ? m.exit_full_screen()
183
- : m.enter_full_screen()}
184
- >
185
- {@render fullscreenBtn()}
186
- </div>
187
- {/if}
188
-
189
- <!-- Annotations Toggle -->
190
- {#if showAnnotations}
191
- <div
192
- class="tooltip tooltip-top"
193
- data-tip={viewerState.showAnnotations
194
- ? m.hide_annotations()
195
- : m.show_annotations()}
196
- >
197
- {@render annotationsBtn()}
198
- </div>
199
- {/if}
200
-
201
- <!-- Metadata Toggle -->
202
- {#if showInfo}
203
- <div class="tooltip tooltip-top" data-tip={m.metadata()}>
204
- {@render infoBtn()}
205
- </div>
206
- {/if}
207
- </div>
208
- {/if}
@@ -1,3 +0,0 @@
1
- declare const FloatingMenu: import("svelte").Component<Record<string, never>, {}, "">;
2
- type FloatingMenu = ReturnType<typeof FloatingMenu>;
3
- export default FloatingMenu;
@@ -1,81 +0,0 @@
1
- <script lang="ts">
2
- import { getContext } from 'svelte';
3
- import PuzzlePiece from 'phosphor-svelte/lib/PuzzlePiece';
4
- import X from 'phosphor-svelte/lib/X';
5
- import { VIEWER_STATE_KEY, type ViewerState } from '../state/viewer.svelte';
6
- import { m, language } from '../state/i18n.svelte';
7
-
8
- const viewerState = getContext<ViewerState>(VIEWER_STATE_KEY);
9
-
10
- let sortedPluginButtons = $derived.by(() => {
11
- // Read language to trigger re-evaluation
12
- language.current;
13
- return [...viewerState.pluginMenuButtons].sort(
14
- (a, b) => (a.order ?? 100) - (b.order ?? 100),
15
- );
16
- });
17
-
18
- let isOpen = $state(false);
19
-
20
- let pluginsTooltip = $derived.by(() => {
21
- language.current;
22
- return m.plugins_tooltip();
23
- });
24
-
25
- function toggleOpen() {
26
- isOpen = !isOpen;
27
- }
28
- </script>
29
-
30
- {#if sortedPluginButtons.length > 0}
31
- <div
32
- class="absolute left-6 bottom-6 z-50 pointer-events-auto flex flex-col items-start transition-all duration-300"
33
- >
34
- <!-- Plugin Buttons Stack -->
35
- <div
36
- class="flex flex-col-reverse gap-3 mb-3 transition-all duration-300 origin-bottom {isOpen
37
- ? 'opacity-100 translate-y-0 scale-100'
38
- : 'opacity-0 translate-y-4 scale-90 pointer-events-none'}"
39
- >
40
- {#each sortedPluginButtons as button (button.id)}
41
- {@const Icon = button.icon}
42
- {@const tooltip =
43
- // @ts-ignore - access message dynamically
44
- typeof m[button.tooltip] === 'function'
45
- ? // @ts-ignore
46
- m[button.tooltip]()
47
- : button.tooltip}
48
- <div class="tooltip tooltip-right" data-tip={tooltip}>
49
- <button
50
- aria-label={button.tooltip}
51
- class="btn btn-lg btn-circle shadow-lg {button.isActive?.()
52
- ? (button.activeClass ?? 'btn-primary')
53
- : 'btn-neutral'}"
54
- onclick={() => {
55
- button.onClick();
56
- isOpen = false;
57
- }}
58
- >
59
- <Icon size={28} weight="bold" />
60
- </button>
61
- </div>
62
- {/each}
63
- </div>
64
-
65
- <div class="tooltip tooltip-right" data-tip={pluginsTooltip}>
66
- <button
67
- class="btn btn-lg btn-secondary btn-circle shadow-xl transition-transform duration-300 {isOpen
68
- ? 'rotate-90'
69
- : ''}"
70
- aria-label={pluginsTooltip}
71
- onclick={toggleOpen}
72
- >
73
- {#if isOpen}
74
- <X size={28} weight="bold" />
75
- {:else}
76
- <PuzzlePiece size={28} weight="bold" />
77
- {/if}
78
- </button>
79
- </div>
80
- </div>
81
- {/if}
@@ -1,3 +0,0 @@
1
- declare const LeftFab: import("svelte").Component<Record<string, never>, {}, "">;
2
- type LeftFab = ReturnType<typeof LeftFab>;
3
- export default LeftFab;