mirror of
https://github.com/open-webui/open-webui.git
synced 2026-08-28 05:27:35 +00:00
refac
This commit is contained in:
parent
2befa8f796
commit
bfb68feea7
4 changed files with 184 additions and 79 deletions
|
|
@ -116,6 +116,35 @@
|
|||
let loadingDirs: Set<string> = new Set();
|
||||
let directoryMenu: { x: number; y: number } | null = null;
|
||||
|
||||
const invalidateVisibleRows = () => {
|
||||
entries = [...entries];
|
||||
};
|
||||
|
||||
/** Normalize Windows backslashes and collapse duplicate separators. */
|
||||
const normalizePath = (path: string) => path.replace(/\\/g, '/').replace(/\/{2,}/g, '/');
|
||||
|
||||
const cleanEntryName = (name: string) =>
|
||||
normalizePath(name)
|
||||
.split('/')
|
||||
.filter(Boolean)
|
||||
.at(-1) ?? name.replace(/\/+$/, '');
|
||||
|
||||
const normalizeEntries = (items: FileEntry[]) =>
|
||||
items.map((entry) => ({ ...entry, name: cleanEntryName(entry.name) }));
|
||||
|
||||
const asDirectoryPath = (path: string) => {
|
||||
const normalized = normalizePath(path || '/');
|
||||
if (normalized === '/') return '/';
|
||||
return normalized.endsWith('/') ? normalized : `${normalized}/`;
|
||||
};
|
||||
|
||||
const joinPath = (parentPath: string, name: string) => {
|
||||
const parent = asDirectoryPath(parentPath);
|
||||
const child = cleanEntryName(name);
|
||||
if (!child) return parent;
|
||||
return normalizePath(`${parent}${child}`);
|
||||
};
|
||||
|
||||
const sortEntries = (items: FileEntry[]): FileEntry[] => {
|
||||
return [...items].sort((a, b) => {
|
||||
// Directories always first
|
||||
|
|
@ -146,15 +175,16 @@
|
|||
sortBy = mode;
|
||||
sortAsc = true;
|
||||
}
|
||||
entries = [...entries];
|
||||
invalidateVisibleRows();
|
||||
treeCache = new Map(treeCache);
|
||||
void refreshBrowser();
|
||||
};
|
||||
|
||||
const filterEntries = (items: FileEntry[]) =>
|
||||
showHidden ? items : items.filter((entry) => !entry.name.startsWith('.'));
|
||||
|
||||
const entryPath = (parentPath: string, entry: FileEntry) =>
|
||||
entry.type === 'directory' ? `${parentPath}${entry.name}/` : `${parentPath}${entry.name}`;
|
||||
entry.type === 'directory' ? asDirectoryPath(joinPath(parentPath, entry.name)) : joinPath(parentPath, entry.name);
|
||||
|
||||
const withRowIndexes = (rows: Omit<BrowserRow, 'rowIndex'>[]): BrowserRow[] =>
|
||||
rows.map((row, rowIndex) => ({ ...row, rowIndex }));
|
||||
|
|
@ -369,14 +399,6 @@
|
|||
const isPdf = (path: string) => path.split('.').pop()?.toLowerCase() === 'pdf';
|
||||
const isOffice = (path: string) => OFFICE_EXTS.has(path.split('.').pop()?.toLowerCase() ?? '');
|
||||
|
||||
/** Normalize Windows backslashes to forward slashes. */
|
||||
const normalizePath = (p: string) => p.replace(/\\/g, '/');
|
||||
|
||||
const asDirectoryPath = (path: string) => {
|
||||
const normalized = normalizePath(path || '/');
|
||||
return normalized.endsWith('/') ? normalized : `${normalized}/`;
|
||||
};
|
||||
|
||||
const resetTreeState = () => {
|
||||
expandedDirs = new Set();
|
||||
treeCache = new Map();
|
||||
|
|
@ -450,7 +472,8 @@
|
|||
homePath && cwdPath && (cwdPath === homePath || cwdPath.startsWith(homePath))
|
||||
? { path: homePath, label: 'Home' }
|
||||
: undefined;
|
||||
setFileRoot(cwd?.root ?? homeRoot);
|
||||
const rootPath = cwd?.root?.path ? asDirectoryPath(cwd.root.path) : null;
|
||||
setFileRoot(rootPath && rootPath !== '/' ? cwd?.root : (homeRoot ?? cwd?.root));
|
||||
const path = cwdPath ?? fileRoot?.path ?? '/';
|
||||
return clampToFileRoot(path);
|
||||
};
|
||||
|
|
@ -463,7 +486,7 @@
|
|||
return parts.reduce(
|
||||
(acc, part) => {
|
||||
const prev = acc[acc.length - 1];
|
||||
acc.push({ label: part, path: `${prev.path}${part}/` });
|
||||
acc.push({ label: part, path: asDirectoryPath(joinPath(prev.path, part)) });
|
||||
return acc;
|
||||
},
|
||||
[{ label: fileRoot.label, path: fileRoot.path }]
|
||||
|
|
@ -474,11 +497,11 @@
|
|||
const isDrive = /^[A-Za-z]:$/.test(parts[0] ?? '');
|
||||
const root = isDrive ? { label: parts[0], path: `${parts[0]}/` } : { label: '/', path: '/' };
|
||||
return (isDrive ? parts.slice(1) : parts).reduce(
|
||||
(acc, part) => {
|
||||
const prev = acc[acc.length - 1];
|
||||
acc.push({ label: part, path: `${prev.path}${part}/` });
|
||||
return acc;
|
||||
},
|
||||
(acc, part) => {
|
||||
const prev = acc[acc.length - 1];
|
||||
acc.push({ label: part, path: asDirectoryPath(joinPath(prev.path, part)) });
|
||||
return acc;
|
||||
},
|
||||
[root]
|
||||
);
|
||||
};
|
||||
|
|
@ -543,8 +566,8 @@
|
|||
entries = [];
|
||||
} else {
|
||||
currentWritable = result.writable !== false;
|
||||
entries = result.entries;
|
||||
treeCache = new Map(treeCache).set(directory, result.entries);
|
||||
entries = normalizeEntries(result.entries);
|
||||
treeCache = new Map(treeCache).set(directory, entries);
|
||||
saveTreeState();
|
||||
}
|
||||
};
|
||||
|
|
@ -559,9 +582,10 @@
|
|||
nextLoading.delete(directory);
|
||||
loadingDirs = nextLoading;
|
||||
if (result === null) return null;
|
||||
treeCache = new Map(treeCache).set(directory, result.entries);
|
||||
const normalizedEntries = normalizeEntries(result.entries);
|
||||
treeCache = new Map(treeCache).set(directory, normalizedEntries);
|
||||
saveTreeState();
|
||||
return result.entries;
|
||||
return normalizedEntries;
|
||||
};
|
||||
|
||||
const refreshExpandedDirs = async () => {
|
||||
|
|
@ -582,16 +606,12 @@
|
|||
next.delete(directory);
|
||||
expandedDirs = next;
|
||||
saveTreeState();
|
||||
invalidateVisibleRows();
|
||||
return;
|
||||
}
|
||||
|
||||
expandedDirs = new Set(expandedDirs).add(directory);
|
||||
saveTreeState();
|
||||
if (treeCache.has(directory)) {
|
||||
entries = [...entries];
|
||||
treeCache = new Map(treeCache);
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await fetchExpandedDir(directory);
|
||||
if (result === null) {
|
||||
|
|
@ -601,7 +621,7 @@
|
|||
saveTreeState();
|
||||
toast.error($i18n.t('Failed to load folder'));
|
||||
} else {
|
||||
entries = [...entries];
|
||||
invalidateVisibleRows();
|
||||
treeCache = new Map(treeCache);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@
|
|||
export let onGoForward: () => void = () => {};
|
||||
|
||||
let dragOverCrumb: number | null = null;
|
||||
let sortMenuOpen = false;
|
||||
let actionsMenuOpen = false;
|
||||
|
||||
let uploadInput: HTMLInputElement;
|
||||
let breadcrumbEl: HTMLDivElement;
|
||||
|
|
@ -49,33 +51,35 @@
|
|||
</script>
|
||||
|
||||
<div class="m-0 flex items-center gap-1 px-1 pt-0 pb-1.5 shrink-0 border-b border-gray-50 dark:border-gray-850/30">
|
||||
<!-- Back -->
|
||||
<Tooltip content={$i18n.t('Back')}>
|
||||
<button
|
||||
class="shrink-0 flex h-5 min-w-5 items-center justify-center rounded px-1 transition-colors duration-100 {canGoBack
|
||||
? 'text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300'
|
||||
: 'text-gray-200 dark:text-gray-700 cursor-default'}"
|
||||
on:click={onGoBack}
|
||||
disabled={!canGoBack}
|
||||
aria-label={$i18n.t('Back')}
|
||||
>
|
||||
<Icon name="chevron-left" size={11} strokeWidth={1.5} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<div class="flex shrink-0 items-center gap-0.5 px-1">
|
||||
<!-- Back -->
|
||||
<Tooltip content={$i18n.t('Back')}>
|
||||
<button
|
||||
class="shrink-0 flex h-5 min-w-6 items-center justify-center rounded px-1.5 transition-colors duration-100 {canGoBack
|
||||
? 'text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300'
|
||||
: 'text-gray-200 dark:text-gray-700 cursor-default'}"
|
||||
on:click={onGoBack}
|
||||
disabled={!canGoBack}
|
||||
aria-label={$i18n.t('Back')}
|
||||
>
|
||||
<Icon name="chevron-left" size={11} strokeWidth={1.5} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
||||
<!-- Forward -->
|
||||
<Tooltip content={$i18n.t('Forward')}>
|
||||
<button
|
||||
class="shrink-0 flex h-5 min-w-5 items-center justify-center rounded px-1 transition-colors duration-100 {canGoForward
|
||||
? 'text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300'
|
||||
: 'text-gray-200 dark:text-gray-700 cursor-default'}"
|
||||
on:click={onGoForward}
|
||||
disabled={!canGoForward}
|
||||
aria-label={$i18n.t('Forward')}
|
||||
>
|
||||
<Icon name="chevron-right" size={11} strokeWidth={1.5} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<!-- Forward -->
|
||||
<Tooltip content={$i18n.t('Forward')}>
|
||||
<button
|
||||
class="shrink-0 flex h-5 min-w-6 items-center justify-center rounded px-1.5 transition-colors duration-100 {canGoForward
|
||||
? 'text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300'
|
||||
: 'text-gray-200 dark:text-gray-700 cursor-default'}"
|
||||
on:click={onGoForward}
|
||||
disabled={!canGoForward}
|
||||
aria-label={$i18n.t('Forward')}
|
||||
>
|
||||
<Icon name="chevron-right" size={11} strokeWidth={1.5} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div
|
||||
bind:this={breadcrumbEl}
|
||||
|
|
@ -143,7 +147,7 @@
|
|||
</Tooltip>
|
||||
|
||||
{#if !selectedFile}
|
||||
<Dropdown align="end" sideOffset={4}>
|
||||
<Dropdown bind:show={sortMenuOpen} align="end" sideOffset={4}>
|
||||
<Tooltip content={$i18n.t('Sort')}>
|
||||
<button
|
||||
class="shrink-0 flex h-5 w-5 items-center justify-center rounded transition-colors duration-100 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
|
|
@ -158,7 +162,10 @@
|
|||
<button
|
||||
type="button"
|
||||
class="select-none flex h-7 w-full items-center gap-2 rounded-lg px-2 text-xs hover:bg-gray-50/40 dark:hover:bg-white/4 transition"
|
||||
on:click={() => onSort('name')}
|
||||
on:click={() => {
|
||||
onSort('name');
|
||||
sortMenuOpen = false;
|
||||
}}
|
||||
>
|
||||
<span class="flex-1 text-left">{$i18n.t('Name')}</span>
|
||||
{#if sortBy === 'name'}
|
||||
|
|
@ -175,7 +182,10 @@
|
|||
<button
|
||||
type="button"
|
||||
class="select-none flex h-7 w-full items-center gap-2 rounded-lg px-2 text-xs hover:bg-gray-50/40 dark:hover:bg-white/4 transition"
|
||||
on:click={() => onSort('size')}
|
||||
on:click={() => {
|
||||
onSort('size');
|
||||
sortMenuOpen = false;
|
||||
}}
|
||||
>
|
||||
<span class="flex-1 text-left">{$i18n.t('Size')}</span>
|
||||
{#if sortBy === 'size'}
|
||||
|
|
@ -192,7 +202,10 @@
|
|||
<button
|
||||
type="button"
|
||||
class="select-none flex h-7 w-full items-center gap-2 rounded-lg px-2 text-xs hover:bg-gray-50/40 dark:hover:bg-white/4 transition"
|
||||
on:click={() => onSort('date')}
|
||||
on:click={() => {
|
||||
onSort('date');
|
||||
sortMenuOpen = false;
|
||||
}}
|
||||
>
|
||||
<span class="flex-1 text-left">{$i18n.t('Date Modified')}</span>
|
||||
{#if sortBy === 'date'}
|
||||
|
|
@ -209,7 +222,7 @@
|
|||
</DropdownMenu>
|
||||
</div>
|
||||
</Dropdown>
|
||||
<Dropdown align="end" sideOffset={4}>
|
||||
<Dropdown bind:show={actionsMenuOpen} align="end" sideOffset={4}>
|
||||
<Tooltip content={$i18n.t('Actions')}>
|
||||
<button
|
||||
class="shrink-0 flex h-5 w-5 items-center justify-center rounded transition-colors duration-100 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300"
|
||||
|
|
@ -224,7 +237,10 @@
|
|||
<button
|
||||
type="button"
|
||||
class="select-none flex h-7 w-full items-center gap-2 rounded-lg px-2 text-xs hover:bg-gray-50/40 dark:hover:bg-white/4 transition disabled:opacity-40 disabled:hover:bg-transparent"
|
||||
on:click={onNewFolder}
|
||||
on:click={() => {
|
||||
onNewFolder();
|
||||
actionsMenuOpen = false;
|
||||
}}
|
||||
disabled={!writable}
|
||||
>
|
||||
<Icon name="folder" size={12} strokeWidth={1.4} />
|
||||
|
|
@ -233,7 +249,10 @@
|
|||
<button
|
||||
type="button"
|
||||
class="select-none flex h-7 w-full items-center gap-2 rounded-lg px-2 text-xs hover:bg-gray-50/40 dark:hover:bg-white/4 transition disabled:opacity-40 disabled:hover:bg-transparent"
|
||||
on:click={onNewFile}
|
||||
on:click={() => {
|
||||
onNewFile();
|
||||
actionsMenuOpen = false;
|
||||
}}
|
||||
disabled={!writable}
|
||||
>
|
||||
<Icon name="empty-page" size={12} strokeWidth={1.4} />
|
||||
|
|
@ -242,7 +261,10 @@
|
|||
<button
|
||||
type="button"
|
||||
class="select-none flex h-7 w-full items-center gap-2 rounded-lg px-2 text-xs hover:bg-gray-50/40 dark:hover:bg-white/4 transition disabled:opacity-40 disabled:hover:bg-transparent"
|
||||
on:click={() => uploadInput?.click()}
|
||||
on:click={() => {
|
||||
actionsMenuOpen = false;
|
||||
uploadInput?.click();
|
||||
}}
|
||||
disabled={!writable}
|
||||
>
|
||||
<Icon name="upload" size={12} strokeWidth={1.4} />
|
||||
|
|
@ -251,7 +273,10 @@
|
|||
<button
|
||||
type="button"
|
||||
class="select-none flex h-7 w-full items-center gap-2 rounded-lg px-2 text-xs hover:bg-gray-50/40 dark:hover:bg-white/4 transition"
|
||||
on:click={onDownloadDir}
|
||||
on:click={() => {
|
||||
onDownloadDir();
|
||||
actionsMenuOpen = false;
|
||||
}}
|
||||
>
|
||||
<Icon name="download" size={12} strokeWidth={1.4} />
|
||||
<span>{$i18n.t('Download')}</span>
|
||||
|
|
@ -259,7 +284,10 @@
|
|||
<button
|
||||
type="button"
|
||||
class="select-none flex h-7 w-full items-center gap-2 rounded-lg px-2 text-xs hover:bg-gray-50/40 dark:hover:bg-white/4 transition"
|
||||
on:click={onToggleHidden}
|
||||
on:click={() => {
|
||||
onToggleHidden();
|
||||
actionsMenuOpen = false;
|
||||
}}
|
||||
>
|
||||
<Icon name="eye" size={12} strokeWidth={1.4} />
|
||||
<span>{showHidden ? $i18n.t('Hide Hidden Files') : $i18n.t('Show Hidden Files')}</span>
|
||||
|
|
|
|||
|
|
@ -9,14 +9,12 @@
|
|||
import Spinner from '../../common/Spinner.svelte';
|
||||
import PDFViewer from '../../common/PDFViewer.svelte';
|
||||
import PanzoomContainer from '../../common/PanzoomContainer.svelte';
|
||||
import Tooltip from '../../common/Tooltip.svelte';
|
||||
import DocxPreview from '../../common/DocxPreview.svelte';
|
||||
import PptxPreview from '../../common/PptxPreview.svelte';
|
||||
import JsonTreeView from './JsonTreeView.svelte';
|
||||
import NotebookView from './NotebookView.svelte';
|
||||
import SqliteView from './SqliteView.svelte';
|
||||
import FileCodeEditor from './FileCodeEditor.svelte';
|
||||
import Icon from './Icon.svelte';
|
||||
|
||||
let pdfViewerRef: PDFViewer;
|
||||
let fileCodeEditorRef: FileCodeEditor;
|
||||
|
|
@ -267,6 +265,7 @@
|
|||
|
||||
let panzoomRef: PanzoomContainer;
|
||||
let pptxPreviewRef: PptxPreview;
|
||||
let imageZoomLevel = 1;
|
||||
export const resetImageView = () => {
|
||||
panzoomRef?.reset();
|
||||
pptxPreviewRef?.resetView();
|
||||
|
|
@ -275,10 +274,6 @@
|
|||
export const resetPdfView = () => {
|
||||
pdfViewerRef?.resetView();
|
||||
};
|
||||
|
||||
const resetFloatingView = () => {
|
||||
if (fileImageUrl !== null) panzoomRef?.reset();
|
||||
};
|
||||
</script>
|
||||
|
||||
<div
|
||||
|
|
@ -294,6 +289,7 @@
|
|||
{:else if fileImageUrl !== null}
|
||||
<PanzoomContainer
|
||||
bind:this={panzoomRef}
|
||||
bind:zoomLevel={imageZoomLevel}
|
||||
className="w-full h-full flex items-center justify-center"
|
||||
options={{ zoomDoubleClickSpeed: 1 }}
|
||||
>
|
||||
|
|
@ -480,18 +476,52 @@
|
|||
|
||||
{#if !fileLoading && fileImageUrl !== null}
|
||||
<div
|
||||
class="absolute bottom-3 left-1/2 z-10 flex -translate-x-1/2 items-center rounded-lg border border-gray-200/60 bg-white/90 px-1 py-0.5 shadow-lg backdrop-blur-sm dark:border-gray-700/60 dark:bg-gray-850/90"
|
||||
class="absolute bottom-3 left-1/2 z-10 flex -translate-x-1/2 items-center gap-0.5 rounded-lg border border-gray-200/60 bg-white/90 px-1 py-0.5 shadow-lg backdrop-blur-sm dark:border-gray-700/60 dark:bg-gray-850/90"
|
||||
>
|
||||
<Tooltip content={$i18n.t('Reset view')}>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex h-7 min-w-7 shrink-0 items-center justify-center rounded-md p-1.5 text-gray-500 transition hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800"
|
||||
on:click={resetFloatingView}
|
||||
aria-label={$i18n.t('Reset view')}
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex h-7 min-w-7 shrink-0 items-center justify-center rounded-md p-1.5 text-gray-500 transition hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800"
|
||||
on:click={() => panzoomRef?.zoomOut()}
|
||||
aria-label={$i18n.t('Zoom out')}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="size-3.5"
|
||||
>
|
||||
<Icon name="refresh" size={13} strokeWidth={1.4} />
|
||||
</button>
|
||||
</Tooltip>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M4 10a.75.75 0 0 1 .75-.75h10.5a.75.75 0 0 1 0 1.5H4.75A.75.75 0 0 1 4 10Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="h-7 min-w-12 shrink-0 rounded-md px-1.5 py-1 text-center text-[0.6875rem] font-normal tabular-nums text-gray-500 transition hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800"
|
||||
on:click={() => panzoomRef?.reset()}
|
||||
aria-label={$i18n.t('Reset zoom')}
|
||||
>
|
||||
{Math.round(imageZoomLevel * 100)}%
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex h-7 min-w-7 shrink-0 items-center justify-center rounded-md p-1.5 text-gray-500 transition hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800"
|
||||
on:click={() => panzoomRef?.zoomIn()}
|
||||
aria-label={$i18n.t('Zoom in')}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
class="size-3.5"
|
||||
>
|
||||
<path
|
||||
d="M10.75 4.75a.75.75 0 0 0-1.5 0v4.5h-4.5a.75.75 0 0 0 0 1.5h4.5v4.5a.75.75 0 0 0 1.5 0v-4.5h4.5a.75.75 0 0 0 0-1.5h-4.5v-4.5Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -10,18 +10,45 @@
|
|||
|
||||
export let className = '';
|
||||
export let options: Partial<PanZoomOptions> = {};
|
||||
export let zoomLevel = 1;
|
||||
|
||||
let containerElement: HTMLElement;
|
||||
let instance: PanZoom | undefined;
|
||||
|
||||
const updateZoomLevel = () => {
|
||||
zoomLevel = instance?.getTransform().scale ?? 1;
|
||||
};
|
||||
|
||||
const center = () => ({
|
||||
x: containerElement?.clientWidth ? containerElement.clientWidth / 2 : 0,
|
||||
y: containerElement?.clientHeight ? containerElement.clientHeight / 2 : 0
|
||||
});
|
||||
|
||||
export const zoomIn = () => {
|
||||
if (!instance) return;
|
||||
const { x, y } = center();
|
||||
instance.zoomTo(x, y, 1.25);
|
||||
updateZoomLevel();
|
||||
};
|
||||
|
||||
export const zoomOut = () => {
|
||||
if (!instance) return;
|
||||
const { x, y } = center();
|
||||
instance.zoomTo(x, y, 0.8);
|
||||
updateZoomLevel();
|
||||
};
|
||||
|
||||
export const reset = () => {
|
||||
instance?.moveTo(0, 0);
|
||||
instance?.zoomAbs(0, 0, 1);
|
||||
updateZoomLevel();
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
const localInstance = panzoom(containerElement, { ...defaultOpts, ...options });
|
||||
instance = localInstance;
|
||||
updateZoomLevel();
|
||||
localInstance.on('zoom', updateZoomLevel);
|
||||
return () => {
|
||||
localInstance.dispose();
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue