triiiceratops 0.11.2 → 0.12.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.
- package/dist/{ArrowCounterClockwise-B_hB6fl1.js → ArrowCounterClockwise-CM9mGGcp.js} +1 -1
- package/dist/X-Bn7S7vUL.js +963 -0
- package/dist/{annotation_tool_point-rZvdAtGa.js → annotation_tool_point-LoRp_nrI.js} +1 -1
- package/dist/components/DemoHeader.svelte +69 -0
- package/dist/components/MetadataDialog.svelte +20 -4
- package/dist/components/OSDViewer.svelte +31 -1
- package/dist/components/ThumbnailGallery.svelte +226 -35
- package/dist/components/Toolbar.svelte +102 -3
- package/dist/components/TriiiceratopsViewer.svelte +37 -12
- package/dist/{image_filters_reset-CAUhlDWt.js → image_filters_reset-CmWuQiOc.js} +1 -1
- package/dist/paraglide/messages/_index.d.ts +9 -0
- package/dist/paraglide/messages/_index.js +10 -1
- package/dist/paraglide/messages/settings_toggle_show_viewing_mode.d.ts +4 -0
- package/dist/paraglide/messages/settings_toggle_show_viewing_mode.js +33 -0
- package/dist/paraglide/messages/show_mode_toggle.d.ts +4 -0
- package/dist/paraglide/messages/show_mode_toggle.js +33 -0
- package/dist/paraglide/messages/toggle_single_page_mode.d.ts +4 -0
- package/dist/paraglide/messages/toggle_single_page_mode.js +33 -0
- package/dist/paraglide/messages/toggle_two_page_mode.d.ts +4 -0
- package/dist/paraglide/messages/toggle_two_page_mode.js +33 -0
- package/dist/paraglide/messages/two_page_mode.d.ts +4 -0
- package/dist/paraglide/messages/two_page_mode.js +33 -0
- package/dist/paraglide/messages/viewing_mode_individuals.d.ts +4 -0
- package/dist/paraglide/messages/viewing_mode_individuals.js +33 -0
- package/dist/paraglide/messages/viewing_mode_label.d.ts +4 -0
- package/dist/paraglide/messages/viewing_mode_label.js +33 -0
- package/dist/paraglide/messages/viewing_mode_paged.d.ts +4 -0
- package/dist/paraglide/messages/viewing_mode_paged.js +33 -0
- package/dist/paraglide/messages/viewing_mode_shift_pairing.d.ts +4 -0
- package/dist/paraglide/messages/viewing_mode_shift_pairing.js +33 -0
- package/dist/plugins/annotation-editor.js +3 -3
- package/dist/plugins/image-manipulation.js +3 -3
- package/dist/state/viewer.svelte.d.ts +18 -0
- package/dist/state/viewer.svelte.js +142 -8
- package/dist/triiiceratops-bundle.js +3068 -2514
- package/dist/triiiceratops-element.iife.js +25 -25
- package/dist/triiiceratops.css +1 -1
- package/dist/types/config.d.ts +33 -0
- package/package.json +1 -1
- package/dist/X-Boj9jj2h.js +0 -890
|
@@ -434,6 +434,49 @@
|
|
|
434
434
|
/>
|
|
435
435
|
</label>
|
|
436
436
|
</li>
|
|
437
|
+
<li>
|
|
438
|
+
<label class="label cursor-pointer py-1">
|
|
439
|
+
<span class="label-text"
|
|
440
|
+
>{m.settings_toggle_show_viewing_mode()}</span
|
|
441
|
+
>
|
|
442
|
+
<input
|
|
443
|
+
type="checkbox"
|
|
444
|
+
class="checkbox checkbox-xs"
|
|
445
|
+
checked={config.toolbar
|
|
446
|
+
?.showViewingMode ?? true}
|
|
447
|
+
onchange={(e) => {
|
|
448
|
+
if (!config.toolbar)
|
|
449
|
+
config.toolbar = {};
|
|
450
|
+
config.toolbar.showViewingMode =
|
|
451
|
+
e.currentTarget.checked;
|
|
452
|
+
}}
|
|
453
|
+
/>
|
|
454
|
+
</label>
|
|
455
|
+
</li>
|
|
456
|
+
<li>
|
|
457
|
+
<label class="label cursor-pointer py-1">
|
|
458
|
+
<span class="label-text"
|
|
459
|
+
>{m.viewing_mode_label()}</span
|
|
460
|
+
>
|
|
461
|
+
<select
|
|
462
|
+
class="select select-bordered select-xs"
|
|
463
|
+
value={config.viewingMode ??
|
|
464
|
+
'individuals'}
|
|
465
|
+
onchange={(e) => {
|
|
466
|
+
config.viewingMode = (
|
|
467
|
+
e.currentTarget as HTMLSelectElement
|
|
468
|
+
).value as 'individuals' | 'paged';
|
|
469
|
+
}}
|
|
470
|
+
>
|
|
471
|
+
<option value="individuals"
|
|
472
|
+
>{m.viewing_mode_individuals()}</option
|
|
473
|
+
>
|
|
474
|
+
<option value="paged"
|
|
475
|
+
>{m.viewing_mode_paged()}</option
|
|
476
|
+
>
|
|
477
|
+
</select>
|
|
478
|
+
</label>
|
|
479
|
+
</li>
|
|
437
480
|
</ul>
|
|
438
481
|
</details>
|
|
439
482
|
</li>
|
|
@@ -534,6 +577,32 @@
|
|
|
534
577
|
</select>
|
|
535
578
|
</label>
|
|
536
579
|
</li>
|
|
580
|
+
<li>
|
|
581
|
+
<label class="label cursor-pointer py-1 gap-2">
|
|
582
|
+
<span class="label-text"
|
|
583
|
+
>Thumbnail Height</span
|
|
584
|
+
>
|
|
585
|
+
<input
|
|
586
|
+
type="range"
|
|
587
|
+
min="50"
|
|
588
|
+
max="300"
|
|
589
|
+
value={config.gallery?.fixedHeight ??
|
|
590
|
+
120}
|
|
591
|
+
class="range range-xs range-primary w-24"
|
|
592
|
+
oninput={(e) => {
|
|
593
|
+
if (!config.gallery)
|
|
594
|
+
config.gallery = {};
|
|
595
|
+
config.gallery.fixedHeight =
|
|
596
|
+
parseInt(e.currentTarget.value);
|
|
597
|
+
}}
|
|
598
|
+
/>
|
|
599
|
+
<span
|
|
600
|
+
class="text-xs opacity-50 w-8 text-right"
|
|
601
|
+
>{config.gallery?.fixedHeight ??
|
|
602
|
+
120}px</span
|
|
603
|
+
>
|
|
604
|
+
</label>
|
|
605
|
+
</li>
|
|
537
606
|
</ul>
|
|
538
607
|
</details>
|
|
539
608
|
</li>
|
|
@@ -103,20 +103,20 @@
|
|
|
103
103
|
</div>
|
|
104
104
|
{/if}
|
|
105
105
|
|
|
106
|
-
<dl
|
|
106
|
+
<dl>
|
|
107
107
|
{#if attribution}
|
|
108
108
|
<dt class="font-bold text-lg opacity-70 mt-6">
|
|
109
109
|
{m.attribution()}
|
|
110
110
|
</dt>
|
|
111
111
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
112
|
-
<dd class="text-sm ps-
|
|
112
|
+
<dd class="text-sm ps-4">{@html attribution}</dd>
|
|
113
113
|
{/if}
|
|
114
114
|
|
|
115
115
|
{#if license}
|
|
116
116
|
<dt class="font-bold text-lg opacity-70 mt-6">
|
|
117
117
|
{m.license()}
|
|
118
118
|
</dt>
|
|
119
|
-
<dd class="text-sm ps-
|
|
119
|
+
<dd class="text-sm ps-4">
|
|
120
120
|
<a
|
|
121
121
|
href={license}
|
|
122
122
|
target="_blank"
|
|
@@ -131,8 +131,24 @@
|
|
|
131
131
|
{item.label}
|
|
132
132
|
</dt>
|
|
133
133
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
134
|
-
<dd class="text-sm ps-
|
|
134
|
+
<dd class="text-sm ps-4">{@html item.value}</dd>
|
|
135
135
|
{/each}
|
|
136
|
+
|
|
137
|
+
{#if viewerState.manifestId}
|
|
138
|
+
<dt class="font-bold text-lg opacity-70 mt-6">
|
|
139
|
+
{m.iiif_manifest_label()}
|
|
140
|
+
</dt>
|
|
141
|
+
<dd class="text-sm ps-4">
|
|
142
|
+
<a
|
|
143
|
+
href={viewerState.manifestId}
|
|
144
|
+
target="_blank"
|
|
145
|
+
rel="noreferrer"
|
|
146
|
+
class="link link-primary break-all"
|
|
147
|
+
>
|
|
148
|
+
{viewerState.manifestId}
|
|
149
|
+
</a>
|
|
150
|
+
</dd>
|
|
151
|
+
{/if}
|
|
136
152
|
</dl>
|
|
137
153
|
</div>
|
|
138
154
|
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
// Track OSD state changes for reactivity
|
|
19
19
|
let osdVersion = $state(0);
|
|
20
|
+
// Track last opened tile source to prevent unnecessary resets
|
|
21
|
+
let lastTileSourceStr = '';
|
|
20
22
|
|
|
21
23
|
// Get all annotations for current canvas (manifest + search)
|
|
22
24
|
let allAnnotations = $derived.by(() => {
|
|
@@ -231,7 +233,35 @@
|
|
|
231
233
|
$effect(() => {
|
|
232
234
|
if (!viewer || !tileSources) return;
|
|
233
235
|
|
|
234
|
-
|
|
236
|
+
// Check if source actually changed to avoid resetting zoom
|
|
237
|
+
const currentStr = JSON.stringify(tileSources);
|
|
238
|
+
if (currentStr === lastTileSourceStr) return;
|
|
239
|
+
lastTileSourceStr = currentStr;
|
|
240
|
+
|
|
241
|
+
if (
|
|
242
|
+
viewerState.viewingMode === 'paged' &&
|
|
243
|
+
tileSources instanceof Array &&
|
|
244
|
+
tileSources.length === 2
|
|
245
|
+
) {
|
|
246
|
+
const secondPageLocation = 1.025;
|
|
247
|
+
const twoPageSpread = [
|
|
248
|
+
{
|
|
249
|
+
tileSource: tileSources[0],
|
|
250
|
+
x: 0,
|
|
251
|
+
y: 0,
|
|
252
|
+
width: 1.0,
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
tileSource: tileSources[1],
|
|
256
|
+
x: secondPageLocation, // small gap between pages
|
|
257
|
+
y: 0,
|
|
258
|
+
width: 1.0,
|
|
259
|
+
},
|
|
260
|
+
];
|
|
261
|
+
viewer.open(twoPageSpread);
|
|
262
|
+
} else {
|
|
263
|
+
viewer.open(tileSources);
|
|
264
|
+
}
|
|
235
265
|
});
|
|
236
266
|
</script>
|
|
237
267
|
|
|
@@ -61,6 +61,28 @@
|
|
|
61
61
|
};
|
|
62
62
|
let galleryElement: HTMLElement | null = $state(null);
|
|
63
63
|
|
|
64
|
+
// Initialize position and size from config if available (only once on mount)
|
|
65
|
+
$effect(() => {
|
|
66
|
+
if (
|
|
67
|
+
viewerState.config.gallery?.width &&
|
|
68
|
+
viewerState.config.gallery?.height
|
|
69
|
+
) {
|
|
70
|
+
viewerState.gallerySize = {
|
|
71
|
+
width: viewerState.config.gallery.width,
|
|
72
|
+
height: viewerState.config.gallery.height,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (
|
|
76
|
+
viewerState.config.gallery?.x !== undefined &&
|
|
77
|
+
viewerState.config.gallery?.y !== undefined
|
|
78
|
+
) {
|
|
79
|
+
viewerState.galleryPosition = {
|
|
80
|
+
x: viewerState.config.gallery.x,
|
|
81
|
+
y: viewerState.config.gallery.y,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
64
86
|
// Generate thumbnail data
|
|
65
87
|
let thumbnails = $derived.by(() => {
|
|
66
88
|
if (!canvases || !Array.isArray(canvases))
|
|
@@ -322,7 +344,26 @@
|
|
|
322
344
|
}
|
|
323
345
|
|
|
324
346
|
function selectCanvas(canvasId: string) {
|
|
325
|
-
viewerState.
|
|
347
|
+
if (viewerState.viewingMode === 'paged') {
|
|
348
|
+
const canvasIndex = thumbnails.findIndex((t) => t.id === canvasId);
|
|
349
|
+
const singlePages = viewerState.pagedOffset;
|
|
350
|
+
// If within single pages section, select directly
|
|
351
|
+
if (canvasIndex < singlePages) {
|
|
352
|
+
viewerState.setCanvas(canvasId);
|
|
353
|
+
} else {
|
|
354
|
+
// Check if this is a left-hand page (start of a pair)
|
|
355
|
+
const pairPosition = (canvasIndex - singlePages) % 2;
|
|
356
|
+
if (pairPosition === 0) {
|
|
357
|
+
viewerState.setCanvas(canvasId);
|
|
358
|
+
} else {
|
|
359
|
+
// Right-hand page, select the left page of this pair
|
|
360
|
+
const prevCanvas = thumbnails[canvasIndex - 1];
|
|
361
|
+
viewerState.setCanvas(prevCanvas.id);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
} else {
|
|
365
|
+
viewerState.setCanvas(canvasId);
|
|
366
|
+
}
|
|
326
367
|
}
|
|
327
368
|
|
|
328
369
|
// State for docking
|
|
@@ -384,6 +425,8 @@
|
|
|
384
425
|
(dockSide === 'none' && viewerState.gallerySize.height < 320),
|
|
385
426
|
);
|
|
386
427
|
|
|
428
|
+
let fixedHeight = $derived(viewerState.galleryFixedHeight);
|
|
429
|
+
|
|
387
430
|
function startDrag(e: MouseEvent) {
|
|
388
431
|
if (!draggable) return; // Dragging disabled in config
|
|
389
432
|
if ((e.target as HTMLElement).closest('.resize-handle')) return; // Don't drag if resizing
|
|
@@ -444,6 +487,53 @@
|
|
|
444
487
|
dockSide = 'none';
|
|
445
488
|
}
|
|
446
489
|
}
|
|
490
|
+
|
|
491
|
+
// Grouped thumbnail mode (for two-page mode)
|
|
492
|
+
const groupedThumbnailIndices = $derived.by(() => {
|
|
493
|
+
const indices: number[] = [];
|
|
494
|
+
if (viewerState.viewingMode === 'paged' && canvases) {
|
|
495
|
+
// Single pages at the start: pagedOffset (default 0, shifted = 1)
|
|
496
|
+
const singlePages = viewerState.pagedOffset;
|
|
497
|
+
// Add indices for single pages
|
|
498
|
+
for (let i = 0; i < singlePages && i < canvases.length; i++) {
|
|
499
|
+
indices.push(i);
|
|
500
|
+
}
|
|
501
|
+
// Add indices for paired pages (step by 2 starting from singlePages)
|
|
502
|
+
for (let i = singlePages; i < canvases.length; i += 2) {
|
|
503
|
+
indices.push(i);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return indices;
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
const groupedThumbnails = $derived.by(() => {
|
|
510
|
+
const groups: Array<{
|
|
511
|
+
id: string;
|
|
512
|
+
label: string;
|
|
513
|
+
srcs: string[];
|
|
514
|
+
index: number;
|
|
515
|
+
}> = [];
|
|
516
|
+
const thumbs = thumbnails;
|
|
517
|
+
const singlePages = viewerState.pagedOffset;
|
|
518
|
+
for (const i of groupedThumbnailIndices) {
|
|
519
|
+
const first = thumbs[i];
|
|
520
|
+
// Only pair if we're past the single pages section
|
|
521
|
+
const second = i < singlePages ? null : thumbs[i + 1];
|
|
522
|
+
const groupId = first.id;
|
|
523
|
+
const groupLabel = first.label;
|
|
524
|
+
const groupSrcs = [first.src];
|
|
525
|
+
if (second) {
|
|
526
|
+
groupSrcs.push(second.src);
|
|
527
|
+
}
|
|
528
|
+
groups.push({
|
|
529
|
+
id: groupId,
|
|
530
|
+
label: groupLabel,
|
|
531
|
+
srcs: groupSrcs,
|
|
532
|
+
index: i,
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
return groups;
|
|
536
|
+
});
|
|
447
537
|
</script>
|
|
448
538
|
|
|
449
539
|
{#if viewerState.showThumbnailGallery}
|
|
@@ -514,44 +604,145 @@
|
|
|
514
604
|
: 'grid gap-2'}
|
|
515
605
|
style={isHorizontal
|
|
516
606
|
? ''
|
|
517
|
-
:
|
|
607
|
+
: `grid-template-columns: repeat(auto-fill, minmax(${fixedHeight}px, 1fr));`}
|
|
518
608
|
>
|
|
519
|
-
{#
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
:
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
609
|
+
{#if viewerState.viewingMode === 'paged'}
|
|
610
|
+
<!-- grouped thumbnail display -->
|
|
611
|
+
{#each groupedThumbnails as thumbGroup}
|
|
612
|
+
<button
|
|
613
|
+
class="group flex flex-col gap-1 p-1 rounded hover:bg-base-200 transition-colors text-left relative shrink-0 {isHorizontal
|
|
614
|
+
? 'w-auto'
|
|
615
|
+
: thumbGroup.srcs.length > 1
|
|
616
|
+
? 'col-span-2'
|
|
617
|
+
: ''} {viewerState.canvasId === thumbGroup.id
|
|
618
|
+
? 'ring-2 ring-primary bg-primary/5'
|
|
619
|
+
: ''}"
|
|
620
|
+
style={isHorizontal
|
|
621
|
+
? `height: ${fixedHeight + 24}px`
|
|
622
|
+
: ''}
|
|
623
|
+
onclick={() => selectCanvas(thumbGroup.id)}
|
|
624
|
+
data-id={thumbGroup.id}
|
|
625
|
+
aria-label="Select canvas {thumbGroup.label}"
|
|
532
626
|
>
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
627
|
+
<div
|
|
628
|
+
class="{isHorizontal
|
|
629
|
+
? 'h-full w-auto flex-row'
|
|
630
|
+
: thumbGroup.srcs.length > 1
|
|
631
|
+
? 'aspect-3/2 w-full'
|
|
632
|
+
: 'aspect-3/4 w-full'} bg-base-300 rounded overflow-hidden relative flex items-center justify-center gap-px"
|
|
633
|
+
style={isHorizontal
|
|
634
|
+
? `height: ${fixedHeight}px`
|
|
635
|
+
: ''}
|
|
636
|
+
>
|
|
637
|
+
<div
|
|
638
|
+
class="flex items-center justify-center overflow-hidden {isHorizontal
|
|
639
|
+
? 'h-full w-auto'
|
|
640
|
+
: 'h-full ' +
|
|
641
|
+
(thumbGroup.srcs.length > 1
|
|
642
|
+
? 'w-1/2'
|
|
643
|
+
: 'w-full')}"
|
|
644
|
+
>
|
|
645
|
+
{#if thumbGroup.srcs[0]}
|
|
646
|
+
<img
|
|
647
|
+
src={thumbGroup.srcs[0]}
|
|
648
|
+
alt={thumbGroup.label}
|
|
649
|
+
class="object-contain {isHorizontal
|
|
650
|
+
? 'h-full w-auto'
|
|
651
|
+
: 'w-full h-full'} {thumbGroup
|
|
652
|
+
.srcs.length > 1
|
|
653
|
+
? 'object-right'
|
|
654
|
+
: 'object-center'}"
|
|
655
|
+
loading="lazy"
|
|
656
|
+
draggable="false"
|
|
657
|
+
/>
|
|
658
|
+
{:else}
|
|
659
|
+
<span class="opacity-20 text-4xl"
|
|
660
|
+
>?</span
|
|
661
|
+
>
|
|
662
|
+
{/if}
|
|
663
|
+
</div>
|
|
664
|
+
{#if thumbGroup.srcs.length > 1}
|
|
665
|
+
<div
|
|
666
|
+
class="flex items-center justify-center overflow-hidden {isHorizontal
|
|
667
|
+
? 'h-full w-auto'
|
|
668
|
+
: 'h-full w-1/2'}"
|
|
669
|
+
>
|
|
670
|
+
{#if thumbGroup.srcs[1]}
|
|
671
|
+
<img
|
|
672
|
+
src={thumbGroup.srcs[1]}
|
|
673
|
+
alt={thumbGroup.label}
|
|
674
|
+
class="object-contain {isHorizontal
|
|
675
|
+
? 'h-full w-auto'
|
|
676
|
+
: 'w-full h-full'} object-left"
|
|
677
|
+
loading="lazy"
|
|
678
|
+
draggable="false"
|
|
679
|
+
/>
|
|
680
|
+
{:else}
|
|
681
|
+
<span class="opacity-20 text-4xl"
|
|
682
|
+
>?</span
|
|
683
|
+
>
|
|
684
|
+
{/if}
|
|
685
|
+
</div>
|
|
686
|
+
{/if}
|
|
687
|
+
</div>
|
|
688
|
+
<div
|
|
689
|
+
class="text-xs font-medium truncate w-full opacity-70 group-hover:opacity-100"
|
|
690
|
+
>
|
|
691
|
+
<span class="font-bold mr-1"
|
|
692
|
+
>{thumbGroup.index + 1}.</span
|
|
693
|
+
>
|
|
694
|
+
{thumbGroup.label}
|
|
695
|
+
</div>
|
|
696
|
+
</button>
|
|
697
|
+
{/each}
|
|
698
|
+
{:else}
|
|
699
|
+
{#each thumbnails as thumb}
|
|
700
|
+
<button
|
|
701
|
+
class="group flex flex-col gap-1 p-1 rounded hover:bg-base-200 transition-colors text-left relative shrink-0 {isHorizontal
|
|
702
|
+
? 'w-auto'
|
|
703
|
+
: ''} {viewerState.canvasId === thumb.id
|
|
704
|
+
? 'ring-2 ring-primary bg-primary/5'
|
|
705
|
+
: ''}"
|
|
706
|
+
style={isHorizontal
|
|
707
|
+
? `height: ${fixedHeight + 24}px`
|
|
708
|
+
: ''}
|
|
709
|
+
onclick={() => selectCanvas(thumb.id)}
|
|
710
|
+
data-id={thumb.id}
|
|
711
|
+
aria-label="Select canvas {thumb.label}"
|
|
547
712
|
>
|
|
548
|
-
<
|
|
549
|
-
|
|
713
|
+
<div
|
|
714
|
+
class="{isHorizontal
|
|
715
|
+
? 'h-full w-auto'
|
|
716
|
+
: 'aspect-3/4 w-full'} bg-base-300 rounded overflow-hidden relative flex items-center justify-center"
|
|
717
|
+
style={isHorizontal
|
|
718
|
+
? `height: ${fixedHeight}px`
|
|
719
|
+
: ''}
|
|
720
|
+
>
|
|
721
|
+
{#if thumb.src}
|
|
722
|
+
<img
|
|
723
|
+
src={thumb.src}
|
|
724
|
+
alt={thumb.label}
|
|
725
|
+
class="object-contain {isHorizontal
|
|
726
|
+
? 'h-full w-auto'
|
|
727
|
+
: 'w-full h-full'}"
|
|
728
|
+
loading="lazy"
|
|
729
|
+
draggable="false"
|
|
730
|
+
/>
|
|
731
|
+
{:else}
|
|
732
|
+
<span class="opacity-20 text-4xl">?</span>
|
|
733
|
+
{/if}
|
|
734
|
+
</div>
|
|
735
|
+
<div
|
|
736
|
+
class="text-xs font-medium truncate w-full opacity-70 group-hover:opacity-100"
|
|
550
737
|
>
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
738
|
+
<span class="font-bold mr-1"
|
|
739
|
+
>{thumb.index + 1}.</span
|
|
740
|
+
>
|
|
741
|
+
{thumb.label}
|
|
742
|
+
</div>
|
|
743
|
+
</button>
|
|
744
|
+
{/each}
|
|
745
|
+
{/if}
|
|
555
746
|
</div>
|
|
556
747
|
</div>
|
|
557
748
|
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
import ChatCenteredText from 'phosphor-svelte/lib/ChatCenteredText';
|
|
8
8
|
import Info from 'phosphor-svelte/lib/Info';
|
|
9
9
|
import List from 'phosphor-svelte/lib/List';
|
|
10
|
+
import BookOpen from 'phosphor-svelte/lib/BookOpen';
|
|
11
|
+
import Scroll from 'phosphor-svelte/lib/Scroll';
|
|
10
12
|
import X from 'phosphor-svelte/lib/X';
|
|
11
13
|
import { VIEWER_STATE_KEY, type ViewerState } from '../state/viewer.svelte';
|
|
12
14
|
import { m, language } from '../state/i18n.svelte';
|
|
@@ -34,6 +36,7 @@
|
|
|
34
36
|
const showFullscreen = $derived(toolbarConfig.showFullscreen !== false);
|
|
35
37
|
const showAnnotations = $derived(toolbarConfig.showAnnotations !== false);
|
|
36
38
|
const showInfo = $derived(toolbarConfig.showInfo !== false);
|
|
39
|
+
const showViewingMode = $derived(toolbarConfig.showViewingMode !== false);
|
|
37
40
|
|
|
38
41
|
// Derived list of sorted plugin buttons
|
|
39
42
|
let sortedPluginButtons = $derived.by(() => {
|
|
@@ -46,6 +49,18 @@
|
|
|
46
49
|
function toggleOpen() {
|
|
47
50
|
viewerState.toggleToolbar();
|
|
48
51
|
}
|
|
52
|
+
|
|
53
|
+
let isOverflowVisible = $state(false);
|
|
54
|
+
$effect(() => {
|
|
55
|
+
if (isOpen) {
|
|
56
|
+
const timer = setTimeout(() => {
|
|
57
|
+
isOverflowVisible = true;
|
|
58
|
+
}, 320); // Slightly longer than 300ms to ensure transition is done
|
|
59
|
+
return () => clearTimeout(timer);
|
|
60
|
+
} else {
|
|
61
|
+
isOverflowVisible = false;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
49
64
|
</script>
|
|
50
65
|
|
|
51
66
|
<div
|
|
@@ -60,7 +75,7 @@
|
|
|
60
75
|
<!-- Collapsible Toolbar -->
|
|
61
76
|
<div
|
|
62
77
|
class={[
|
|
63
|
-
'pointer-events-auto bg-base-100/95 backdrop-blur shadow-xl transition-all duration-300 ease-in-out flex
|
|
78
|
+
'pointer-events-auto bg-base-100/95 backdrop-blur shadow-xl transition-all duration-300 ease-in-out flex',
|
|
64
79
|
// Layout based on position
|
|
65
80
|
isTop &&
|
|
66
81
|
'flex-row-reverse h-12 w-auto max-w-full rounded-b-xl border-x border-b border-base-200 origin-top',
|
|
@@ -76,6 +91,9 @@
|
|
|
76
91
|
!isOpen && !isTop && 'w-0 opacity-0',
|
|
77
92
|
!isOpen && !isTop && isLeft && '-translate-x-full',
|
|
78
93
|
!isOpen && !isTop && !isLeft && 'translate-x-full',
|
|
94
|
+
|
|
95
|
+
// Overflow handling
|
|
96
|
+
isOverflowVisible ? 'overflow-visible' : 'overflow-hidden',
|
|
79
97
|
]}
|
|
80
98
|
>
|
|
81
99
|
<!-- Close Button (Inside Menu) -->
|
|
@@ -104,13 +122,19 @@
|
|
|
104
122
|
<div class={isTop ? 'w-2' : 'h-2'}></div>
|
|
105
123
|
{/if}
|
|
106
124
|
|
|
125
|
+
<!-- Scrollable Actions -->
|
|
107
126
|
<ul
|
|
108
127
|
class={[
|
|
109
128
|
'menu menu-md gap-2 flex-nowrap items-center min-h-0',
|
|
129
|
+
isTop && 'px-2 py-1 menu-horizontal w-auto flex-row-reverse',
|
|
110
130
|
isTop &&
|
|
111
|
-
|
|
131
|
+
!isOverflowVisible &&
|
|
132
|
+
'overflow-x-auto overflow-y-hidden',
|
|
133
|
+
isTop && isOverflowVisible && 'overflow-visible',
|
|
112
134
|
!isTop &&
|
|
135
|
+
!isOverflowVisible &&
|
|
113
136
|
'py-2 px-1 flex-1 overflow-y-auto overflow-x-hidden w-12',
|
|
137
|
+
!isTop && isOverflowVisible && 'py-2 px-1 flex-1 w-12',
|
|
114
138
|
]}
|
|
115
139
|
>
|
|
116
140
|
<!-- --- Standard Actions --- -->
|
|
@@ -157,6 +181,81 @@
|
|
|
157
181
|
</li>
|
|
158
182
|
{/if}
|
|
159
183
|
|
|
184
|
+
{#if showViewingMode}
|
|
185
|
+
<li
|
|
186
|
+
class="dropdown {isTop
|
|
187
|
+
? 'dropdown-bottom'
|
|
188
|
+
: isLeft
|
|
189
|
+
? 'dropdown-right'
|
|
190
|
+
: 'dropdown-left'}"
|
|
191
|
+
>
|
|
192
|
+
<div
|
|
193
|
+
tabindex="0"
|
|
194
|
+
role="button"
|
|
195
|
+
class="flex items-center justify-center"
|
|
196
|
+
use:tooltip={{
|
|
197
|
+
content: m.viewing_mode_label(),
|
|
198
|
+
position: tooltipPos,
|
|
199
|
+
}}
|
|
200
|
+
aria-label={m.viewing_mode_label()}
|
|
201
|
+
>
|
|
202
|
+
{#if viewerState.viewingMode === 'paged'}
|
|
203
|
+
<BookOpen size={24} weight="bold" />
|
|
204
|
+
{:else}
|
|
205
|
+
<Scroll size={24} weight="bold" />
|
|
206
|
+
{/if}
|
|
207
|
+
</div>
|
|
208
|
+
<ul
|
|
209
|
+
tabindex="-1"
|
|
210
|
+
class="dropdown-content z-50 menu p-2 shadow bg-base-100 rounded-box w-48 border border-base-200 font-normal {isTop
|
|
211
|
+
? 'left-1/2 -translate-x-1/2'
|
|
212
|
+
: ''}"
|
|
213
|
+
>
|
|
214
|
+
<li>
|
|
215
|
+
<button
|
|
216
|
+
class={viewerState.viewingMode === 'individuals'
|
|
217
|
+
? 'active'
|
|
218
|
+
: ''}
|
|
219
|
+
onclick={() =>
|
|
220
|
+
viewerState.setViewingMode('individuals')}
|
|
221
|
+
>
|
|
222
|
+
<Scroll size={16} />
|
|
223
|
+
{m.viewing_mode_individuals()}
|
|
224
|
+
</button>
|
|
225
|
+
</li>
|
|
226
|
+
<li>
|
|
227
|
+
<button
|
|
228
|
+
class={viewerState.viewingMode === 'paged'
|
|
229
|
+
? 'active'
|
|
230
|
+
: ''}
|
|
231
|
+
onclick={() =>
|
|
232
|
+
viewerState.setViewingMode('paged')}
|
|
233
|
+
>
|
|
234
|
+
<BookOpen size={16} />
|
|
235
|
+
{m.viewing_mode_paged()}
|
|
236
|
+
</button>
|
|
237
|
+
</li>
|
|
238
|
+
{#if viewerState.viewingMode === 'paged'}
|
|
239
|
+
<div class="divider my-1"></div>
|
|
240
|
+
<li>
|
|
241
|
+
<label class="label cursor-pointer py-1 gap-2">
|
|
242
|
+
<span class="label-text text-sm"
|
|
243
|
+
>{m.viewing_mode_shift_pairing()}</span
|
|
244
|
+
>
|
|
245
|
+
<input
|
|
246
|
+
type="checkbox"
|
|
247
|
+
class="checkbox checkbox-sm"
|
|
248
|
+
checked={viewerState.pagedOffset === 1}
|
|
249
|
+
onchange={() =>
|
|
250
|
+
viewerState.togglePagedOffset()}
|
|
251
|
+
/>
|
|
252
|
+
</label>
|
|
253
|
+
</li>
|
|
254
|
+
{/if}
|
|
255
|
+
</ul>
|
|
256
|
+
</li>
|
|
257
|
+
{/if}
|
|
258
|
+
|
|
160
259
|
{#if showFullscreen}
|
|
161
260
|
<li>
|
|
162
261
|
<button
|
|
@@ -227,7 +326,7 @@
|
|
|
227
326
|
{/if}
|
|
228
327
|
|
|
229
328
|
<!-- Separator if both groups exist -->
|
|
230
|
-
{#if (showSearch || showGallery || showFullscreen || showAnnotations || showInfo) && sortedPluginButtons.length > 0}
|
|
329
|
+
{#if (showSearch || showGallery || showFullscreen || showAnnotations || showInfo || showViewingMode) && sortedPluginButtons.length > 0}
|
|
231
330
|
<div
|
|
232
331
|
class={[
|
|
233
332
|
'divider',
|