triiiceratops 0.10.5 → 0.11.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.
- package/dist/{ArrowCounterClockwise-C2bPi1fL.js → ArrowCounterClockwise-CN8KGaI0.js} +1 -1
- package/dist/{X-Hy7z8RKI.js → X-i_EmjXwW.js} +376 -359
- package/dist/actions/tooltip.d.ts +10 -0
- package/dist/actions/tooltip.js +107 -0
- package/dist/{annotation_tool_point-CVRtOCN5.js → annotation_tool_point-BpZXtX5D.js} +1 -1
- package/dist/components/CanvasNavigation.svelte +57 -21
- package/dist/components/DemoHeader.svelte +81 -40
- package/dist/components/OSDViewer.svelte +2 -1
- package/dist/components/Toolbar.svelte +294 -0
- package/dist/components/Toolbar.svelte.d.ts +3 -0
- package/dist/components/TriiiceratopsViewer.svelte +7 -13
- package/dist/{image_filters_reset-BOxhPxhP.js → image_filters_reset-CyWg622b.js} +1 -1
- package/dist/paraglide/messages/_index.d.ts +6 -3
- package/dist/paraglide/messages/_index.js +6 -3
- package/dist/paraglide/messages/{settings_toggle_right_menu.d.ts → open_menu.d.ts} +1 -1
- package/dist/paraglide/messages/open_menu.js +33 -0
- package/dist/paraglide/messages/{settings_submenu_right_menu_items.d.ts → settings_submenu_toolbar.d.ts} +1 -1
- package/dist/paraglide/messages/{settings_toggle_right_menu.js → settings_submenu_toolbar.js} +9 -9
- package/dist/paraglide/messages/settings_toggle_show_toggle.d.ts +4 -0
- package/dist/paraglide/messages/{settings_submenu_right_menu_items.js → settings_toggle_show_toggle.js} +9 -9
- package/dist/paraglide/messages/settings_toggle_zoom_controls.d.ts +4 -0
- package/dist/paraglide/messages/settings_toggle_zoom_controls.js +34 -0
- package/dist/paraglide/messages/settings_toolbar_open.d.ts +4 -0
- package/dist/paraglide/messages/settings_toolbar_open.js +34 -0
- package/dist/paraglide/messages/{settings_toggle_left_menu.d.ts → settings_toolbar_position.d.ts} +1 -1
- package/dist/paraglide/messages/{settings_toggle_left_menu.js → settings_toolbar_position.js} +9 -9
- package/dist/plugins/annotation-editor.js +3 -3
- package/dist/plugins/image-manipulation.js +3 -3
- package/dist/state/viewer.svelte.d.ts +7 -2
- package/dist/state/viewer.svelte.js +26 -5
- package/dist/triiiceratops-bundle.js +2767 -2533
- package/dist/triiiceratops-element.iife.js +17 -17
- package/dist/triiiceratops.css +1 -1
- package/dist/types/config.d.ts +24 -18
- package/dist/utils/annotationAdapter.test.js +32 -29
- package/package.json +1 -1
- package/dist/components/FloatingMenu.svelte +0 -208
- package/dist/components/FloatingMenu.svelte.d.ts +0 -3
- package/dist/components/LeftFab.svelte +0 -81
- package/dist/components/LeftFab.svelte.d.ts +0 -3
package/dist/types/config.d.ts
CHANGED
|
@@ -68,7 +68,7 @@ export interface AnnotationsConfig {
|
|
|
68
68
|
*/
|
|
69
69
|
visible?: boolean;
|
|
70
70
|
}
|
|
71
|
-
export interface
|
|
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
|
|
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
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Configuration for the left menu items.
|
|
115
|
-
*/
|
|
116
|
-
leftMenu?: MenuConfig;
|
|
103
|
+
showCanvasNav?: boolean;
|
|
117
104
|
/**
|
|
118
|
-
* Whether to show the canvas navigation
|
|
105
|
+
* Whether to show the zoom controls in the canvas navigation.
|
|
119
106
|
* @default true
|
|
120
107
|
*/
|
|
121
|
-
|
|
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
|
|
2
|
-
import { parseAnnotation } from
|
|
3
|
-
describe(
|
|
4
|
-
describe(
|
|
5
|
-
it(
|
|
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
|
-
|
|
8
|
-
on:
|
|
9
|
-
label:
|
|
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(
|
|
15
|
+
expect(result.geometry.type).toBe('RECTANGLE');
|
|
16
16
|
const geometry = result.geometry;
|
|
17
|
-
if (
|
|
17
|
+
if ('x' in geometry) {
|
|
18
18
|
expect(geometry).toEqual({
|
|
19
|
-
type:
|
|
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(
|
|
27
|
+
throw new Error('Geometry should be RECTANGLE type with x, y, w, h');
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
|
-
it(
|
|
30
|
+
it('should extract SVG selector geometry', () => {
|
|
31
31
|
const annotation = {
|
|
32
|
-
|
|
32
|
+
'@id': 'http://example.org/anno2',
|
|
33
33
|
on: {
|
|
34
34
|
selector: {
|
|
35
|
-
type:
|
|
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(
|
|
44
|
+
expect(result.geometry.type).toBe('POLYGON');
|
|
45
45
|
const geometry = result.geometry;
|
|
46
|
-
if (
|
|
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(
|
|
56
|
+
throw new Error('Geometry should be POLYGON type with points');
|
|
57
57
|
}
|
|
58
58
|
});
|
|
59
|
-
it(
|
|
59
|
+
it('should handle Manifesto-style getTarget and getId methods', () => {
|
|
60
60
|
const mockManifestoAnno = {
|
|
61
|
-
getId: () =>
|
|
62
|
-
getTarget: () =>
|
|
61
|
+
getId: () => 'http://example.org/manifesto-anno',
|
|
62
|
+
getTarget: () => 'http://example.org/canvas1#xywh=5,5,50,50',
|
|
63
63
|
getBody: () => [
|
|
64
|
-
{
|
|
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(
|
|
72
|
+
expect(result?.id).toBe('http://example.org/manifesto-anno');
|
|
70
73
|
const geometry = result?.geometry;
|
|
71
|
-
if (geometry &&
|
|
74
|
+
if (geometry && 'x' in geometry) {
|
|
72
75
|
expect(geometry).toMatchObject({
|
|
73
|
-
type:
|
|
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(
|
|
83
|
+
expect(result?.body[0].value).toBe('Manifesto Body');
|
|
81
84
|
});
|
|
82
|
-
it(
|
|
85
|
+
it('should return null for invalid annotations with no geometry', () => {
|
|
83
86
|
const invalidAnno = {
|
|
84
|
-
|
|
85
|
-
on:
|
|
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,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,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}
|