diff --git a/apps/desktop/src/main/__tests__/lightboxMediaActions.test.ts b/apps/desktop/src/main/__tests__/lightboxMediaActions.test.ts index 60f02681688..b9ebaa300c0 100644 --- a/apps/desktop/src/main/__tests__/lightboxMediaActions.test.ts +++ b/apps/desktop/src/main/__tests__/lightboxMediaActions.test.ts @@ -145,6 +145,7 @@ describe('mime helpers', () => { function makeDeps(overrides: Partial = {}): LightboxMediaDeps { return { + realpath: vi.fn(async (filePath: string) => filePath), isPathAllowed: () => true, resolveImageCacheUrl: (url) => ({ absPath: `/cache/${url.replace('xdt-image://', '')}`, @@ -217,6 +218,37 @@ describe('openWithDefaultApp', () => { }); describe('saveAs', () => { + // Unified previews: PDF/video originals save with their names, while image-only + // reading stays closed to binary files and protected symlink targets stay blocked. + it.each(['report.pdf', 'clip.mp4', 'archive.zip'])( + 'saves the original %s without image decoding', + async (name) => { + const file = path.resolve('/tmp', name); + const deps = makeDeps(); + const handlers = createLightboxMediaHandlers(deps); + await expect(handlers.saveAs({ url: localFileUrl(file) })).resolves.toMatchObject({ + canceled: false, + }); + expect(deps.showSaveDialog).toHaveBeenCalledWith({ + defaultPath: path.join('/downloads', name), + }); + expect(deps.copyFile).toHaveBeenCalledWith(file, '/downloads/out.png'); + await expect(handlers.readImageBytes({ url: localFileUrl(file) })).rejects.toThrow(); + }, + ); + it('rejects an allowed-looking file that resolves to a blocked path before offering save', async () => { + const blocked = path.resolve('/private/key'); + const deps = makeDeps({ + realpath: vi.fn(async () => blocked), + isPathAllowed: (value) => value !== blocked, + }); + await expect( + createLightboxMediaHandlers(deps).saveAs({ url: localFileUrl('/tmp/document.pdf') }), + ).rejects.toThrow(); + expect(deps.showSaveDialog).not.toHaveBeenCalled(); + expect(deps.copyFile).not.toHaveBeenCalled(); + }); + it('copies a local source to the chosen path', async () => { const deps = makeDeps(); const result = await createLightboxMediaHandlers(deps).saveAs({ diff --git a/apps/desktop/src/main/bootstrap-electron.ts b/apps/desktop/src/main/bootstrap-electron.ts index 5f08c09ee1c..db0402995f8 100644 --- a/apps/desktop/src/main/bootstrap-electron.ts +++ b/apps/desktop/src/main/bootstrap-electron.ts @@ -8464,10 +8464,13 @@ const registerIpcHandlers = () => { const lightboxMedia = createLightboxMediaHandlers({ isPathAllowed, + realpath: (absPath) => fs.promises.realpath(absPath), resolveImageCacheUrl: (url) => url.startsWith('cindy-media://') ? cindyMediaBlobStore.resolveSafe(url) - : imageCacheStore.resolveSafe(url), + : url.startsWith('xdt-video://') + ? videoCacheStore.resolveSafe(url) + : imageCacheStore.resolveSafe(url), // 规则 25:lightbox「发送到对话」的缓存写同样进媒体总仓(草稿语义, // 发送时统一由 register.ts 挂引用),与 IPC from-path/from-buffer 一致。 cacheImageFromPath: (params) => diff --git a/apps/desktop/src/main/lightboxMediaActions.ts b/apps/desktop/src/main/lightboxMediaActions.ts index 2968cb967e0..031c7a9dd6c 100644 --- a/apps/desktop/src/main/lightboxMediaActions.ts +++ b/apps/desktop/src/main/lightboxMediaActions.ts @@ -19,6 +19,8 @@ import { createHash } from 'node:crypto'; import * as path from 'node:path'; +import { isDangerousAttachmentName } from '../shared/attachmentSafety'; + import { isIpcErrorCode } from '../shared/ipc-errors'; import { throwIpcError } from './utils/ipcValidate'; @@ -69,8 +71,9 @@ const EXT_BY_MIME: Record = { * - `http(s)://` / `data:image/...;base64,` 标记为需取字节的远程/内联源; * - 其余(cindy-remote-media:// / blob: / 相对路径等)一律 unsupported。 */ -export function classifyLightboxMediaUrl(url: string): LightboxMediaSource { +export function classifyLightboxMediaUrl(url: string, forSave = false): LightboxMediaSource { if (typeof url !== 'string' || url.length === 0) return { kind: 'unsupported' }; + if (forSave && url.startsWith('xdt-video://')) return { kind: 'image-cache', url }; if (url.startsWith('xdt-image://')) return { kind: 'image-cache', url }; // cindy-media(媒体总仓 blob)与 xdt-image 同语义:本机缓存文件,可解析绝对 // 路径;resolveImageCacheUrl 依赖按 scheme 分派到对应 store(bootstrap 接线)。 @@ -91,7 +94,9 @@ export function classifyLightboxMediaUrl(url: string): LightboxMediaSource { ? resolved.toLowerCase() === normalizedInput.toLowerCase() : resolved === normalizedInput; if (!eq) return { kind: 'unsupported' }; - if (!IMAGE_EXTS.has(path.extname(resolved).toLowerCase())) { + if ( + (!forSave && !IMAGE_EXTS.has(path.extname(resolved).toLowerCase())) || + (forSave && isDangerousAttachmentName(resolved))) { return { kind: 'unsupported' }; } return { kind: 'local-file', absPath: resolved }; @@ -241,6 +246,7 @@ export async function collectStreamWithLimit( export interface LightboxMediaDeps { /** bootstrap-electron 的路径白名单校验。 */ isPathAllowed(absPath: string): boolean; + realpath(absPath: string): Promise; /** imageCacheStore.resolveSafe:xdt-image:// URL → 缓存文件绝对路径(含安全校验,失败抛错)。 */ resolveImageCacheUrl(url: string): { absPath: string; mimeType: string }; /** imageCacheStore.copyFromPath(lifecycle 由调用处指定)。 */ @@ -260,8 +266,7 @@ export interface LightboxMediaDeps { }): Promise<{ url: string; filename: string }>; /** dialog.showSaveDialog(挂到聚焦窗口)。 */ showSaveDialog(opts: { - defaultPath: string; - }): Promise<{ canceled: boolean; filePath?: string }>; + defaultPath: string }): Promise<{ canceled: boolean; filePath?: string }>; /** shell.openPath:成功返回 '',失败返回错误串。 */ openPath(absPath: string): Promise; /** fs.promises.readFile:readImageBytes 的 local-file 分支读字节。 */ @@ -293,8 +298,7 @@ export interface LightboxMediaHandlers { saveAs(params: { url: string }): Promise<{ canceled: boolean; savedPath?: string }>; cacheForSession(params: { url: string; - sessionId: string; - }): Promise; + sessionId: string }): Promise; /** renderer 字节层(标注烧录/位图复制)对 http / remote-media 源的取字节入口。 */ readImageBytes(params: { url: string }): Promise<{ base64: string; mimeType: string }>; } @@ -441,7 +445,8 @@ export function createLightboxMediaHandlers(deps: LightboxMediaDeps): LightboxMe // 另存为。先弹保存对话框(只需要建议文件名),确认后才取字节/复制, // 用户取消不浪费下载。 async saveAs(params) { - const source = classifyLightboxMediaUrl(params?.url ?? ''); + // Save supports files; image decoding / annotation keep their image-only classifier. + const source = classifyLightboxMediaUrl(params?.url ?? '', true); if (source.kind === 'unsupported') { throwIpcError('INVALID_PARAMS', 'unsupported media source'); } @@ -449,6 +454,14 @@ export function createLightboxMediaHandlers(deps: LightboxMediaDeps): LightboxMe let localAbsPath: string | null = null; if (source.kind === 'image-cache' || source.kind === 'local-file') { localAbsPath = resolveLocalSourceOrThrow(source, deps); + try { + localAbsPath = await deps.realpath(localAbsPath); + if (!deps.isPathAllowed(localAbsPath) || isDangerousAttachmentName(localAbsPath)) + throwIpcError('PERMISSION_DENIED', '不允许访问该路径'); + } catch (err) { + rethrowIfIpcError(err); + throwIpcError('NOT_FOUND', '文件不存在'); + } } // remote-media 先取字节:正在 lightbox 显示的图协议缓存大概率命中(快), @@ -462,10 +475,30 @@ export function createLightboxMediaHandlers(deps: LightboxMediaDeps): LightboxMe rethrowIfIpcError(err); throwIpcError('INTERNAL', err instanceof Error ? err.message : String(err)); } - const mime = sniffImageMime(remoteBytes.buffer) ?? remoteBytes.mimeType ?? 'image/png'; - suggested = `image-${deps.now()}${extForMimeType(mime)}`; + const mime = sniffImageMime(remoteBytes.buffer) ?? remoteBytes.mimeType ?? 'application/octet-stream'; + const mediaExt: Record = { + 'video/mp4': '.mp4', + 'video/quicktime': '.mov', + 'video/webm': '.webm', + 'audio/mpeg': '.mp3', + 'audio/mp4': '.m4a', + 'audio/wav': '.wav', + 'audio/ogg': '.ogg', + 'application/pdf': '.pdf', + 'model/gltf-binary': '.glb', + }; + suggested = `${mime.startsWith('image/') ? 'image' : 'media'}-${deps.now()}${mediaExt[mime] ?? (mime.startsWith('image/') ? extForMimeType(mime) : '.bin')}`; } else { suggested = suggestSaveFileName(source, localAbsPath, deps.now()); + if (source.kind === 'http') { + try { + const base = decodeURIComponent(new URL(source.url).pathname.split('/').pop() ?? ''); + if (base && !/[\\/\x00-\x1f]/.test(base) && !isDangerousAttachmentName(base)) + suggested = base; + } catch { + /* retain the safe fallback */ + } + } } const { canceled, filePath } = await deps.showSaveDialog({ diff --git a/apps/desktop/src/renderer/__tests__/chatImageView.test.ts b/apps/desktop/src/renderer/__tests__/chatImageView.test.ts index 6425171a04c..46e0ad54e5f 100644 --- a/apps/desktop/src/renderer/__tests__/chatImageView.test.ts +++ b/apps/desktop/src/renderer/__tests__/chatImageView.test.ts @@ -15,8 +15,8 @@ vi.mock('@/hooks/useRemoteMediaUrl', () => ({ })); vi.mock('../components/chat/ImageLightbox', () => ({ - ImageLightbox: ({ onClose }: { onClose: () => void }) => React.createElement( - 'button', { 'data-testid': 'image-lightbox', onClick: onClose }, 'close preview', + ImageLightbox: ({ onClose, enableGallery }: { onClose: () => void; enableGallery?: boolean }) => React.createElement( + 'button', { 'data-testid': 'image-lightbox', 'data-gallery-enabled': String(enableGallery), onClick: onClose }, 'close preview', ), })); @@ -368,7 +368,7 @@ describe('ChatImageView', () => { trigger.focus(); fireEvent.keyDown(trigger, { key }); expect(screen.getByTestId('image-lightbox')).toBeTruthy(); - expect(trigger.dataset.galleryActive).toBe('1'); + expect(screen.getByTestId('image-lightbox').dataset.galleryEnabled).toBe('true'); const close = screen.getByTestId('image-lightbox'); close.focus(); fireEvent.click(close); diff --git a/apps/desktop/src/renderer/__tests__/chatMediaLightboxFocus.test.ts b/apps/desktop/src/renderer/__tests__/chatMediaLightboxFocus.test.ts index 0c70a1fcc6b..b87b02ff9af 100644 --- a/apps/desktop/src/renderer/__tests__/chatMediaLightboxFocus.test.ts +++ b/apps/desktop/src/renderer/__tests__/chatMediaLightboxFocus.test.ts @@ -5,7 +5,7 @@ * 与 chatVideoView.test.ts 对位,覆盖 review 指出的"image/model 路径同样 * 没有把焦点移进 lightbox"的一半:不 mock ImageLightbox / ModelLightbox, * 用真实组件断言键盘打开后焦点进入弹窗(FocusScope 挂载焦点)、Esc 延迟 - * 关闭后回到预览触发器。图片路径同时保留 gallery 定位标记的既有断言。 + * 关闭后回到预览触发器。图片路径同时保留集合入口的键盘断言。 */ import React from 'react'; import { afterEach, describe, expect, it, vi } from 'vitest'; @@ -28,8 +28,8 @@ afterEach(() => { cleanup(); vi.useRealTimers(); vi.clearAllMocks(); }); describe('ChatImageView 键盘交互(真实 ImageLightbox / ModelLightbox)', () => { // ModelLightbox's 2026-06 scheme regression: the custom element reports a - // parse/load error, but its poster must stay usable and the user gets a - // toast. Existing focus tests never dispatch that event. Reuse the real + // parse/load error, but its poster must stay usable and the user gets an + // inline error with retry. Existing focus tests never dispatch that event. Reuse the real // lightbox unit harness, with fake time to detect delayed unwanted closure. it('reports a model load error and keeps the preview open', async () => { vi.useFakeTimers(); @@ -42,7 +42,9 @@ describe('ChatImageView 键盘交互(真实 ImageLightbox / ModelLightbox)', () fireEvent.error(viewer); await vi.advanceTimersByTimeAsync(300); }); - expect(toast.error).toHaveBeenCalledExactlyOnceWith('Failed to load 3D model'); + expect(screen.getByText('chat.media.modelLoadFailed')).toBeTruthy(); + expect(screen.getByRole('button', { name: 'attachmentPreview.retry' })).toBeTruthy(); + expect(toast.error).not.toHaveBeenCalled(); expect(onClose).not.toHaveBeenCalled(); expect(viewer.isConnected).toBe(true); expect(viewer.getAttribute('src')).toBe(url); @@ -61,9 +63,6 @@ describe('ChatImageView 键盘交互(真实 ImageLightbox / ModelLightbox)', () trigger.focus(); fireEvent.keyDown(trigger, { key: 'Enter' }); - // 注:gallery 定位标记(data-gallery-active)是瞬时信号——真实 - // ImageLightbox 挂载后立即清掉(见其 mount effect),这里断不到;它的 - // 设置路径由 chatImageView.test.ts 的 mock 用例覆盖。 // 真实 ImageLightbox:portal 内出现 alt="" 的大图(触发器缩略图 alt 是 // 文件名,不会撞)。 const lightboxImg = document.body.querySelector('img[alt=""]'); diff --git a/apps/desktop/src/renderer/__tests__/chatVideoView.test.ts b/apps/desktop/src/renderer/__tests__/chatVideoView.test.ts index 17e9cdfea76..68c21871618 100644 --- a/apps/desktop/src/renderer/__tests__/chatVideoView.test.ts +++ b/apps/desktop/src/renderer/__tests__/chatVideoView.test.ts @@ -51,9 +51,14 @@ describe('ChatVideoView inline keyboard controls', () => { expect(screen.queryByRole('dialog')).toBeNull(); expect(screen.queryByRole('button', { name: 'chat.lightbox.close' })).toBeNull(); + await user.tab(); + const preview = screen.getByRole('button', { name: 'attachmentPreview.open' }); + expect(document.activeElement).toBe(preview); await user.tab(); expect(document.activeElement).toBe(screen.getByRole('button', { name: 'After' })); await user.tab({ shift: true }); + expect(document.activeElement).toBe(preview); + await user.tab({ shift: true }); expect(document.activeElement).toBe(video); }); }); diff --git a/apps/desktop/src/renderer/__tests__/openPathFeedback.test.ts b/apps/desktop/src/renderer/__tests__/openPathFeedback.test.ts index 84bb3d013cd..1fde3d88c06 100644 --- a/apps/desktop/src/renderer/__tests__/openPathFeedback.test.ts +++ b/apps/desktop/src/renderer/__tests__/openPathFeedback.test.ts @@ -2,11 +2,6 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { invokeOpenPath } from '../../preload/openPath'; import { shouldOpenTextLightbox } from '../lib/filePreview'; -import { toast } from '../lib/toast'; - -vi.mock('../lib/toast', () => ({ toast: { error: vi.fn() } })); -vi.mock('@/i18n', () => ({ i18n: { t: (key: string) => key } })); -vi.mock('../lib/remoteFileOpen', () => ({ openRemoteChatFile: vi.fn() })); afterEach(() => { vi.unstubAllGlobals(); @@ -25,16 +20,17 @@ describe('opening a non-text file from chat', () => { window.electronAPI = { openPath: (target: string) => invokeOpenPath(invoke, target), } as typeof window.electronAPI; - expect(await shouldOpenTextLightbox('report.pdf')).toBe(false); - expect(toast.error).toHaveBeenCalledTimes(visible ? 1 : 0); - if (visible) expect(toast.error).toHaveBeenCalledWith(message); + const result = await window.electronAPI.openPath('report.pdf'); + expect(result).toEqual({ success: false, error: message, ...(!visible ? { failureKind: 'ipc_lifecycle' } : {}) }); }); - it('still shows an ordinary system error returned by main', async () => { + it('routes PDF to the in-app preview without invoking the system app', async () => { vi.stubGlobal('electronAPI', { openPath: vi.fn().mockResolvedValue({ success: false, error: 'No application associated' }), }); - await shouldOpenTextLightbox('report.pdf'); - expect(toast.error).toHaveBeenCalledWith('No application associated'); + expect(await shouldOpenTextLightbox('report.pdf')).toBe(true); + expect(window.electronAPI.openPath).not.toHaveBeenCalled(); + const systemError = { success: false, error: 'No application associated' }; + expect(await invokeOpenPath(vi.fn().mockResolvedValue(systemError), 'report.pdf')).toEqual(systemError); }); }); diff --git a/apps/desktop/src/renderer/__tests__/textLightbox.test.ts b/apps/desktop/src/renderer/__tests__/textLightbox.test.ts index 0ba028aaa79..6c89d1ca145 100644 --- a/apps/desktop/src/renderer/__tests__/textLightbox.test.ts +++ b/apps/desktop/src/renderer/__tests__/textLightbox.test.ts @@ -16,7 +16,8 @@ import '../themes/colors'; * moved exclusively to the Oversize CTA after the file-modal-and-toast * polish on 2026-04-19. * F4 Toolbar Close (X) button wires to handleClose. - * F4 Doc Card sized at 80vw/80vh capped 1600/1200 (no longer 1024×640). + * Private-card geometry and tooltip source checks retired with PreviewChrome; + * real closing and rendering are covered in runtime acceptance. * F5 Oversize body shows triangle-alert + i18n 阈值文案 + Open-in-system CTA * (Oversize is the SOLE caller of window.electronAPI.openPath now). * F6 Esc closes; Doc Card clicks stop propagation; Overlay onClick=handleClose @@ -58,19 +59,6 @@ describe('TextLightbox — F6 close behaviour', () => { expect(source).toMatch(/handleClose\(\)/); }); - it('backdrop button wires onClick to handleClose (backdrop click closes)', () => { - expect(source).toMatch(/ { - // The backdrop close target is a separate background button behind the Doc - // Card. Toolbar buttons and text selection stay on the higher card layer. - expect(source).toMatch(/data-text-lightbox-card/); - expect(source).toContain('backdrop is a separate background button behind the Doc Card'); - expect(source).toMatch(/zIndex:\s*1/); - }); - it('handleClose guards against double-fire via isClosingRef', () => { expect(source).toContain('isClosingRef.current'); expect(source).toMatch(/if\s*\(isClosingRef\.current\)\s*return;/); @@ -85,23 +73,6 @@ describe('TextLightbox — F3 overlay style', () => { expect(source).toContain("background: 'var(--overlay-lightbox)'"); for (const mode of ['light', 'dark'] as const) expect(colorRegistry.resolveDefault('overlay-lightbox', mode)).toBe('rgba(0, 0, 0, 0.85)'); }); - - it('overlay is rendered via createPortal to document.body', () => { - expect(source).toMatch(/createPortal\(overlay,\s*document\.body\)/); - }); - - it('keeps every toolbar tooltip above the lightbox overlay', () => { - expect(source).toMatch(/TEXT_LIGHTBOX_OVERLAY_Z_INDEX\s*=\s*9999/); - expect(source).toMatch( - /TEXT_LIGHTBOX_TOOLTIP_STYLE\s*=\s*\{[\s\S]*zIndex:\s*TEXT_LIGHTBOX_OVERLAY_Z_INDEX\s*\+\s*1/, - ); - - const tooltipContents = source.match(/ { const openInSystemMatches = source.match(/onClick=\{openInSystem\}/g) ?? []; expect(openInSystemMatches.length).toBe(1); }); - - it('Toolbar adds an X close button wired to handleClose', () => { - // The new affordance — a visible × button on the Toolbar's right side - // gives users a third escape hatch (besides Esc and backdrop click). - expect(source).toMatch(/import\s+\{[^}]*\bX\b[^}]*\}\s+from\s+'lucide-react'/); - // Two `onClick={handleClose}` are now expected: overlay backdrop + Toolbar X. - const handleCloseClicks = source.match(/onClick=\{handleClose\}/g) ?? []; - expect(handleCloseClicks.length).toBeGreaterThanOrEqual(2); - }); - - it('Doc Card sizes to 80vw/80vh with 1600/1200 caps (post-polish)', () => { - // Old hard-coded `min(1024px, …)` caused the modal to feel small + offset. - // Polish picks the design-spec 80%/80% with sane upper bounds for 4K/5K. - expect(source).toMatch(/width:\s*'80vw'/); - expect(source).toMatch(/height:\s*'80vh'/); - expect(source).toMatch(/maxWidth:\s*'1600px'/); - expect(source).toMatch(/maxHeight:\s*'1200px'/); - // And explicitly NOT the old hard-coded 1024 / 640 values. - expect(source).not.toMatch(/min\(1024px/); - expect(source).not.toMatch(/min\(640px/); - }); }); // ── F5: Oversize body ─────────────────────────────────────────────────────── @@ -178,9 +128,7 @@ describe('TextLightbox — F5 Oversize body', () => { it('uses i18n copy with dynamic file size and the preview cap', () => { // 文案必须通过 i18n 注入动态 size/limit,组件内不再保留英文裸文案。 expect(source).toContain("t('chat.textLightbox.oversizeTitle')"); - expect(source).toContain( - "t('chat.textLightbox.oversizeBody', { size: oversizeSizeText, limit: oversizeLimitMb })", - ); + expect(source.replace(/\s+/g, "")).toContain("t('chat.textLightbox.oversizeBody',{size:oversizeSizeText,limit:oversizeLimitMb"); expect(source).not.toMatch( /This file is \{oversizeSizeText\}, exceeding the \{oversizeLimitMb\} MB preview limit/, ); diff --git a/apps/desktop/src/renderer/__tests__/unsupportedTextPreview.test.ts b/apps/desktop/src/renderer/__tests__/unsupportedTextPreview.test.ts index d4c4dcf9065..245342e86e2 100644 --- a/apps/desktop/src/renderer/__tests__/unsupportedTextPreview.test.ts +++ b/apps/desktop/src/renderer/__tests__/unsupportedTextPreview.test.ts @@ -1,16 +1,19 @@ /** * unsupportedTextPreview.test.ts * --------------------------------------------------------------------------- - * Regression tests for file chips that route to TextLightbox. PDF and other - * non-text formats must fall back to the OS default app instead of mounting - * the text previewer. + * Regression tests for file chips that route to TextLightbox. Non-text formats + * use the unified type dispatcher instead of being decoded as text or silently + * opened outside Cindy. */ -import { describe, expect, it } from 'vitest'; +import { afterEach, describe, expect, it, vi } from 'vitest'; import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { isTextPreviewSupported } from '../lib/textPreview'; +import { shouldOpenTextLightbox, shouldOpenTextLightboxForOrigin } from '../lib/filePreview'; + +afterEach(() => vi.unstubAllGlobals()); const source = (relative: string) => readFileSync(resolve(__dirname, '..', ...relative.split('/')), 'utf8'); @@ -29,11 +32,15 @@ describe('unsupported text preview guard', () => { expect(isTextPreviewSupported('archive.zip')).toBe(false); }); - it('fallback helper opens unsupported formats through the system app', () => { - const src = source('lib/filePreview.ts'); - expect(src).toContain('isTextPreviewSupported(filePath)'); - expect(src).toContain('window.electronAPI.openPath(filePath)'); - expect(src).toContain('toast.error'); + it('routes non-text files through the in-app viewer without opening the system app', async () => { + const openPath = vi.fn(); + vi.stubGlobal('window', { electronAPI: { openPath } }); + for (const filePath of ['spec.pdf', 'photo.png', 'archive.zip']) { + expect(await shouldOpenTextLightbox(filePath)).toBe(true); + expect(await shouldOpenTextLightboxForOrigin({ origin: { kind: 'local' }, workingDir: '/task' }, filePath)).toBe(true); + } + expect(await shouldOpenTextLightbox('')).toBe(false); + expect(openPath).not.toHaveBeenCalled(); }); it('guards all TextLightbox entry points that can receive file chips', () => { diff --git a/apps/desktop/src/renderer/cindy-brain/GhostMediaLightboxHost.tsx b/apps/desktop/src/renderer/cindy-brain/GhostMediaLightboxHost.tsx index b39df223573..e0f09244fd1 100644 --- a/apps/desktop/src/renderer/cindy-brain/GhostMediaLightboxHost.tsx +++ b/apps/desktop/src/renderer/cindy-brain/GhostMediaLightboxHost.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, type ReactNode } from 'react'; +import { useEffect, useRef, useState, type ReactNode } from 'react'; import { ImageLightbox } from '@/components/chat/ImageLightbox'; import { VideoLightbox } from '@/components/chat/VideoLightbox'; @@ -23,7 +23,12 @@ export function GhostMediaLightboxHost({ * 图片 lightbox 显示「发送到对话」——预览的图一键落进该会话的附件托盘。 */ sessionId?: string; }): ReactNode { - const [media, setMedia] = useState<{ src: string; kind: 'image' | 'video' } | null>(null); + const [media, setMedia] = useState<{ src: string; kind: 'image' | 'video'; + originSessionId?: string; + } | null>(null); + + const openingSession = useRef(sessionId); + openingSession.current = sessionId; useEffect(() => { const api = window.electronAPI?.ghosts; @@ -36,7 +41,7 @@ export function GhostMediaLightboxHost({ const active = document.activeElement; if (active instanceof HTMLElement) active.blur(); // 旧 main(重启前)不带 kind:按图片处理,与历史行为一致。 - setMedia({ src: next, kind: kind ?? 'image' }); + setMedia({ src: next, kind: kind ?? 'image', originSessionId: openingSession.current }); }); }, []); @@ -44,5 +49,8 @@ export function GhostMediaLightboxHost({ if (media.kind === 'video') { return setMedia(null)} />; } - return setMedia(null)} sessionId={sessionId} />; + return ( + setMedia(null)} sessionId={media.originSessionId} + /> + ); } diff --git a/apps/desktop/src/renderer/components/chat/AttachmentCollection.tsx b/apps/desktop/src/renderer/components/chat/AttachmentCollection.tsx new file mode 100644 index 00000000000..1d2fa898dff --- /dev/null +++ b/apps/desktop/src/renderer/components/chat/AttachmentCollection.tsx @@ -0,0 +1,132 @@ +import { lazy, Suspense, useEffect, useRef, useState } from 'react'; +import { + AttachmentCollectionContext, + ActivePreviewCollection, + type AttachmentCollectionItem, +} from './AttachmentCollectionContext'; +export { + AttachmentCollectionContext, + ActivePreviewCollection, + useOpeningCollection, + type AttachmentCollectionItem, +} from './AttachmentCollectionContext'; +import { ImageLightbox } from './ImageLightbox'; +import { VideoLightbox } from './VideoLightbox'; +import { createPortal } from 'react-dom'; +import { PreviewHeader } from './PreviewChrome'; +import { Spinner } from '@/components/ui/spinner'; +const TextLightbox = lazy(() => + import('./TextLightbox').then((module) => ({ default: module.TextLightbox })), +); +import { ChatSessionFileProvider, useChatSessionFile } from './ChatSessionFileContext'; +import { ImageGalleryContext } from './ImageGalleryContext'; + +/** Snapshot the opening collection and file origin. Live messages cannot retarget an open viewer. */ +export function AttachmentCollectionPreview({ + items: openingItems, + source, + onClose, + initial, + initialLine, + triggerRef, +}: { + items: readonly AttachmentCollectionItem[]; + source: string; + onClose(): void; + initial?: React.ReactNode; + initialLine?: number; + triggerRef?: React.RefObject; +}) { + const [items] = useState(() => [...openingItems]); + const [start] = useState(() => + Math.max( + 0, + items.findIndex((item) => item.source === source), + ), + ); + const [index, setIndex] = useState(start); + const fileContext = useChatSessionFile(); + const [origin] = useState(fileContext); + const beforeLeave = useRef<((leave: () => void) => void) | null>(null); + const item = items[index]!; + const closing = useRef(false); + const close = () => { + closing.current = true; + onClose(); + }; + const restoreFocus = (event: Event) => { + if (!triggerRef) return; + event.preventDefault(); + const trigger = triggerRef.current; + if (closing.current && trigger && document.contains(trigger)) trigger.focus({ preventScroll: true }); + }; + const select = (next: number) => { + if (next < 0 || next >= items.length || next === index) return; + const leave = () => { + beforeLeave.current = null; + setIndex(next); + }; + if (beforeLeave.current) beforeLeave.current(leave); + else leave(); + }; + const selectRef = useRef(select); + selectRef.current = select; + useEffect(() => { + const key = (event: KeyboardEvent) => { + if (event.defaultPrevented || event.isComposing || document.fullscreenElement) return; + const target = event.target as HTMLElement; + if (target.closest('input, textarea, [contenteditable="true"], video, audio, [role="menu"]')) + return; + if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return; + event.preventDefault(); + selectRef.current(index + (event.key === 'ArrowLeft' ? -1 : 1)); + }; + document.addEventListener('keydown', key); + return () => document.removeEventListener('keydown', key); + }, [index]); + return ( + + + + + + + + , + document.body, + )} + > + {index === start && initial ? ( + initial + ) : item.media && item.kind === 'image' ? ( + + ) : item.media && (item.kind === 'video' || item.kind === 'audio') ? ( + + ) : ( + + )} + + + + + + ); +} diff --git a/apps/desktop/src/renderer/components/chat/AttachmentCollectionContext.ts b/apps/desktop/src/renderer/components/chat/AttachmentCollectionContext.ts new file mode 100644 index 00000000000..b30c2b957f7 --- /dev/null +++ b/apps/desktop/src/renderer/components/chat/AttachmentCollectionContext.ts @@ -0,0 +1,28 @@ +import { createContext, useContext, type ComponentProps } from 'react'; +import type { PreviewItem } from '@cindy/maker-shared/attachment-preview'; +import type { ImageLightbox } from './ImageLightbox'; + +export type AttachmentCollectionItem = PreviewItem & { + /** Media URLs are already resolved by the opening surface; files retain host paths. */ + media?: boolean; + image?: Partial, + 'src' | 'annotationEdit' | 'initialStrokes' | 'annotationFallbackSrc' + >>; +}; + +export const AttachmentCollectionContext = createContext< + readonly AttachmentCollectionItem[] | null +>(null); +export const ActivePreviewCollection = createContext<{ + items: readonly AttachmentCollectionItem[]; + index: number; + select(index: number): void; + beforeLeave: { current: ((leave: () => void) => void) | null }; + restoreFocus?(event: Event): void; +} | null>(null); + +export function useOpeningCollection(source: string) { + const items = useContext(AttachmentCollectionContext); + return items && items.length > 1 && items.some((item) => item.source === source) ? items : null; +} diff --git a/apps/desktop/src/renderer/components/chat/ChatImageView.tsx b/apps/desktop/src/renderer/components/chat/ChatImageView.tsx index 3aa8ee32624..a203cf7a331 100644 --- a/apps/desktop/src/renderer/components/chat/ChatImageView.tsx +++ b/apps/desktop/src/renderer/components/chat/ChatImageView.tsx @@ -249,7 +249,6 @@ export function ChatImageView({ // 打 image lightbox 前,在被点的这张图上做个临时标记,让 lightbox // 精确定位到用户点的这张(而不是按 src 搜到的第一张)。model 路径 // 不进 gallery,不标记。 - if (next === 'image') e.currentTarget.dataset.galleryActive = '1'; setLightboxOpen(next); }} onContextMenu={(e) => { @@ -353,13 +352,16 @@ export function ChatImageView({ // 单张展示:gallery 按烧录图 src 定位,换成原图后无法匹配。 // 原图若已被清理,lightbox 退回烧录图(annotationFallbackSrc)供查看 / 叠加新标。 ) : ( - + ))} {lightboxOpen === 'model' && modelFile && ( (null); + const [previewTime, setPreviewTime] = useState(null); const [failedSrc, setFailedSrc] = useState(null); // 远程会话改写到 cindy-remote-media://(OSS range 流式);本地原样。下游统一用 displaySrc。 const displaySrc = useRemoteMediaUrl(src, sessionId); @@ -168,7 +171,32 @@ export function ChatVideoView({ src, filename, variant, sessionId }: ChatVideoVi className={cn(className, 'block', CHAT_FOCUS_CLASS)} onError={() => setFailedSrc(displaySrc)} /> +
+ { + videoRef.current?.pause(); + setPreviewTime(videoRef.current?.currentTime ?? 0); + }} + > + + +
+ {previewTime !== null ? ( + { + setPreviewTime(null); + if (videoRef.current) { + if (time !== undefined) videoRef.current.currentTime = time; + videoRef.current.focus({ preventScroll: true }); + } + }} + /> + ) : null} {canRevealInFolder ? ( ; + onClose(): void; +}) { + const { t } = useTranslation(); + const restorePreviewFocus = usePreviewFocusReturn(); + const collection = useContext(ActivePreviewCollection); + const ctx = useChatSessionFile(); + const remote = isRemoteFileOrigin(ctx.origin) ? ctx.origin : null; + const [local, setLocal] = useState(remote ? null : filePath); + const [failed, setFailed] = useState(false); + const [retry, setRetry] = useState(0); + const overlay = useRef(null); + const name = fileName ?? filePath.split(/[\\/]/).pop() ?? filePath; + const kind = attachmentPreviewKind(filePath); + const downloadOnly = isSafetyDowngradedAttachment({ name, path: filePath }); + useEffect(() => { + if (!remote) { + setLocal(filePath); + return; + } + let cancelled = false; + setFailed(false); + setLocal(null); + void fetchChatFileToCache(remote, ctx.workingDir, filePath) + .then((result) => { + if (!cancelled) { + if (result.ok) setLocal(result.cachePath); + else setFailed(true); + } + }) + .catch(() => { + if (!cancelled) setFailed(true); + }); + return () => { + cancelled = true; + }; + }, [remote, ctx.workingDir, filePath, retry]); + const close = useCallback(() => { + onClose(); + triggerRef?.current?.focus({ preventScroll: true }); + }, [onClose, triggerRef]); + useEffect(() => { + const key = (event: KeyboardEvent) => { + if ( + event.key === 'Escape' && + !event.defaultPrevented && + !document.fullscreenElement && + !document.querySelector('[data-radix-popper-content-wrapper]') + ) { + event.preventDefault(); + close(); + } + }; + // Media viewers own their own keyboard scope. + if (local && ['image', 'video', 'audio', 'model'].includes(kind)) return; + document.addEventListener('keydown', key); + return () => document.removeEventListener('keydown', key); + }, [close, kind, local]); + if (local && kind === 'image') + return ( + + ); + if (local && (kind === 'video' || kind === 'audio')) + return ( + + ); + if (local && kind === 'model') + return ( + + ); + return createPortal( + { + event.preventDefault(); + overlay.current?.focus(); + }} + > +
+
+ {local ? ( + + ) : ( +
+ {failed ? ( + <> + {t('logic.errors.openFileFailed')} + + + ) : ( + + )} +
+ )} +
+ {kind !== 'pdf' ? : null} + { + if (downloadOnly) { + void saveChatAttachmentWithToasts(ctx, { name, path: filePath }); + return; + } + void window.electronAPI + .saveMediaAs({ url: toLocalFileUrl(local) }) + .catch(() => toast.error(t('chat.userMessage.attachmentSaveFailed'))); + } + : undefined + } + menu={ + <> + { + if (local) void window.electronAPI.openPath(local); + }} + > + {t('attachmentPreview.open')} + + { + if (local) void window.electronAPI.showItemInFolder({ filePath: local }); + }} + > + {t('chat.lightbox.openInExplorer')} + + + } + /> +
+
, + document.body, + ); +} + +function LocalFileBody({ path, sessionId }: { path: string; sessionId?: string }) { + const split = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')); + const workdir = split === 0 ? '/' : path.slice(0, split); + const relPath = path.slice(split + 1); + const { content } = useFileContent(workdir, relPath); + return ( + + ); +} diff --git a/apps/desktop/src/renderer/components/chat/ImageGalleryContext.tsx b/apps/desktop/src/renderer/components/chat/ImageGalleryContext.tsx index 4a2ca3a16c2..8d293932fdb 100644 --- a/apps/desktop/src/renderer/components/chat/ImageGalleryContext.tsx +++ b/apps/desktop/src/renderer/components/chat/ImageGalleryContext.tsx @@ -1,25 +1,11 @@ -/** - * ImageGalleryContext - * --------------------------------------------------------------------------- - * 把"当前会话内全部可翻阅图片的有序列表"下发给 ImageLightbox。 - * - * 为什么需要它:聊天消息流为了性能只渲染末尾一段(RENDER_WINDOW_INITIAL_ITEMS), - * 早期消息不在 DOM 里。如果 lightbox 只扫 DOM,计数和翻页就只能覆盖"已加载"的图, - * 用户得先往上滚动把老图加载出来才会被算进去(codex review P2)。 - * - * 所以改由 MessageStream 从 **allRenderItems(全量,未窗口裁剪)** 派生完整列表, - * 经本 context 传给 lightbox —— 计数立刻是整个会话的总数,也能直接翻到最早那张 - * (img 直接按 url 加载,不依赖缩略图是否挂载)。 - * - * 列表项带标注元数据:持久化的烧录标注图翻页到达时,lightbox 据此打开 - * "原图 + 可编辑笔迹"视图(与直接点击该图的再编辑分支同语义),否则翻页会把 - * 烧录图当普通图降级发送(codex review P2)。 - * - * 非聊天场景(上传预览条 / 输入框预览 / 文件浏览器)不在 Provider 内,取默认 - * null,ImageLightbox 退回原有的扫 DOM / 单图行为,完全不受影响。 +/** Explicit ordered images supplied by the opening message, draft or file list. + * Portals retain this scope; no viewer scans the DOM or a whole session history. + * Annotation metadata travels with each image so switching keeps editable strokes. */ import { createContext } from 'react'; import type { AnnotationStroke } from './lightboxAnnotations'; +import type { AttachmentCollectionItem } from './AttachmentCollectionContext'; +import { previewSourceName } from '@cindy/maker-shared/attachment-preview'; /** 会话画廊里的一张图:src 为渲染所用 URL(标注图 = 烧录图)。 */ export interface GalleryImage { @@ -36,5 +22,19 @@ export interface GalleryImage { annotationStrokes?: readonly AnnotationStroke[]; } -/** 当前会话全部图片的有序列表(全量)。无 Provider 时为 null。 */ +/** Opening surface collection. Without a provider the viewer shows only its source. */ export const ImageGalleryContext = createContext(null); + +/** Keep the displayed (burned) identity for selection, but edit the original pixels. */ +export function galleryImagePreviewItem(image: GalleryImage, id: string): AttachmentCollectionItem { + return { + id: image.galleryId ?? id, + name: previewSourceName(image.src, 'Image'), + source: image.src, + kind: 'image', + media: true, + image: image.annotationSourceUrl + ? { src: image.annotationSourceUrl, initialStrokes: image.annotationStrokes, annotationFallbackSrc: image.src } + : undefined, + }; +} diff --git a/apps/desktop/src/renderer/components/chat/ImageLightbox.tsx b/apps/desktop/src/renderer/components/chat/ImageLightbox.tsx index 5e05fab29e4..f7877c24368 100644 --- a/apps/desktop/src/renderer/components/chat/ImageLightbox.tsx +++ b/apps/desktop/src/renderer/components/chat/ImageLightbox.tsx @@ -1,3 +1,4 @@ +import { usePreviewFocusReturn } from './PreviewChrome'; /** * ImageLightbox * --------------------------------------------------------------------------- @@ -46,6 +47,9 @@ import { useCallback, useContext, useEffect, useLayoutEffect, useRef, useState } import { createPortal } from 'react-dom'; import { FocusScope } from '@radix-ui/react-focus-scope'; import { + Minus, + Plus, + Maximize, Check, ChevronLeft, ChevronRight, @@ -64,7 +68,6 @@ import { useTranslation } from 'react-i18next'; import { toast } from '@/lib/toast'; import { createLogger } from '@/lib/logger'; import { isEditableKeyboardTarget } from '@/lib/editableKeyboardTarget'; -import { cn } from '@/lib/utils'; import { extractIpcError } from '@/utils/ipcError'; import { getMimeType, extractExt, type AttachedFile } from '@/lib/fileTypes'; import { @@ -97,7 +100,6 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import { Tip } from '@/components/ui/tooltip'; import { useChatSessionFile } from './ChatSessionFileContext'; import { LIGHTBOX_MAX_SCALE, @@ -113,6 +115,17 @@ import { } from './lightboxGestures'; import { ImageGalleryContext, type GalleryImage } from './ImageGalleryContext'; import { containImageSize } from './imageDisplaySize'; +import { + ActivePreviewCollection, + AttachmentCollectionPreview, + useOpeningCollection, +} from './AttachmentCollection'; +import { + PreviewHeader, + PreviewFooter, + PreviewButton as LightboxToolbarButton, +} from './PreviewChrome'; +import { previewAdjacentIndex, previewSourceName } from '@cindy/maker-shared/attachment-preview'; /** 图片不允许缩得比"适配窗口"更小,所以下限是 1 而不是共享常量的 0.2。 */ const IMAGE_MIN_SCALE = 1; @@ -167,6 +180,7 @@ function isMacPlatform(): boolean { interface ImageLightboxProps { src: string; + filename?: string; onClose: () => void; /** * Stable identity of the clicked gallery item. Needed when the source lives @@ -298,102 +312,23 @@ function isDirectCacheable(src: string): boolean { } /** 左右翻页圆形按钮的内联样式。lightbox overlay 不走 Tailwind,这里同样用内联。 */ -function navButtonStyle(side: 'left' | 'right'): React.CSSProperties { - return { - position: 'fixed', - top: '50%', - [side]: 24, - transform: 'translateY(-50%)', - width: 48, - height: 48, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - borderRadius: '9999px', - border: 'none', - background: 'rgba(0,0,0,0.45)', - color: '#fff', - cursor: 'pointer', - zIndex: 10000, - }; -} - -/** - * 顶部居中 "n / 总数" 计数器的内联样式。原在底部,底部让位给媒体操作栏后移到 - * 顶部居中——与手机版 lightbox 的页码位置一致。 - */ -const counterStyle: React.CSSProperties = { - position: 'fixed', - top: 24, - left: '50%', - transform: 'translateX(-50%)', - padding: '4px 12px', - borderRadius: '9999px', - background: 'rgba(0,0,0,0.45)', - color: '#fff', - fontSize: 13, - fontVariantNumeric: 'tabular-nums', - userSelect: 'none', - zIndex: 10000, -}; - -/** - * 收集当前聊天滚动区里所有可翻阅图片,按 DOM 顺序(即视觉 / 时间顺序)返回。 - * 数据源是各图片组件 `` 上的 `data-gallery-src` 属性。优先限定在 - * [data-scroll-container](聊天消息区,和 scroll-lock 同一个)内,避免抓到 - * 其它面板里的图。 - * - * `activeIndex` 是被点击那张图在列表里的位置:点击时调用方会在被点的 `` - * 上打一个临时 `data-gallery-active="1"` 标记,这里据此精确定位起始图。这样 - * 即便同一 URL 在会话里非相邻地出现多次(用户重复上传 / 工具返回同一缓存图), - * 也能从用户真正点的那张开始,而不是按 URL 搜到的第一张。找不到标记时返回 -1, - * 由调用方退回按 src 搜索。注意:这里**不做去重**,让列表下标和 DOM 元素一一 - * 对应,activeIndex 才准。 - */ -function collectGallery(): { items: GalleryImage[]; activeIndex: number } { - const root: ParentNode = document.querySelector('[data-scroll-container]') ?? document; - const els = Array.from(root.querySelectorAll('[data-gallery-src]')); - const items: GalleryImage[] = []; - let activeIndex = -1; - els.forEach((el, i) => { - items.push({ src: el.dataset.gallerySrc ?? '' }); - if (el.dataset.galleryActive === '1') activeIndex = i; - }); - return { items, activeIndex }; -} - -/** 清掉所有遗留的 `data-gallery-active` 临时标记(lightbox 读取起始下标后调用)。 */ -function clearGalleryActiveMarks(): void { - document.querySelectorAll('[data-gallery-active]').forEach((el) => { - delete el.dataset.galleryActive; - }); -} - -/** - * 在「会话全量列表 `full`」里定位用户点的那张图的下标。 - * - * 渲染窗口是全量列表的**末尾连续段**,所以 DOM 里已加载的 gallery 图就是 full 的 - * 尾巴。据此可做精确的位置映射:被点图在已加载段里的序号 `k`(由 data-gallery-active - * 标记定位)+「full 长度 − 已加载数」= 它在 full 里的精确位置 —— 即便同一 URL 在 - * 会话里非相邻出现多次,也能落到用户真正点的那张(修 greptile P1)。 - * - * 映射结果再用 src 校验一次:`full[mapped] === src` 才采用;不一致(列表与 DOM 口径 - * 偶有偏差,如失败图被换成占位符)则退回按 src 搜索的首个匹配,保证不会定位到错图。 - */ -function resolveStartIndexInFull(full: readonly GalleryImage[], src: string): number { - const { items: domItems, activeIndex } = collectGallery(); - const M = domItems.length; - const T = full.length; - if (activeIndex >= 0 && M > 0 && T >= M) { - const mapped = T - M + activeIndex; - if (full[mapped]?.src === src) return mapped; - } - const bySrc = full.findIndex((g) => g.src === src); - return bySrc >= 0 ? bySrc : 0; +export function ImageLightbox(props: ImageLightboxProps) { + const items = useOpeningCollection(props.annotationFallbackSrc ?? props.src); + return items ? ( + } + /> + ) : ( + + ); } -export function ImageLightbox({ +function ImageLightboxContent({ src, + filename, onClose, enableGallery = false, galleryId, @@ -405,6 +340,7 @@ export function ImageLightbox({ }: ImageLightboxProps) { const { t } = useTranslation(); // 会话全量图片列表(MessageStream 下发);非聊天场景无 Provider 时为 null。 + const restorePreviewFocus = usePreviewFocusReturn(); const sessionImages = useContext(ImageGalleryContext); // 当前会话 id(发送到对话用):prop 优先(文件浏览器等 provider 外宿主), // 否则取聊天流 context;都没有则为 undefined → 不显示该动作。 @@ -412,6 +348,8 @@ export function ImageLightbox({ const chatSessionId = sessionIdProp ?? contextSessionId; const sidebarTargetSessionId = useSidebarTargetSessionId(chatSessionId); const [isVisible, setIsVisible] = useState(false); + const [loadFailed, setLoadFailed] = useState(null); + const [retryVersion, setRetryVersion] = useState(0); // 会话内翻图列表 + 起始下标。只在挂载时算一次,翻页只在已知列表内移动下标。 // enableGallery 关时退化成单张 [src]。开启时: // 1) 优先用 context 的全量列表(覆盖整个会话,含渲染窗口外的早期图), @@ -446,13 +384,10 @@ export function ImageLightbox({ return { items: sessionImages, start: keyedIndex }; } if (sessionImages?.some((g) => g.src === src)) { - return { items: sessionImages, start: resolveStartIndexInFull(sessionImages, src) }; - } - const { items, activeIndex } = collectGallery(); - if (items.some((g) => g.src === src)) { - const bySrc = items.findIndex((g) => g.src === src); - const start = activeIndex >= 0 ? activeIndex : bySrc; - return { items, start }; + return { items: sessionImages, start: Math.max( + 0, + sessionImages.findIndex((image) => image.src === src)), + }; } return { items: [{ src }], start: 0 }; }); @@ -570,7 +505,7 @@ export function ImageLightbox({ ? containImageSize( { width: currentNaturalSize.w, height: currentNaturalSize.h }, viewportSize.width - 80, - viewportSize.height - 80, + viewportSize.height - 160, ) : null; @@ -658,8 +593,55 @@ export function ImageLightbox({ }; // once-bound keydown handler 通过 ref 调用最新的放弃逻辑(依赖 confirmDialog 等 // 每次渲染的值)。 - const discardAnnotationRef = useRef(discardAnnotation); - discardAnnotationRef.current = discardAnnotation; + const activeCollection = useContext(ActivePreviewCollection); + const requestClose = async (leave = handleClose) => { + if (confirmPendingRef.current) return; + if (!hasUnsavedStrokeChanges()) { + leave(); + return; + } + if (!confirmDialog) return; + confirmPendingRef.current = true; + try { + const canSave = Boolean(annotationEdit || chatSessionId); + const choice = canSave + ? await confirmDialog.confirmThree({ + title: t('chat.media.annotateDiscardConfirmTitle'), + description: t('chat.media.annotateDiscardConfirmDescription'), + confirmText: t('chat.media.annotateSave'), + cancelText: t('chat.media.annotateDiscardConfirmCancel'), + tertiaryText: t('chat.media.annotateDiscardConfirmAction'), + }) + : (await confirmDialog.confirm({ + title: t('chat.media.annotateDiscardConfirmTitle'), + confirmText: t('chat.media.annotateDiscardConfirmAction'), + cancelText: t('chat.media.annotateDiscardConfirmCancel'), + })) + ? 'tertiary' + : 'cancel'; + if (!mountedRef.current) return; + if (choice === 'tertiary') leave(); + if (choice === 'confirm') { + if (annotationEdit) await handleAnnotationSave(leave); + else await handleSendToChat(leave); + } + } finally { + setTimeout(() => { + confirmPendingRef.current = false; + }, 0); + } + }; + const requestCloseRef = useRef(requestClose); + requestCloseRef.current = requestClose; + useEffect(() => { + if (!activeCollection) return; + activeCollection.beforeLeave.current = (leave) => { + void requestCloseRef.current(leave); + }; + return () => { + activeCollection.beforeLeave.current = null; + }; + }, [activeCollection?.beforeLeave]); const applyViewport = useCallback((next: LightboxViewport) => { viewportRef.current = next; @@ -1046,23 +1028,18 @@ export function ImageLightbox({ return () => cancelAnimationFrame(raf); }, []); - // 起始下标已在 useState 初始化时从 data-gallery-active 读出,标记使命完成, - // 挂载后立即清掉,避免污染下一次打开(否则下次扫描会读到上次的残留标记)。 - useEffect(() => { - clearGalleryActiveMarks(); - }, []); - // Esc 关闭;+/-/0 缩放;开启会话画廊时用 ←/→ 翻图。 // biome-ignore lint/correctness/useExhaustiveDependencies: galleryItems 在挂载时固定;这里只订阅一次全局键盘事件,避免反复绑定 DOM listener。 useEffect(() => { const handler = (e: KeyboardEvent) => { // 放弃确认框打开期间,按键归确认框(含关闭它的那次 Esc)。 - if (confirmPendingRef.current) return; + if (confirmPendingRef.current || e.defaultPrevented || isEditableKeyboardTarget(e.target)) return; if (e.key === 'Escape') { if (performance.now() - lastMenuCloseAt.current < 150) return; // 标注模式中 Esc = 放弃标注(清笔迹退出),再按一次才关 lightbox。 if (isAnnotatingRef.current) { - void discardAnnotationRef.current(); + if (!hasUnsavedStrokeChanges()) performDiscardAnnotation(); + else void requestCloseRef.current(); return; } handleClose(); @@ -1125,10 +1102,10 @@ export function ImageLightbox({ if (galleryItems.length <= 1) return; if (e.key === 'ArrowRight') { e.preventDefault(); - setIndex((i) => (i + 1) % galleryItems.length); + setIndex((i) => previewAdjacentIndex(i, 1, galleryItems.length)); } else if (e.key === 'ArrowLeft') { e.preventDefault(); - setIndex((i) => (i - 1 + galleryItems.length) % galleryItems.length); + setIndex((i) => previewAdjacentIndex(i, -1, galleryItems.length)); } }; // Space 松开:结束平移修饰(只处理本组件接管过的那次按键)。 @@ -1285,7 +1262,7 @@ export function ImageLightbox({ * useAttachments(刷新托盘)与 ChatInput(重设文本并聚焦末尾——正好把焦点 * 交还输入框,与手机版"回会话页聚焦 composer"一致)。成功后关闭 lightbox。 */ - async function handleSendToChat(): Promise { + async function handleSendToChat(afterSave = handleClose): Promise { if (!chatSessionId) return; setMenuPos(null); try { @@ -1410,7 +1387,7 @@ export function ImageLightbox({ { preserveRemoteOptimisticRecovery: true }, ); toast.success(t('chat.media.sentToChat')); - handleClose(); + afterSave(); } catch (err) { toast.error(extractIpcError(err)?.message ?? t('chat.media.sendToChatFailed')); } @@ -1420,13 +1397,13 @@ export function ImageLightbox({ * 编辑模式出口:把当前笔迹(可能为空——用户撤光了之前的编辑)交回调用方 * 更新托盘附件。惰性烧录:这里不产位图,发送消息时统一物化。 */ - async function handleAnnotationSave(): Promise { + async function handleAnnotationSave(afterSave = handleClose): Promise { if (!annotationEdit) return; try { await annotationEdit.onSave({ strokes: strokesRef.current.map((stroke) => ({ points: [...stroke.points] })), }); - handleClose(); + afterSave(); } catch (err) { toast.error(extractIpcError(err)?.message ?? t('chat.media.annotateSaveFailed')); } @@ -1441,6 +1418,7 @@ export function ImageLightbox({ // 的元素(ChatImageView 键盘路径 = 预览触发器,与 onClose 里的显式恢复 // 指向同一元素)。 - {/* 图片 + 标注层的公共 transform 容器:缩放/平移作用在容器上,SVG 笔迹 + { + void requestClose(); + }} + onSave={canSaveAs ? handleSaveAs : undefined} + onMenuClose={() => { + lastMenuCloseAt.current = performance.now(); + }} + menu={ + <> + {canCopy ? ( + + + {t('chat.media.copyImage')} + + ) : null} + {canOpenWith ? ( + + + {t('chat.media.openWithApp')} + + ) : null} + {canOpenInBrowser ? ( + + + {t('chat.media.openInBrowser')} + + ) : null} + {canSendToChat ? ( + void handleSendToChat()}> + + {t('chat.media.sendToChat')} + + ) : null} + + } + /> + {loadFailed === currentSrc ? ( +
+

{t('chat.media.imageMissing')}

+ +
+ ) : null} + {/* 图片 + 标注层的公共 transform 容器:缩放/平移作用在容器上,SVG 笔迹 天然跟随图片。容器是 flex item,尺寸自适应贴合图片盒。 */} {/* biome-ignore lint/a11y/noStaticElementInteractions: 容器接管缩放/平移/画笔手势;点击背景关闭由 overlay 处理。 */}
{ - setNaturalSize({ + setLoadFailed(null); + setNaturalSize({ src: currentSrc, w: e.currentTarget.naturalWidth, h: e.currentTarget.naturalHeight, @@ -1563,8 +1602,7 @@ export function ImageLightbox({ replaceStrokes([]); return; } - toast.warning(t('chat.media.imageMissing')); - handleClose(); + setLoadFailed(currentSrc); }} /> {/* 标注层:viewBox = 图片自然尺寸,归一化笔迹 × 自然尺寸 = path 坐标, @@ -1657,7 +1695,7 @@ export function ImageLightbox({ ) : null} {canSendToChat ? ( - + void handleSendToChat()}> {t('chat.media.sendToChat')} @@ -1665,20 +1703,66 @@ export function ImageLightbox({ ) : null} - {/* 底部媒体操作栏(视觉复用 MermaidLightbox 的胶囊 toolbar)。点按不冒泡, - 否则会被 overlay 的 click-to-close 吞掉。标注模式下切换为画笔工具组。 */} - {isAnnotating ? ( - // 标注模式:只有三个选择——放弃(X)/ 撤销上一笔 / 发送到聊天。 - // 标注的唯一出口是发送,没有"完成后保留笔迹"的中间态。 -
e.stopPropagation()} - > + + zoomBy(1 / LIGHTBOX_ZOOM_STEP)} + > + + + { + if (!open) lastMenuCloseAt.current = performance.now(); + }} + > + + + + + + {t('attachmentPreview.fit')} + + { + if (fittedImageSize && currentNaturalSize) + applyViewport({ + scale: Math.max(1, currentNaturalSize.w / fittedImageSize.width), + tx: 0, + ty: 0, + }); + }} + > + {t('attachmentPreview.actualSize')} + + + + = LIGHTBOX_MAX_SCALE} + onClick={() => zoomBy(LIGHTBOX_ZOOM_STEP)} + > + + + + + +
+ {isAnnotating ? ( + <> void discardAnnotation()} label={t('chat.media.annotateDiscard')} @@ -1702,62 +1786,16 @@ export function ImageLightbox({ > -
- {annotationEdit ? ( - void (annotationEdit ? handleAnnotationSave() : handleSendToChat())} + label={t(annotationEdit ? 'chat.media.annotateSave' : 'chat.media.sendToChat')} keepPointerFocus > + ) : ( - - - - )} -
- ) : hasAnyAction || canAnnotate ? ( -
e.stopPropagation()} - > - {canCopy ? ( - - - - ) : null} - {canOpenWith ? ( - - - - ) : null} - {canOpenInBrowser ? ( - - - - ) : null} - {canSaveAs ? ( - - - - ) : null} - {/* 标注与发送是一组:圈点的产物就是发到对话,分隔线右侧成组摆放。 */} - {canAnnotate || canSendToChat ? ( <> -
{canAnnotate ? ( setIsAnnotating(true)} @@ -1768,100 +1806,64 @@ export function ImageLightbox({ ) : null} {canSendToChat ? ( void handleSendToChat()} label={t('chat.media.sendToChat')} > ) : null} - ) : null} -
- ) : null} - {/* 会话内翻图:左右箭头 + 计数。点按钮时 stopPropagation,否则会冒泡到 - overlay 的 onClick 把整个 lightbox 关掉。只有 >1 张才显示;标注中或 - 标注模式中隐藏(翻页会清空笔迹,防误触)。 */} + )} {hasMultiple && !isAnnotating ? ( <> - + + + { + if (!open) lastMenuCloseAt.current = performance.now(); + }} + > + - {/* biome-ignore lint/a11y/noStaticElementInteractions lint/a11y/useKeyWithClickEvents: 计数器不是交互控件;onClick 只阻止冒泡关闭 lightbox。 */} -
e.stopPropagation()}> + className="rounded-full px-3 py-2 text-xs tabular-nums hover:bg-[var(--surface-hover)]" + > {index + 1} / {galleryItems.length} -
+ +
+ + {galleryItems.map((item, itemIndex) => ( + setIndex(itemIndex)} + > + {itemIndex + 1}. {previewSourceName(item.src, t('attachmentPreview.image'))} + + ))} + +
+ + setIndex((value) => previewAdjacentIndex(value, 1, galleryItems.length)) + } + > + + ) : null} -
+ +
); return createPortal(overlay, document.body); } - -/** - * 底部操作栏的圆形图标按钮,样式与 MermaidLightbox 的 ToolbarButton 一致。 - * Tips 用应用统一的 Radix Tooltip(跨平台一致,原生 title 在 Electron 下延迟 - * 且样式不可控);lightbox overlay 是 z-9999,content 需抬到其上。 - */ -function LightboxToolbarButton({ - onClick, - label, - keepPointerFocus = false, - disabled = false, - children, -}: { - onClick: () => void; - label: string; - /** - * 鼠标点击不把焦点停在按钮上(焦点留在画布)。标注工具栏用:否则点过「撤销」 - * 后按住 Space 平移会变成再次激活该按钮。键盘 Tab 聚焦与 Space / Enter 激活不受影响。 - */ - keepPointerFocus?: boolean; - /** - * 当前不可用(如没有可撤销 / 可重做的笔迹):60% 不透明度、普通指针、无 hover 反馈, - * 点击无效。用 aria-disabled 而非原生 disabled,悬停仍能看到按钮说明。 - */ - disabled?: boolean; - children: React.ReactNode; -}) { - return ( - - - - ); -} diff --git a/apps/desktop/src/renderer/components/chat/MarkdownMermaidBlock.tsx b/apps/desktop/src/renderer/components/chat/MarkdownMermaidBlock.tsx index 5714c89d0df..ab60243ee5d 100644 --- a/apps/desktop/src/renderer/components/chat/MarkdownMermaidBlock.tsx +++ b/apps/desktop/src/renderer/components/chat/MarkdownMermaidBlock.tsx @@ -20,7 +20,7 @@ * Theme: re-renders when toggles, via MutationObserver. */ -import { memo, useEffect, useId, useRef, useState } from 'react'; +import { memo, useEffect, useId, useRef, useState, type ReactNode } from 'react'; import { Check, Code2, Copy, Expand, Eye, Pen } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { repairMermaidSource } from '@cindy/maker-shared/mermaid-autofix'; @@ -39,9 +39,12 @@ import { interface MarkdownMermaidBlockProps { raw: string; + fullscreen?: boolean; + fullscreenFallback?: ReactNode; + onClose?: () => void; } -type MermaidModule = typeof import('mermaid')['default']; +type MermaidModule = (typeof import('mermaid'))['default']; let mermaidPromise: Promise | null = null; @@ -57,6 +60,9 @@ function isDarkMode(): boolean { export const MarkdownMermaidBlock = memo(function MarkdownMermaidBlock({ raw, + fullscreen = false, + fullscreenFallback, + onClose, }: MarkdownMermaidBlockProps) { const { t } = useTranslation(); const reactId = useId(); @@ -210,6 +216,21 @@ export const MarkdownMermaidBlock = memo(function MarkdownMermaidBlock({ // a hard error (not just streaming-partial). const showSourceView = showSource || (svg == null && error != null); + if (fullscreen && !svg && fullscreenFallback) return fullscreenFallback; + + if (fullscreen && svg) + return ( + <> + {})} + onAnnotate={canAnnotate ? openAnnotate : undefined} + /> + {annotateNode} + + ); + return (
{showSourceView ? ( diff --git a/apps/desktop/src/renderer/components/chat/MermaidLightbox.tsx b/apps/desktop/src/renderer/components/chat/MermaidLightbox.tsx index af56f78347d..ed2cb83dd97 100644 --- a/apps/desktop/src/renderer/components/chat/MermaidLightbox.tsx +++ b/apps/desktop/src/renderer/components/chat/MermaidLightbox.tsx @@ -1,3 +1,5 @@ +import { usePreviewFocusReturn } from './PreviewChrome'; +import { PlaintextEditor } from '@/components/markdown/PlaintextEditor'; /** * MermaidLightbox * --------------------------------------------------------------------------- @@ -15,8 +17,12 @@ */ import { useCallback, useEffect, useRef, useState } from 'react'; +import { FocusScope } from '@radix-ui/react-focus-scope'; +import { PreviewHeader, PreviewFooter, PreviewButton as ToolbarButton } from './PreviewChrome'; +import { DropdownMenuItem } from '@/components/ui/dropdown-menu'; +import { toast } from '@/lib/toast'; import { createPortal } from 'react-dom'; -import { Check, Copy, Minus, Pen, Plus, X } from 'lucide-react'; +import { Check, Copy, Minus, Pen, Plus, Maximize } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { cn } from '@/lib/utils'; import { resolveExportBackground, svgToPngBlob } from '@/lib/rasterizeToImage'; @@ -47,7 +53,9 @@ interface MermaidLightboxProps { } export function MermaidLightbox({ svg, source, onAnnotate, onClose }: MermaidLightboxProps) { + const restorePreviewFocus = usePreviewFocusReturn(); const { t } = useTranslation(); + const [showSource, setShowSource] = useState(false); const [isVisible, setIsVisible] = useState(false); const [scale, setScale] = useState(1); const [translate, setTranslate] = useState({ x: 0, y: 0 }); @@ -57,6 +65,7 @@ export function MermaidLightbox({ svg, source, onAnnotate, onClose }: MermaidLig const isClosingRef = useRef(false); const isWheelingRef = useRef(false); const wheelIdleTimerRef = useRef | null>(null); + const overlayRef = useRef(null); const stageRef = useRef(null); const cardRef = useRef(null); const viewportRef = useRef({ scale: 1, tx: 0, ty: 0 }); @@ -116,7 +125,7 @@ export function MermaidLightbox({ svg, source, onAnnotate, onClose }: MermaidLig // Esc closes useEffect(() => { const handler = (e: KeyboardEvent) => { - if (e.key === 'Escape') handleClose(); + if (e.key === 'Escape' && !e.defaultPrevented) handleClose(); }; document.addEventListener('keydown', handler); return () => document.removeEventListener('keydown', handler); @@ -238,14 +247,18 @@ export function MermaidLightbox({ svg, source, onAnnotate, onClose }: MermaidLig } const overlay = ( -
{ event.preventDefault(); overlayRef.current?.focus(); }} + > +
-
+ +
+ ) : null} +
- {/* Toolbar */} -
e.stopPropagation()} - > + { + const reader = new FileReader(); + reader.onload = () => { + if (typeof reader.result === 'string') + void window.electronAPI + .saveMediaAs({ url: reader.result }) + .catch(() => toast.error(t('chat.media.saveFailed'))); + }; + reader.readAsDataURL(new Blob([svg], { type: 'image/svg+xml' })); + }} + menu={ + source ? ( + setShowSource((value) => !value)}> + {t(showSource ? 'attachmentPreview.rendered' : 'attachmentPreview.source')} + + ) : undefined + } + /> + + + + zoomBy(1 / LIGHTBOX_ZOOM_STEP)} label={t('chat.mermaidLightbox.zoomOut')} @@ -315,7 +349,7 @@ export function MermaidLightbox({ svg, source, onAnnotate, onClose }: MermaidLig {Math.round(scale * 100)}% - + zoomBy(LIGHTBOX_ZOOM_STEP)} label={t('chat.mermaidLightbox.zoomIn')} @@ -336,38 +370,13 @@ export function MermaidLightbox({ svg, source, onAnnotate, onClose }: MermaidLig > {copiedImage ? : } - - + + +
-
+ ); return createPortal(overlay, document.body); } - -function ToolbarButton({ - onClick, - label, - children, -}: { - onClick: () => void; - label: string; - children: React.ReactNode; -}) { - return ( - - ); -} diff --git a/apps/desktop/src/renderer/components/chat/MessageStream.tsx b/apps/desktop/src/renderer/components/chat/MessageStream.tsx index 0957d8d10b7..1d5d01bb151 100644 --- a/apps/desktop/src/renderer/components/chat/MessageStream.tsx +++ b/apps/desktop/src/renderer/components/chat/MessageStream.tsx @@ -1,3 +1,5 @@ +import { AttachmentCollectionContext, type AttachmentCollectionItem } from './AttachmentCollection'; +import { attachmentPreviewKind, previewSourceName } from '@cindy/maker-shared/attachment-preview'; import { TeammateProcessStatus } from '@/features/bots/TeammateProcessStatus'; import { scrollRangeIntoView } from '@/lib/scrollRangeIntoView'; import { historyAfter } from './historyAfter'; @@ -369,7 +371,7 @@ import { createGhostCardSpawnIndex, } from '@/cindy-brain/ghostCardGallery'; import { ChatImageView } from './ChatImageView'; -import { ImageGalleryContext, type GalleryImage } from './ImageGalleryContext'; +import { galleryImagePreviewItem, ImageGalleryContext, type GalleryImage } from './ImageGalleryContext'; import { GhostFulfillmentContext } from './GhostSummonCard'; import { ChatSessionFileProvider, useChatSessionFileValue } from './ChatSessionFileContext'; import { toRemoteMediaOrigin } from '@/lib/sessionFileOrigin'; @@ -1823,11 +1825,11 @@ export function buildRenderItems( // 本次 build 内已被认领的活卡(claude 精确锚 / codex 启发式各认领一次)。 const claimedLiveCallIds = new Set(); const sessionLiveCards = opts?.sessionId - ? ghostCards?.liveCards.filter(lc => lc.sessionId === opts.sessionId) ?? [] + ? (ghostCards?.liveCards.filter((lc) => lc.sessionId === opts.sessionId) ?? []) : []; // Mail is a finished document, not plugin progress. Without an exact tool ID, // wait for its result rather than attach it to a schema/read call by guesswork. - const guessableLiveCards = sessionLiveCards.filter(lc => { + const guessableLiveCards = sessionLiveCards.filter((lc) => { const entry = ghostCards?.byCallId.get(lc.callId); return !(entry?.status === 'ready' && readMailCard(entry.html)); }); @@ -1971,7 +1973,7 @@ export function buildRenderItems( const end = Date.parse(messages[hi]?.createdAt ?? ''); const artifacts = (opts?.historyArtifacts ?? []).filter((artifact) => { const time = Date.parse(artifact.createdAt); - return !(Number.isFinite(start) && time < start || Number.isFinite(end) && time >= end); + return !((Number.isFinite(start) && time < start) || (Number.isFinite(end) && time >= end)); }); const editedPaths = new Set(artifacts.filter((file) => file.exclude && file.ready) .map((file) => pathKey(resolveToolFilePath(file.path, workingDir)))); @@ -2988,7 +2990,10 @@ export const WorkGroupRenderItemView = memo(function WorkGroupRenderItemView({ deferred={item.deferred} fitHeader={Boolean(replyColumnSpacer)} journal={item.teammateJournal} - journalTitle={item.isStreaming ? elapsed => {elapsed} : undefined} + journalTitle={item.isStreaming ? (elapsed) => ( + {elapsed} + ) + : undefined} /> ); return ( @@ -3540,7 +3545,7 @@ export function MessageStream({ visibleRenderItemsRef.current = visibleRenderItems; // Keep complex/stateful cards alive. Plain historical message bodies can be // replaced by measured-height placeholders without browser display locking. - const viewportEntries = useMemo(() => visibleRenderItems.map(item => ({ + const viewportEntries = useMemo(() => visibleRenderItems.map((item) => ({ key: item.key, retain: item.type !== 'message' || item.message.isStreaming === true || Boolean(item.message.systemCardType) || (item.message.role !== 'user' && item.message.role !== 'assistant') || @@ -3615,7 +3620,7 @@ export function MessageStream({ : findRenderItemElement(itemsRef.current, previous.viewportTopKey); const rect = target?.getBoundingClientRect(); const viewport = container.getBoundingClientRect(); - const offset = previous.messageClientId ? previous.messageOffset ?? 0 : previous.offset; + const offset = previous.messageClientId ? (previous.messageOffset ?? 0) : previous.offset; // Native anchoring also emits scroll events during prepend/size settling. // An already aligned reading row must not be replaced by a new offscreen // row whose content-visibility estimate temporarily crosses the top edge. @@ -4086,17 +4091,56 @@ export function MessageStream({ () => toRemoteMediaOrigin(sessionFileValue.origin, sessionFileValue.workingDir), [sessionFileValue], ); - const sessionImageSrcs = useMemo( + const messageImageCollections = useMemo( () => - collectSessionImageSrcs( - renderMetadataItems, + new Map( + renderMetadataItems.map((item) => [ + item.key, + collectSessionImageSrcs( + [item], galleryMediaOrigin, ghostCardSnapshot, isSessionStreaming, + ), + ]), ), [renderMetadataItems, galleryMediaOrigin, ghostCardSnapshot, isSessionStreaming], ); + const messageAttachmentCollections = useMemo( + () => + new Map( + renderMetadataItems.map((item) => { + const entries: AttachmentCollectionItem[] = ( + messageImageCollections.get(item.key) ?? [] + ).map((image, index) => galleryImagePreviewItem(image, `${item.key}:image:${index}`)); + if (item.type === 'message' && item.message.role === 'user') { + for (const [index, file] of (item.message.files ?? []).entries()) + entries.push({ + id: `${item.key}:file:${index}`, + name: file.name, + source: file.path, + kind: attachmentPreviewKind(file.name), + }); + } + if (item.type === 'tool_media') { + for (const [index, media] of item.items.entries()) { + if ((media.kind === 'video' || media.kind === 'audio') && media.url) + entries.push({ + id: `${item.key}:media:${index}`, + name: previewSourceName(media.url, media.kind), + source: rewriteToRemoteMediaOrigin(media.url, galleryMediaOrigin), + kind: media.kind, + media: true, + }); + } + } + return [item.key, entries] as const; + }), + ), + [renderMetadataItems, messageImageCollections, galleryMediaOrigin], + ); + // 把可见窗口往前(更早)推 RENDER_WINDOW_GROWTH_ITEMS 个 item,用于滚到顶时的客户端扩窗。 // render-window-bidirectional 要点 4: expandWindow 必须同步把上界 +GROWTH, // 否则 start 前移而上界不动,会把用户视口下方的内容反向截掉。 @@ -6470,7 +6514,7 @@ export function MessageStream({ return ( - + <>
{/* chat-text-quote:选中消息文字 → 浮出"添加到对话"按钮(portal 到 body)。 绑定本流的滚动容器:协同模式多流并存时,选区归属按各自容器判定。 */} @@ -6527,7 +6571,8 @@ export function MessageStream({ disabled: shareSelectionActive, // Refresh ownership before virtualization mutates the DOM. onProgrammaticScroll: () => handleScroll(), - }}>{messageViewport =>
{(messageViewport) => ( +
- {visibleRenderItems.map((item, itemIndex) => { + {visibleRenderItems.map((item, itemIndex) => ( + + + {(() => { if (!messageViewport.shouldMount(viewportEntries[itemIndex])) { - return ); - })} -
} - {footer ?
{footer}
: null} + })()} + + + ))} +
+ )} + {footer ? ( +
{footer}
+ ) : null}
@@ -6907,7 +6968,7 @@ export function MessageStream({ chipSlot, )}
- + ); diff --git a/apps/desktop/src/renderer/components/chat/ModelLightbox.tsx b/apps/desktop/src/renderer/components/chat/ModelLightbox.tsx index 46ac9a241ec..b95b146d2d8 100644 --- a/apps/desktop/src/renderer/components/chat/ModelLightbox.tsx +++ b/apps/desktop/src/renderer/components/chat/ModelLightbox.tsx @@ -1,3 +1,6 @@ +import { usePreviewFocusReturn } from './PreviewChrome'; +import { PreviewHeader, PreviewFooter, PreviewButton } from './PreviewChrome'; +import { previewSourceName } from '@cindy/maker-shared/attachment-preview'; import { shouldShowOpenPathError } from '../../../shared/openPathResult'; /** * ModelLightbox @@ -28,7 +31,7 @@ import { shouldShowOpenPathError } from '../../../shared/openPathResult'; import { useCallback, useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { FocusScope } from '@radix-ui/react-focus-scope'; -import { Box, FolderOpen } from 'lucide-react'; +import { Box, FolderOpen, RotateCcw, Minus, Plus } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { toast } from '@/lib/toast'; import { extractIpcError } from '@/utils/ipcError'; @@ -65,11 +68,15 @@ export type ModelLightboxSource = interface ModelLightboxProps { source: ModelLightboxSource; onClose: () => void; + filename?: string; } -export function ModelLightbox({ source, onClose }: ModelLightboxProps) { +export function ModelLightbox({ source, onClose, filename }: ModelLightboxProps) { + const restorePreviewFocus = usePreviewFocusReturn(); const { t } = useTranslation(); const [isVisible, setIsVisible] = useState(false); + const [failed, setFailed] = useState(false); + const [retry, setRetry] = useState(0); const isClosingRef = useRef(false); const onCloseRef = useRef(onClose); const [menuPos, setMenuPos] = useState<{ x: number; y: number } | null>(null); @@ -85,6 +92,17 @@ export function ModelLightbox({ source, onClose }: ModelLightboxProps) { // "open" / "reveal" 正在执行时禁用菜单, 避免重复点击。Mirror ChatImageView。 const [modelPending, setModelPending] = useState<'open' | 'reveal' | null>(null); + const zoomModel = (factor: number) => { + const viewer = modelViewerRef.current as + (HTMLElement & { getCameraOrbit?(): { theta: number; phi: number; radius: number } }) | null; + const orbit = viewer?.getCameraOrbit?.(); + if (orbit) + viewer!.setAttribute( + 'camera-orbit', + `${orbit.theta}rad ${orbit.phi}rad ${orbit.radius * factor}m`, + ); + }; + const poster = source.kind === 'blob' ? source.poster : undefined; useEffect(() => { @@ -113,7 +131,7 @@ export function ModelLightbox({ source, onClose }: ModelLightboxProps) { // recently-closed context menu doesn't also dismiss the lightbox. useEffect(() => { const handler = (e: KeyboardEvent) => { - if (e.key !== 'Escape') return; + if (e.defaultPrevented || e.key !== 'Escape' || document.fullscreenElement) return; if (performance.now() - lastMenuCloseAt.current < 150) return; handleClose(); }; @@ -140,11 +158,11 @@ export function ModelLightbox({ source, onClose }: ModelLightboxProps) { const el = modelViewerRef.current; if (!el) return; const onError = () => { - toast.error(t('chat.media.modelLoadFailed', 'Failed to load 3D model')); + setFailed(true); }; el.addEventListener('error', onError); return () => el.removeEventListener('error', onError); - }, [t]); + }, [retry]); async function handleModelAction(action: 'open' | 'reveal'): Promise { if (modelPending) return; @@ -199,6 +217,7 @@ export function ModelLightbox({ source, onClose }: ModelLightboxProps) { // 里既有的 blur 仍保留——点进模型后 model-viewer shadow DOM 的焦点框 // 要在淡出前先灭掉。 +
+ ) : null} + + zoomModel(1.25)}> + + + { + const viewer = modelViewerRef.current; + if (!viewer) return; + viewer.setAttribute('camera-orbit', '0deg 75deg auto'); + viewer.setAttribute('camera-target', 'auto auto auto'); + viewer.setAttribute('field-of-view', 'auto'); + }} + > + + + zoomModel(0.8)}> + + + + { diff --git a/apps/desktop/src/renderer/components/chat/PreviewChrome.tsx b/apps/desktop/src/renderer/components/chat/PreviewChrome.tsx new file mode 100644 index 00000000000..e01e4a62df5 --- /dev/null +++ b/apps/desktop/src/renderer/components/chat/PreviewChrome.tsx @@ -0,0 +1,189 @@ +import { useContext, type ReactNode } from 'react'; +import { ChevronLeft, ChevronRight, Download, MoreHorizontal, X } from 'lucide-react'; +import { useTranslation } from 'react-i18next'; +import { Tip } from '@/components/ui/tooltip'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { cn } from '@/lib/utils'; +import { ActivePreviewCollection } from './AttachmentCollectionContext'; + +/** Scope teardown runs after closing/switching; don't restore into a removed viewer. */ +export function usePreviewFocusReturn() { + return useContext(ActivePreviewCollection)?.restoreFocus; +} + +/** Shared preview chrome. Content renderers retain their own lifecycle and permissions. */ +export function PreviewHeader({ + name, + detail, + onClose, + onSave, + actions, + menu, + onMenuClose, +}: { + name: string; + detail?: ReactNode; + onClose: () => void; + onSave?: () => void; + actions?: ReactNode; + menu?: ReactNode; + onMenuClose?: () => void; +}) { + const { t } = useTranslation(); + return ( +
event.stopPropagation()} + > +
+
+ {name} +
+ {detail ? ( +
{detail}
+ ) : null} +
+
+ {actions} + {onSave ? ( + + + + ) : null} + {menu ? ( + { + if (!open) onMenuClose?.(); + }} + > + + + + event.stopPropagation()} + className="z-[10002]" + align="end" + > + {menu} + + + ) : null} + + + +
+
+ ); +} + +const PREVIEW_BUTTON_CLASS = + 'inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-[var(--text-primary)] hover:bg-[var(--surface-hover)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--text-primary)] disabled:opacity-40 disabled:pointer-events-none'; + +export function PreviewButton({ + label, + onClick, + disabled, + children, + keepPointerFocus, +}: { + label: string; + onClick: () => void; + disabled?: boolean; + children: ReactNode; + keepPointerFocus?: boolean; +}) { + return ( + + + + ); +} + +export function PreviewFooter({ + children, + className, + collectionOnly = false, +}: { + children?: ReactNode; + className?: string; + collectionOnly?: boolean; +}) { + const collection = useContext(ActivePreviewCollection); + const { t } = useTranslation(); + if (collectionOnly && !collection) return null; + return ( +
event.stopPropagation()} + > + {children} + {collection ? ( +
+ collection.select(collection.index - 1)} + > + + + + + + + event.stopPropagation()} + > + {collection.items.map((item, index) => ( + collection.select(index)}> + {index + 1}. {item.name} + + ))} + + + collection.select(collection.index + 1)} + > + + +
+ ) : null} +
+ ); +} diff --git a/apps/desktop/src/renderer/components/chat/TextLightbox.tsx b/apps/desktop/src/renderer/components/chat/TextLightbox.tsx index 6d99f43a09f..411b6a93544 100644 --- a/apps/desktop/src/renderer/components/chat/TextLightbox.tsx +++ b/apps/desktop/src/renderer/components/chat/TextLightbox.tsx @@ -1,36 +1,32 @@ -import { FileTypeIcon } from '@/components/ui/file-type-icon'; +import { usePreviewFocusReturn } from './PreviewChrome'; +import { MarkdownMermaidBlock } from './MarkdownMermaidBlock'; +import { openHtmlFileByPreference } from './useOpenWithMenu'; +import { DocSearchBar } from '@/features/cc-agent/workdir-browse/DocSearchBar'; +import { + AttachmentCollectionPreview, + useOpeningCollection, + ActivePreviewCollection, +} from './AttachmentCollection'; +import { attachmentPreviewKind } from '@cindy/maker-shared/attachment-preview'; +import { FocusScope } from '@radix-ui/react-focus-scope'; +import { toLocalFileUrl } from '@/lib/localPathResolver'; +import { PreviewHeader, PreviewFooter, PreviewButton } from './PreviewChrome'; +import { DropdownMenuItem } from '@/components/ui/dropdown-menu'; import { Button } from '@/components/ui/button'; import { shouldShowOpenPathError } from '../../../shared/openPathResult'; -/** - * TextLightbox - * --------------------------------------------------------------------------- - * Full-screen text-file preview overlay rendered via React Portal. - * - * text-lightbox spec (2026-04-19): - * F3 Overlay via --overlay-lightbox + centered Doc Card (80vw/80vh, 12px radius) - * F4 Toolbar (file-text · name · · · size · external-link · copy) + - * scrollable body containing the plain-text content. - * F5 超限状态(> OVERSIZE_LIMIT_MB):triangle-alert + title + dynamic copy + - * 单个 Black-Pill 系统打开 CTA。 - * F6 Esc OR click on Overlay backdrop closes; clicks inside Doc Card stay. - * - * Sibling: ImageLightbox.tsx — same Portal/scroll-lock/Esc/fade pattern. - * Difference: this lightbox has interactive widgets (Toolbar, body), so the - * backdrop is a separate background button behind the Doc Card. Per F6, only - * the backdrop and Esc close. - */ - -import { CHAT_LIGHTBOX_ICON_BUTTON_CLASS, CHAT_FOCUS_CLASS } from './chatChrome'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { + lazy, + Suspense, + useCallback, + useContext, useEffect, useMemo, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { isLightboxCovered } from '@/lib/lightboxLayer'; -import { Copy, Download, ExternalLink, Folder, TriangleAlert, X } from 'lucide-react'; +import { Copy, Search, WrapText, ListOrdered, ExternalLink, TriangleAlert } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { cn, basename } from '@/lib/utils'; import { Spinner } from '@/components/ui/spinner'; import { toast } from '@/lib/toast'; -import { Tooltip } from '@/components/ui/tooltip'; import { detectRenderable } from '@/lib/textPreview'; import { isRemoteFileOrigin } from '@/lib/sessionFileOrigin'; import { observeFileTransferProgressText } from '@/lib/fileTransferProgress'; @@ -43,6 +39,10 @@ import { useChatSessionFile } from './ChatSessionFileContext'; import { PlaintextEditor, type PlaintextEditorHandle } from '@/components/markdown/PlaintextEditor'; import { MarkdownRenderer } from './MarkdownRenderer'; +const FileLightbox = lazy(() => + import('./FileLightbox').then((module) => ({ default: module.FileLightbox })), +); + interface TextLightboxProps { /** Absolute path of the file being previewed. */ filePath: string; @@ -65,9 +65,6 @@ interface TextLightboxProps { const OVERSIZE_LIMIT_MB = 10; const OVERSIZE_LIMIT = OVERSIZE_LIMIT_MB * 1024 * 1024; const TEXT_LIGHTBOX_OVERLAY_Z_INDEX = 9999; -const TEXT_LIGHTBOX_TOOLTIP_STYLE = { - zIndex: TEXT_LIGHTBOX_OVERLAY_Z_INDEX + 1, -} as const; /** * Format a byte count for display in the toolbar (`3.4 MB`, `812 KB`, `73 B`). @@ -114,8 +111,46 @@ type LoadState = | { phase: 'oversize'; size: number; limitMb: number } | { phase: 'error'; message: string }; -export function TextLightbox({ filePath, fileName, initialLine, triggerRef, onClose }: TextLightboxProps) { +export function TextLightbox(props: TextLightboxProps) { + const items = useOpeningCollection(props.filePath); + if (items) + return ( + + ); + const kind = attachmentPreviewKind(props.filePath); + return kind === 'text' || + kind === 'markdown' || + kind === 'html' || + /\.(mmd|mermaid)$/i.test(props.filePath) ? ( + + ) : ( + + + +
, + document.body, + )} + > + + + ); +} + +function TextDocumentLightbox({ filePath, fileName, initialLine, triggerRef, onClose }: TextLightboxProps) { const { t } = useTranslation(); + const restorePreviewFocus = usePreviewFocusReturn(); + const collection = useContext(ActivePreviewCollection); const [isVisible, setIsVisible] = useState(false); const [loadState, setLoadState] = useState({ phase: 'loading' }); // 远程会话(device-link / SSH):filePath 是远端绝对路径,内容经 chat-file:fetch @@ -147,6 +182,37 @@ export function TextLightbox({ filePath, fileName, initialLine, triggerRef, onCl const [showSpinner, setShowSpinner] = useState(false); + const [showSource, setShowSource] = useState(false); + const [wordWrap, setWordWrap] = useState(true); + const [lineNumbers, setLineNumbers] = useState(true); + const [search, setSearch] = useState(false); + const [query, setQuery] = useState(''); + const [matches, setMatches] = useState(0); + const [match, setMatch] = useState(0); + const searchInput = useRef(null); + const [retry, setRetry] = useState(0); + const openSearch = () => { + setShowSource(true); + setSearch(true); + }; + useEffect(() => { + if (!search) { + editorRef.current?.search.clear(); + return; + } + searchInput.current?.focus(); + const count = editorRef.current?.search.findAll(query) ?? 0; + setMatches(count); + setMatch(0); + if (count) editorRef.current?.search.setActive(0); + }, [search, query, showSource, loadState.phase]); + const selectMatch = (delta: number) => { + if (!matches) return; + const next = (match + delta + matches) % matches; + setMatch(next); + editorRef.current?.search.setActive(next); + }; + const contentReady = loadState.phase === 'oversize' || loadState.phase === 'error' || @@ -192,13 +258,25 @@ export function TextLightbox({ filePath, fileName, initialLine, triggerRef, onCl useEffect(() => { const handler = (e: KeyboardEvent) => { if (e.repeat || e.defaultPrevented || e.isComposing || e.keyCode === 229) return; + if ((e.metaKey || e.ctrlKey) && e.key === 'f') { + e.preventDefault(); + e.stopImmediatePropagation(); + setShowSource(true); + setSearch(true); + return; + } + if (e.key === 'Escape' && search) { + e.preventDefault(); + setSearch(false); + return; + } if (e.key === 'Escape' && overlayRef.current && !isLightboxCovered(overlayRef.current)) { handleClose(); } }; - document.addEventListener('keydown', handler); - return () => document.removeEventListener('keydown', handler); - }, [handleClose]); + document.addEventListener('keydown', handler, true); + return () => document.removeEventListener('keydown', handler, true); + }, [handleClose, search]); // F6: Scroll lock on the message stream's scroll container. // Same selector ImageLightbox uses; keeping it in sync prevents the page @@ -224,6 +302,7 @@ export function TextLightbox({ filePath, fileName, initialLine, triggerRef, onCl // response so we don't have to re-statically encode the MB number in two places. useEffect(() => { let cancelled = false; + setLoadState({ phase: 'loading' }); setFetchProgressText(null); const progress = remoteOrigin ? observeFileTransferProgressText(setFetchProgressText) : null; (async () => { @@ -290,7 +369,7 @@ export function TextLightbox({ filePath, fileName, initialLine, triggerRef, onCl cancelled = true; progress?.dispose(); }; - }, [filePath, remoteOrigin, sessionFileCtx.workingDir, t]); + }, [filePath, remoteOrigin, sessionFileCtx.workingDir, t, retry]); useEffect(() => { @@ -406,7 +485,8 @@ export function TextLightbox({ filePath, fileName, initialLine, triggerRef, onCl > - - + {t(showSource ? 'attachmentPreview.rendered' : 'attachmentPreview.source')} + + ) : null} + setWordWrap((value) => !value)}> + + {t('attachmentPreview.wrap')} + {wordWrap ? ' ✓' : ''} + + setLineNumbers((value) => !value)}> + + {t('attachmentPreview.lineNumbers')} + {lineNumbers ? ' ✓' : ''} + + {t('chat.lightbox.clickToCopyPath')} - - - - {/* Right — Show in folder + Copy content + Close (Open in System moved to Oversize CTA only). */} - {/* v5: 原生 title 替换为 Radix Tooltip(默认 sans) — 短文案提示。 */} -
- - - - - + + {remoteOrigin ? t('chat.remoteFile.downloadToLocal') : t('chat.lightbox.openInExplorer')} - - - - - - - - {t('chat.textLightbox.copyAll')} - - - - - - - - {t('chat.lightbox.close')} - - + + + } + /> + + {search ? ( +
+ selectMatch(1)} + onPrev={() => selectMatch(-1)} + onClose={() => setSearch(false)} + />
-
- + ) : null} {/* Body */} {!contentReady && (showSpinner ? ( @@ -564,7 +618,7 @@ export function TextLightbox({ filePath, fileName, initialLine, triggerRef, onCl
{remoteOrigin - ? fetchProgressText ?? t('chat.remoteFile.fetching') + ? (fetchProgressText ?? t('chat.remoteFile.fetching')) : t('chat.textLightbox.loading')}
@@ -588,6 +642,9 @@ export function TextLightbox({ filePath, fileName, initialLine, triggerRef, onCl
{loadState.message}
+ )} @@ -627,11 +684,13 @@ export function TextLightbox({ filePath, fileName, initialLine, triggerRef, onCl {contentReady && loadState.phase === 'ready' && - (renderable.kind === 'text' || renderable.kind === 'code') && ( + (renderable.kind === 'text' || renderable.kind === 'code' || showSource) && ( )} - - - {/* F6 提示 footer:位于 backdrop 上,不压到卡片内部。 */} -
- - Esc - - {t('chat.textLightbox.closeHint')} + {collection ? : null}
); - return createPortal(overlay, document.body); + const documentView = createPortal( + + {overlay} + , document.body); + if (/\.(mmd|mermaid)$/i.test(filePath) && loadState.phase === 'ready' && !showSource) + return ; + return documentView; } export const __test_internals = { diff --git a/apps/desktop/src/renderer/components/chat/ToolPayloadLightbox.tsx b/apps/desktop/src/renderer/components/chat/ToolPayloadLightbox.tsx index 2ca8588364d..20953032237 100644 --- a/apps/desktop/src/renderer/components/chat/ToolPayloadLightbox.tsx +++ b/apps/desktop/src/renderer/components/chat/ToolPayloadLightbox.tsx @@ -1,3 +1,5 @@ +import { PreviewHeader, PreviewButton } from './PreviewChrome'; +import { DropdownMenuItem } from '@/components/ui/dropdown-menu'; import { Button } from '@/components/ui/button'; /** * ToolPayloadLightbox @@ -297,7 +299,8 @@ export function ToolPayloadLightbox({ > - - + + + + } + menu={ + <> + {payload.kind === 'diff' ? t('chat.lightbox.clickToCopyPath') : t('chat.lightbox.clickToCopy')} - - - -
- {payload.kind === 'diff' && singleDiffFile && ( - - - - - {revealLabel} - - )} - - - - - {t('chat.lightbox.copyContent')} - - - - - - {t('chat.lightbox.close')} - -
- + + {payload.kind === 'diff' && singleDiffFile ? ( + {revealLabel} + ) : null} + + } + /> {/* Body */}
void; + filename?: string; + kind?: 'video' | 'audio'; + initialTime?: number; + onClose: (time?: number) => void; } -export function VideoLightbox({ src, onClose }: VideoLightboxProps) { +export function VideoLightbox(props: VideoLightboxProps) { + const items = useOpeningCollection(props.src); + return items ? ( + } + /> + ) : ( + + ); +} + +function VideoLightboxContent({ + src, + filename, + kind = 'video', + initialTime = 0, onClose }: VideoLightboxProps) { + const restorePreviewFocus = usePreviewFocusReturn(); const { t } = useTranslation(); + const Media = kind === 'audio' ? 'audio' : 'video'; const [isVisible, setIsVisible] = useState(false); + const [failed, setFailed] = useState(false); const isClosingRef = useRef(false); const videoRef = useRef(null); const { menuPos, setMenuPos, portalContainer, restoreVideoFocus } = useVideoContextMenu(videoRef, src); @@ -47,8 +75,10 @@ export function VideoLightbox({ src, onClose }: VideoLightboxProps) { const handleClose = useCallback(() => { if (isClosingRef.current) return; isClosingRef.current = true; + videoRef.current?.pause(); + const time = videoRef.current?.currentTime; setIsVisible(false); - setTimeout(() => onClose(), 200); + setTimeout(() => onClose(time), 200); }, [onClose]); // 全局媒体互斥:lightbox autoPlay 起播时 bus 会自动 pause 其它正在 @@ -66,7 +96,7 @@ export function VideoLightbox({ src, onClose }: VideoLightboxProps) { useEffect(() => { const handler = (e: KeyboardEvent) => { - if (e.key !== 'Escape') return; + if (e.defaultPrevented || e.key !== 'Escape' || document.fullscreenElement) return; if (performance.now() - lastMenuCloseAt.current < 150) return; handleClose(); }; @@ -112,6 +142,7 @@ export function VideoLightbox({ src, onClose }: VideoLightboxProps) { // 前的元素(ChatVideoView 键盘路径 = 预览触发器,与 onClose 里的显式 // 恢复指向同一元素)。 + + ) : ( + + )} + {canRevealInFolder ? ( { expect(onClose).not.toHaveBeenCalled(); }); - it('still reports a missing image and closes when there is no burned fallback', () => { + it('keeps the close and retry controls available when the original image is missing', () => { vi.useFakeTimers(); const onClose = vi.fn(); render( @@ -406,6 +409,33 @@ describe('ImageLightbox annotate mode', () => { act(() => { vi.advanceTimersByTime(250); }); + expect(onClose).not.toHaveBeenCalled(); + expect(screen.getByRole('button', { name: label('attachmentPreview.retry') })).toBeTruthy(); + fireEvent.click(screen.getByRole('button', { name: label('chat.lightbox.close') })); + act(() => { + vi.advanceTimersByTime(250); + }); expect(onClose).toHaveBeenCalledTimes(1); }); }); + +// #8356 r4436061975: selecting a persisted annotation from a mixed collection must +// load the unburned source and allow removing old strokes. Previous tests opened it directly. +it('keeps persisted strokes editable after switching collection items', () => { + const burned = 'cindy-media://blobs/burned.png'; + render(); + fireEvent.click(screen.getByRole('button', { name: label('attachmentPreview.previous') })); + expect(lightboxImage().getAttribute('src')).toBe(SRC); + loadImage(lightboxImage()); + openAnnotate(); + expect(committedPaths('stroke')).toHaveLength(1); + pressKey({ key: 'z', ctrlKey: true }); + expect(committedPaths('stroke')).toHaveLength(0); + expect(lightboxImage().getAttribute('src')).toBe(SRC); +}); diff --git a/apps/desktop/src/renderer/components/chat/__tests__/TextLightboxInteraction.test.tsx b/apps/desktop/src/renderer/components/chat/__tests__/TextLightboxInteraction.test.tsx new file mode 100644 index 00000000000..9acf69e3b4d --- /dev/null +++ b/apps/desktop/src/renderer/components/chat/__tests__/TextLightboxInteraction.test.tsx @@ -0,0 +1,112 @@ +// @vitest-environment jsdom +// #8356 r4436061992/r4436061986: production collection + text/diagram viewer. +// The existing text tests inspect source/line helpers; they cannot observe lost +// opening props or a missing close surface. Only editor/diagram engines are stubbed. +import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'; +import { useRef, useState } from 'react'; +import { afterEach, expect, it, vi } from 'vitest'; +import { i18n } from '@/i18n'; +import { TextLightbox } from '../TextLightbox'; +import { AttachmentCollectionContext } from '../AttachmentCollection'; + +const state = vi.hoisted(() => ({ line: vi.fn(), parse: vi.fn() })); +vi.mock('mermaid', () => ({ default: { initialize: vi.fn(), parse: state.parse } })); +vi.mock('@/components/markdown/PlaintextEditor', async () => { + const { forwardRef, useImperativeHandle } = await import('react'); + return { + PlaintextEditor: forwardRef(({ initialValue }: { initialValue: string }, ref) => { + useImperativeHandle(ref, () => ({ scrollToLine: state.line, search: { clear: vi.fn() } })); + return
{initialValue}
; + }), + }; +}); +vi.mock('../MarkdownRenderer', () => ({ MarkdownRenderer: () => null })); +vi.mock('../useCopyAsImage', () => ({ useCopyAsImage: () => ({ canAnnotate: false }) })); + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); + vi.unstubAllGlobals(); + vi.useRealTimers(); +}); + +function mockFile(content: string) { + Object.defineProperty(window, 'electronAPI', { + configurable: true, + value: { + readTextFilePreview: vi + .fn() + .mockResolvedValue({ success: true, data: content, size: content.length }), + }, + }); +} + +it('opens the referenced line in a collection and returns focus even after switching files', async () => { + vi.useFakeTimers(); + mockFile('one\ntwo\nthree'); + function Host() { + const [open, setOpen] = useState(true); + const triggerRef = useRef(null); + return ( + <> + + {open && ( + + setOpen(false)} + /> + + )} + + ); + } + render(); + await act(async () => { + await vi.runOnlyPendingTimersAsync(); + }); + await act(async () => { + await vi.advanceTimersByTimeAsync(20); + }); + expect(state.line).toHaveBeenCalledWith(3); + fireEvent.click(screen.getByRole('button', { name: i18n.t('attachmentPreview.next') })); + await act(async () => { + await vi.runOnlyPendingTimersAsync(); + }); + fireEvent.click(screen.getByRole('button', { name: i18n.t('chat.lightbox.close') })); + await act(async () => { + await vi.advanceTimersByTimeAsync(250); + }); + await act(async () => { + await vi.runOnlyPendingTimersAsync(); + }); + expect(document.activeElement).toBe(screen.getByText('entry')); + expect(document.querySelector('[data-text-lightbox-overlay]')).toBeNull(); +}); + +it.each(['pending', 'invalid'])('keeps the diagram %s state closable', async (phase) => { + vi.useFakeTimers(); + state.parse.mockImplementation(() => + phase === 'pending' ? new Promise(() => {}) : Promise.reject(new Error('invalid syntax')), + ); + mockFile('invalid diagram'); + const close = vi.fn(); + render(); + await act(async () => { + await vi.runOnlyPendingTimersAsync(); + }); + const button = screen.getByRole('button', { name: i18n.t('chat.lightbox.close') }); + if (phase === 'pending') fireEvent.keyDown(document.body, { key: 'Escape' }); + else fireEvent.click(button); + await act(async () => { + await vi.advanceTimersByTimeAsync(250); + }); + expect(close).toHaveBeenCalledOnce(); +}); diff --git a/apps/desktop/src/renderer/components/chat/__tests__/imageClipboardInteractions.test.tsx b/apps/desktop/src/renderer/components/chat/__tests__/imageClipboardInteractions.test.tsx index 5199d2eaffb..4449d76d16a 100644 --- a/apps/desktop/src/renderer/components/chat/__tests__/imageClipboardInteractions.test.tsx +++ b/apps/desktop/src/renderer/components/chat/__tests__/imageClipboardInteractions.test.tsx @@ -89,6 +89,11 @@ async function copyFromMenu(image: HTMLElement) { fireEvent.click(await screen.findByRole('menuitem', { name: label('chat.media.copyImage') })); } +async function previewAction(key: string) { + fireEvent.pointerDown(screen.getByRole('button', { name: label('attachmentPreview.more') }), { button: 0, ctrlKey: false }); + fireEvent.click(await screen.findByRole('menuitem', { name: label(key) })); +} + describe('shared production image interactions', () => { it.each([false, true])( 'assistant local Markdown image menu, simplified=%s', @@ -177,13 +182,11 @@ describe('shared production image interactions', () => { await waitFor(() => expect(nativeCopy).toHaveBeenCalledTimes(1)); expect(readBytes).toHaveBeenCalledWith({ url: remoteSrc }); fireEvent.click(img); - await act(async () => - fireEvent.click(screen.getByRole('button', { name: label('chat.media.copyImage') })), - ); - expect(nativeCopy).toHaveBeenCalledTimes(2); + await previewAction('chat.media.copyImage'); + await waitFor(() => expect(nativeCopy).toHaveBeenCalledTimes(2)); fireEvent.click(screen.getByRole('button', { name: label('chat.media.saveAs') })); await waitFor(() => expect(save).toHaveBeenCalledWith({ url: remoteSrc })); - fireEvent.click(screen.getByRole('button', { name: label('chat.media.openWithApp') })); + await previewAction('chat.media.openWithApp'); await waitFor(() => expect(open).toHaveBeenCalledWith({ url: remoteSrc })); expect(fileCopy).not.toHaveBeenCalled(); expect(reveal).not.toHaveBeenCalled(); @@ -196,7 +199,7 @@ describe('shared production image interactions', () => { await i18n.changeLanguage(locale); nativeCopy.mockRejectedValue(new Error('[INTERNAL] secret/path must not appear')); render(); - fireEvent.click(screen.getByRole('button', { name: label('chat.media.copyImage') })); + await previewAction('chat.media.copyImage'); await waitFor(() => expect(toast.error).toHaveBeenCalledWith(label('ipcError.INTERNAL'))); expect(toast.success).not.toHaveBeenCalled(); }, @@ -222,7 +225,7 @@ describe('shared production image interactions', () => { onClose={vi.fn()} />, ); - fireEvent.click(screen.getByRole('button', { name: label('chat.media.copyImage') })); + await previewAction('chat.media.copyImage'); await waitFor(() => expect(nativeCopy).toHaveBeenCalledTimes(1)); expect(readBytes).toHaveBeenCalledWith({ url: managed }); expect(ctx.drawImage).toHaveBeenCalled(); @@ -316,7 +319,7 @@ describe('shared production image interactions', () => { ); webCopy.mockResolvedValue(undefined); render(); - fireEvent.click(screen.getByRole('button', { name: label('chat.media.copyImage') })); + await previewAction('chat.media.copyImage'); await waitFor(() => expect(webCopy).toHaveBeenCalledTimes(1)); expect(nativeCopy).not.toHaveBeenCalled(); expect(toast.success).toHaveBeenCalledWith(label('chat.media.imageCopied')); diff --git a/apps/desktop/src/renderer/components/markdown/PlaintextEditor.tsx b/apps/desktop/src/renderer/components/markdown/PlaintextEditor.tsx index 58e43278c7f..eb87112b852 100644 --- a/apps/desktop/src/renderer/components/markdown/PlaintextEditor.tsx +++ b/apps/desktop/src/renderer/components/markdown/PlaintextEditor.tsx @@ -167,6 +167,9 @@ export interface PlaintextEditorProps { /** Read-only mode. Toggled at runtime via Compartment.reconfigure, * 不重建 EditorView,scrollTop 自然保留。 */ readOnly?: boolean; + /** Optional reading controls; undefined preserves the editor defaults. */ + wordWrap?: boolean; + showLineNumbers?: boolean; /** Forwarded to the outer wrapper. */ className?: string; /** highlight.js-style language alias ('python', 'json', 'typescript', ...). @@ -1070,6 +1073,8 @@ export const PlaintextEditor = forwardRef { + cmViewRef.current?.dispatch({ + effects: [ + wrapCompartment.reconfigure( + wordWrap === false || initialConfigRef.current?.skipWrap ? [] : EditorView.lineWrapping, + ), + lineNumbersCompartment.reconfigure( + (showLineNumbers ?? initialConfigRef.current?.useCodeChrome) ? lineNumbers() : [], + ), + ], + }); + }, [wordWrap, showLineNumbers, wrapCompartment, lineNumbersCompartment]); + return ( // select-text:globals.css 全局禁了文本选中,CodeMirror 内部的 .cm-scroller / // 选区高亮层等非 contenteditable 元素不加这个会出现选中延迟、双击选词偶尔不 diff --git a/apps/desktop/src/renderer/components/new-chat/ComposerAttachments.tsx b/apps/desktop/src/renderer/components/new-chat/ComposerAttachments.tsx index d6df7e10f26..daddd3ef466 100644 --- a/apps/desktop/src/renderer/components/new-chat/ComposerAttachments.tsx +++ b/apps/desktop/src/renderer/components/new-chat/ComposerAttachments.tsx @@ -1,3 +1,8 @@ +import { + AttachmentCollectionContext, + type AttachmentCollectionItem, +} from '@/components/chat/AttachmentCollection'; +import { attachmentPreviewKind } from '@cindy/maker-shared/attachment-preview'; /** * ComposerAttachments — the attachment tray shared by the chat composers. * --------------------------------------------------------------------------- @@ -99,12 +104,38 @@ export function ThumbnailStrip({ /** 标注编辑保存后就地替换附件(useAttachments.updateFile)。 */ onUpdate: (id: string, patch: Partial) => void; }) { + const collection: AttachmentCollectionItem[] = attachments.flatMap((file) => { + const media = file.category === 'image' && Boolean(file.url || file.base64); + const source = media ? (file.url ?? `data:${file.mimeType};base64,${file.base64}`) : file.path; + return source + ? [ + { + id: file.id, + name: file.name, + source, + kind: attachmentPreviewKind(file.name, file.mimeType), + media, + image: media + ? { + annotationEdit: { + initialStrokes: file.annotationStrokes, + onSave: (result: { strokes: ImageAnnotationStroke[] }) => + applyAnnotationEdit(file, result, onUpdate), + }, + } + : undefined, + }, + ] + : []; + }); return ( -
+ +
{attachments.map((file) => ( ))}
+
); } diff --git a/apps/desktop/src/renderer/features/bots/BotGroupMessageAttachments.tsx b/apps/desktop/src/renderer/features/bots/BotGroupMessageAttachments.tsx index 96a56de5e79..fe9445197eb 100644 --- a/apps/desktop/src/renderer/features/bots/BotGroupMessageAttachments.tsx +++ b/apps/desktop/src/renderer/features/bots/BotGroupMessageAttachments.tsx @@ -1,3 +1,9 @@ +import { + AttachmentCollectionContext, + type AttachmentCollectionItem, +} from '@/components/chat/AttachmentCollection'; +import { attachmentPreviewKind } from '@cindy/maker-shared/attachment-preview'; +import { ImageGalleryContext } from '@/components/chat/ImageGalleryContext'; /** * 群聊消息的附件(docs/product-rules/bot-group-chat.md §3.1),主群时间线与 Thread 共用。 * @@ -16,7 +22,11 @@ import { FileTypeIcon } from '@/components/ui/file-type-icon'; import { Spinner } from '@/components/ui/spinner'; import { cn } from '@/lib/utils'; import type { BotGroupAttachment } from '../../../shared/botGroupChat'; -import { botGroupAttachmentRows, type BotGroupAttachmentSlot, type BotGroupPendingSlot } from './botGroupAttachments'; +import { + botGroupAttachmentRows, + type BotGroupAttachmentSlot, + type BotGroupPendingSlot, +} from './botGroupAttachments'; export function BotGroupMessageAttachments({ attachments, @@ -36,11 +46,23 @@ export function BotGroupMessageAttachments({ const renderSlot = (slot: BotGroupAttachmentSlot) => { const name = slot.attachment.name || untitled; if (slot.kind === 'image') { - return ; + return ( + + ); } if (slot.kind === 'pending') { return slot.image ? ( - + ) : ( ); @@ -56,31 +78,76 @@ export function BotGroupMessageAttachments({ /> ); }; + const gallery = rows + .flatMap((row) => (row.kind === 'picture' ? [row.slot] : row.slots)) + .flatMap((slot) => + slot.kind === 'image' ? [{ src: slot.url, galleryId: slot.attachment.id }] : [], + ); + const collection: AttachmentCollectionItem[] = rows + .flatMap((row) => (row.kind === 'picture' ? [row.slot] : row.slots)) + .flatMap((slot): AttachmentCollectionItem[] => + slot.kind === 'pending' + ? [] + : [ + { + id: slot.attachment.id, + name: slot.attachment.name || untitled, + source: slot.kind === 'image' ? slot.url : slot.path, + kind: + slot.kind === 'image' + ? 'image' + : attachmentPreviewKind(slot.attachment.name || slot.path), + media: slot.kind === 'image', + }, + ], + ); return ( -
- {rows.map((row) => row.kind === 'picture' ? renderSlot(row.slot) : ( -
- {row.slots.map(renderSlot)} + + +
+ {rows.map((row) => + row.kind === 'picture' ? ( + renderSlot(row.slot) + ) : ( +
+ {row.slots.map(renderSlot)} +
+ ), + )} + {textPreview ? ( + setTextPreview(null)} + /> + ) : null}
- ))} - {textPreview ? ( - setTextPreview(null)} - /> - ) : null} -
+ + ); } /** A file that is not on this computer yet: the same chip shape, not openable, with its state. */ -function PendingAttachmentChip({ state, name }: { state: BotGroupPendingSlot['state']; name: string }) { +function PendingAttachmentChip({ + state, + name, +}: { + state: BotGroupPendingSlot['state']; + name: string; +}) { const { t } = useTranslation(); const loading = state === 'loading'; const status = t(loading ? 'bots.groupChat.files.loading' : 'bots.groupChat.files.unavailable'); diff --git a/apps/desktop/src/renderer/features/bots/__tests__/BotGroupChatView.test.tsx b/apps/desktop/src/renderer/features/bots/__tests__/BotGroupChatView.test.tsx index ed4ba5e5beb..51ee8aa42c7 100644 --- a/apps/desktop/src/renderer/features/bots/__tests__/BotGroupChatView.test.tsx +++ b/apps/desktop/src/renderer/features/bots/__tests__/BotGroupChatView.test.tsx @@ -26,6 +26,10 @@ import type { BotGroupPlanView, } from '../../../../shared/botGroupChat'; +vi.mock('@/components/chat/FileLightbox', () => ({ + FileLightbox: ({ filePath }: { filePath: string }) =>
, +})); + const mocks = vi.hoisted(() => ({ // Keep translation identity stable so attachment effects do not reload on every render. t: (key: string, options?: Record) => @@ -916,8 +920,8 @@ describe('BotGroupChatView', () => { if (kind === 'text-control') within(preview as HTMLElement).getAllByRole('button', { name: 'chat.lightbox.close' })[0]!.focus(); else if (kind === 'text-body') { await user.click(screen.getByText('Preview note content')); - expect(document.activeElement).toBe(document.body); - } else expect(screen.getByRole('main').contains(document.activeElement)).toBe(true); + expect(preview.contains(document.activeElement)).toBe(true); + } else expect(preview.contains(document.activeElement)).toBe(true); } await user.keyboard('{Escape}'); expect(screen.getByRole('complementary')).toBe(panel); @@ -944,10 +948,10 @@ describe('BotGroupChatView', () => { render(, { container }); const diagram = await within(container).findByRole('button', { name: 'chat.mermaid.clickToZoom' }); await user.click(diagram); - await screen.findByRole('button', { name: 'chat.mermaidLightbox.close' }); - expect(document.activeElement).toBe(diagram); + await screen.findByRole('button', { name: 'chat.lightbox.close' }); + expect(document.querySelector('[data-lightbox-layer]')?.contains(document.activeElement)).toBe(true); await user.keyboard('{Escape}'); - await waitFor(() => expect(screen.queryByRole('button', { name: 'chat.mermaidLightbox.close' })).toBeNull()); + await waitFor(() => expect(screen.queryByRole('button', { name: 'chat.lightbox.close' })).toBeNull()); expect(screen.getByRole('complementary')).toBe(panel); expect((within(panel).getByRole('textbox') as HTMLTextAreaElement).value).toBe('Unsent diagram reply'); await user.keyboard('{Escape}'); @@ -3134,9 +3138,10 @@ describe('BotGroupChatView', () => { const bubble = image.closest('[data-testid="bot-group-self-bubble"]')!; const chip = screen.getByRole('button', { name: '报告.pdf' }); expect(bubble.contains(chip)).toBe(true); - // A PDF is not text: it opens in the system app, as from a task's chip. + // PDF enters the same in-app attachment viewer as a task's chip. fireEvent.click(chip); - await waitFor(() => expect(mocks.openPath).toHaveBeenCalledWith('/Users/me/报告.pdf')); + expect((await screen.findByTestId('group-pdf-preview')).getAttribute('data-path')).toBe('/Users/me/报告.pdf'); + expect(mocks.openPath).not.toHaveBeenCalled(); const withText = screen.getByAltText('草图.png').closest('article')!; const textBubbles = withText.querySelectorAll('[data-testid="bot-group-self-bubble"]'); diff --git a/apps/desktop/src/renderer/features/cc-agent/workdir-browse/PdfPreview.tsx b/apps/desktop/src/renderer/features/cc-agent/workdir-browse/PdfPreview.tsx index 8f6c2c25a8c..34454d010d3 100644 --- a/apps/desktop/src/renderer/features/cc-agent/workdir-browse/PdfPreview.tsx +++ b/apps/desktop/src/renderer/features/cc-agent/workdir-browse/PdfPreview.tsx @@ -32,6 +32,9 @@ import { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import * as pdfjs from 'pdfjs-dist'; +import 'pdfjs-dist/web/pdf_viewer.css'; +import { ChevronLeft, ChevronRight, Maximize, Minus, Plus, Search } from 'lucide-react'; +import { PreviewButton, PreviewFooter } from '@/components/chat/PreviewChrome'; import workerUrl from 'pdfjs-dist/build/pdf.worker.min.mjs?url'; import { createLogger } from '@/lib/logger'; @@ -128,11 +131,13 @@ interface PdfPageProps { pdf: pdfjs.PDFDocumentProxy; pageNumber: number; isVisible: boolean; + scale: number; onError: (pageNumber: number, error: unknown) => void; } -function PdfPage({ pdf, pageNumber, isVisible, onError }: PdfPageProps) { +function PdfPage({ pdf, pageNumber, isVisible, scale, onError }: PdfPageProps) { const canvasRef = useRef(null); + const textRef = useRef(null); const renderTaskRef = useRef(null); const [renderState, setRenderState] = useState('idle'); const [pageSize, setPageSize] = useState({ @@ -149,6 +154,7 @@ function PdfPage({ pdf, pageNumber, isVisible, onError }: PdfPageProps) { cancelRenderTask(renderTaskRef.current); renderTaskRef.current = null; clearCanvas(canvasRef.current); + textRef.current?.replaceChildren(); setRenderState((current) => (current === 'idle' ? current : 'idle')); }, [isVisible]); @@ -156,6 +162,7 @@ function PdfPage({ pdf, pageNumber, isVisible, onError }: PdfPageProps) { if (!isVisible) return; let cancelled = false; let activeTask: ActiveRenderTask | null = null; + let textTask: pdfjs.TextLayer | null = null; setRenderState('rendering'); void (async () => { @@ -165,7 +172,7 @@ function PdfPage({ pdf, pageNumber, isVisible, onError }: PdfPageProps) { const page = await pdf.getPage(pageNumber); if (cancelled) return; - const viewport = page.getViewport({ scale: PDF_PREVIEW_SCALE }); + const viewport = page.getViewport({ scale }); setPageSize({ width: viewport.width, height: viewport.height }); const canvas = canvasRef.current; @@ -192,6 +199,16 @@ function PdfPage({ pdf, pageNumber, isVisible, onError }: PdfPageProps) { if (cancelled || renderTaskRef.current !== task) return; renderTaskRef.current = null; setRenderState('rendered'); + const container = textRef.current; + if (container) { + container.replaceChildren(); + container.style.setProperty('--scale-factor', String(viewport.scale)); + container.style.setProperty('--total-scale-factor', String(viewport.scale)); + const textContentSource = await page.getTextContent(); + if (cancelled) return; + textTask = new pdfjs.TextLayer({ textContentSource, container, viewport }); + await textTask.render(); + } } catch (error) { // Leaving the viewport intentionally rejects RenderTask.promise with // RenderingCancelledException. It is not a failed PDF and must not @@ -215,10 +232,12 @@ function PdfPage({ pdf, pageNumber, isVisible, onError }: PdfPageProps) { return () => { cancelled = true; + textTask?.cancel(); + textRef.current?.replaceChildren(); cancelRenderTask(activeTask ?? renderTaskRef.current); if (renderTaskRef.current === activeTask) renderTaskRef.current = null; }; - }, [isVisible, onError, pageNumber, pdf]); + }, [isVisible, onError, pageNumber, pdf, scale]); return (
)} +
{renderState !== 'rendered' && (
(null); const [pdfDoc, setPdfDoc] = useState(null); const [pageCount, setPageCount] = useState(0); + const [currentPage, setCurrentPage] = useState(1); + const [scale, setScale] = useState(PDF_PREVIEW_SCALE); + const [password, setPassword] = useState(''); + const [passwordNeeded, setPasswordNeeded] = useState(false); + const passwordReply = useRef<((value: string) => void) | null>(null); + const [retry, setRetry] = useState(0); + const [find, setFind] = useState(false); + const [query, setQuery] = useState(''); + const [matches, setMatches] = useState(null); + const searchGeneration = useRef(0); + const go = (page: number) => { + const next = Math.max(1, Math.min(pageCount, page)); + pagesHostRef.current + ?.querySelector(`[data-pdf-page="${next}"]`) + ?.scrollIntoView({ block: 'start' }); + setCurrentPage(next); + }; + const fit = async () => { + if (!pdfDoc) return; + const page = await pdfDoc.getPage(currentPage); + const width = scrollContainerRef.current?.clientWidth || 640; + const height = scrollContainerRef.current?.clientHeight || 800; + const viewport = page.getViewport({ scale: 1 }); + setScale( + Math.max(0.1, Math.min((width - 48) / viewport.width, (height - 48) / viewport.height)), + ); + }; + const search = async () => { + if (!pdfDoc) return; + const generation = ++searchGeneration.current; + const found: number[] = []; + const needle = query.toLocaleLowerCase(); + for (let page = 1; needle && page <= pdfDoc.numPages; page++) { + const content = await (await pdfDoc.getPage(page)).getTextContent(); + if (generation !== searchGeneration.current) return; + if ( + content.items + .map((item) => ('str' in item ? item.str : '')) + .join(' ') + .toLocaleLowerCase() + .includes(needle) + ) + found.push(page); + } + setMatches(found); + if (found[0]) go(found[0]); + }; const [visiblePages, setVisiblePages] = useState>(() => new Set()); const documentVersionRef = useRef(0); const [documentVersion, setDocumentVersion] = useState(0); @@ -288,6 +355,11 @@ export function PdfPreview({ workdir, relPath, size, mtimeMs }: PdfPreviewProps) setPageCount(0); setVisiblePages(new Set()); setState({ kind: 'loading' }); + setPasswordNeeded(false); + setPassword(''); + setCurrentPage(1); + setMatches(null); + searchGeneration.current++; const absPath = joinPath(workdir, relPath); @@ -306,12 +378,26 @@ export function PdfPreview({ workdir, relPath, size, mtimeMs }: PdfPreviewProps) cMapPacked: true, standardFontDataUrl: '/pdfjs/standard_fonts/', }); + loadingTask.onPassword = (update: (password: string) => void) => { + if (!cancelled) { + passwordReply.current = update; + setPasswordNeeded(true); + } + }; const pdf = await loadingTask.promise; loadedPdf = pdf; if (cancelled) { await pdf.destroy(); return; } + const first = await pdf.getPage(1); + if (cancelled) return; + const width = scrollContainerRef.current?.clientWidth || 640; + const height = scrollContainerRef.current?.clientHeight || 800; + const viewport = first.getViewport({ scale: 1 }); + setScale( + Math.max(0.1, Math.min((width - 48) / viewport.width, (height - 48) / viewport.height)), + ); setPdfDoc(pdf); setPageCount(pdf.numPages); setState({ kind: 'rendered', pageCount: pdf.numPages }); @@ -326,6 +412,8 @@ export function PdfPreview({ workdir, relPath, size, mtimeMs }: PdfPreviewProps) return () => { cancelled = true; + searchGeneration.current++; + passwordReply.current = null; // destroy 两者:loadingTask 中止未完成的加载;pdfDoc 释放已解析文档的 // worker / 渲染资源(promise resolve 后仅 destroy loadingTask 不够, // 中途导航离开会泄漏文档)。destroy 幂等,两者都调是安全的。 @@ -335,7 +423,7 @@ export function PdfPreview({ workdir, relPath, size, mtimeMs }: PdfPreviewProps) // size/mtimeMs 进依赖:同一路径的文件被就地改写(agent 重生成 PDF 等)时 // relPath 不变但 mtimeMs/size 变,需要重新读字节 + 重渲染,否则预览会停在 // 旧内容直到用户切走再切回。FileBodyView 正是为此把 size/mtimeMs 传进来。 - }, [workdir, relPath, size, mtimeMs]); + }, [workdir, relPath, size, mtimeMs, retry]); useEffect(() => { const host = pagesHostRef.current; @@ -410,15 +498,31 @@ export function PdfPreview({ workdir, relPath, size, mtimeMs }: PdfPreviewProps) if (state.kind === 'error') { return ( - +
+ + + +
); } return ( -
+
+ onScroll={() => { + const root = scrollContainerRef.current; + if (!root) return; + const edge = root.getBoundingClientRect().top; + const page = Array.from( + pagesHostRef.current?.querySelectorAll('[data-pdf-page]') ?? [], + ).find((element) => element.getBoundingClientRect().bottom > edge + 24); + if (page) setCurrentPage(Number(page.dataset.pdfPage)); + }} + className="relative h-full w-full overflow-auto bg-[var(--surface)]" + > {/* Align pages to a shared left edge: a wide page must not push narrower placeholders outside the observer's horizontal viewport. */}
@@ -430,7 +534,8 @@ export function PdfPreview({ workdir, relPath, size, mtimeMs }: PdfPreviewProps) pageNumber={index + 1} isVisible={visiblePages.has(index + 1)} onError={handlePageError} - /> + scale={scale} + /> ))} {state.kind === 'loading' && (
@@ -441,5 +546,100 @@ export function PdfPreview({ workdir, relPath, size, mtimeMs }: PdfPreviewProps) )}
+ {passwordNeeded ? ( +
{ + event.preventDefault(); + passwordReply.current?.(password); + setPassword(''); + setPasswordNeeded(false); + }} + > + + setPassword(event.target.value)} + className="rounded-xl border border-[var(--border-default)] bg-[var(--surface)] p-3" + /> + +
+ ) : null} + {find ? ( +
{ + event.preventDefault(); + void search(); + }} + > + setQuery(event.target.value)} + className="min-w-0 flex-1 bg-transparent" + /> + + {matches ? ( + + {t('attachmentPreview.matches', { count: matches.length })} + + ) : null} +
+ ) : null} + {pageCount ? ( + + go(currentPage - 1)} + > + + + + {currentPage} / {pageCount} + + = pageCount} + onClick={() => go(currentPage + 1)} + > + + + setScale((value) => Math.max(0.1, value / 1.25))} + > + + + {Math.round(scale * 100)}% + setScale((value) => Math.min(8, value * 1.25))} + > + + + { + void fit(); + }} + > + + + setFind((value) => !value)} + > + + + + ) : null} +
); } diff --git a/apps/desktop/src/renderer/features/cc-agent/workdir-browse/__tests__/PdfPreview.test.tsx b/apps/desktop/src/renderer/features/cc-agent/workdir-browse/__tests__/PdfPreview.test.tsx index 6402ac2d0b2..9e822a0e1c4 100644 --- a/apps/desktop/src/renderer/features/cc-agent/workdir-browse/__tests__/PdfPreview.test.tsx +++ b/apps/desktop/src/renderer/features/cc-agent/workdir-browse/__tests__/PdfPreview.test.tsx @@ -7,6 +7,13 @@ vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => key }), })); +vi.mock('@/components/chat/PreviewChrome', () => ({ + PreviewFooter: ({ children }: { children: React.ReactNode }) =>
{children}
, + PreviewButton: ({ children, onClick }: { children: React.ReactNode; onClick: () => void }) => ( + + ), +})); + vi.mock('@/lib/logger', () => ({ createLogger: () => ({ warn: vi.fn() }), })); @@ -18,6 +25,12 @@ vi.mock('../UnrenderablePlaceholder', () => ({ vi.mock('pdfjs-dist', () => ({ GlobalWorkerOptions: {}, getDocument: vi.fn(), + TextLayer: class { + render() { + return Promise.resolve(); + } + cancel() {} + }, })); import * as pdfjs from 'pdfjs-dist'; @@ -130,6 +143,7 @@ describe('PdfPreview lazy page rendering', () => { it('keeps mixed-width pages left-aligned while rendering only nearby pages', async () => { const widths = [3000, 918, 3000]; const pages = widths.map((width) => ({ + getTextContent: vi.fn(async () => ({ items: [] })), getViewport: vi.fn(() => ({ width, height: 1188 })), render: vi.fn(() => ({ cancel: vi.fn(), promise: Promise.resolve() })), })); @@ -167,7 +181,7 @@ describe('PdfPreview lazy page rendering', () => { }); await waitFor(() => expect(pageNodes[index].dataset.pdfPagePlaceholder).toBeUndefined()); expect(pageNodes[index].style.width).toBe(`${widths[index]}px`); - expect(pdf.getPage).toHaveBeenCalledTimes(index + 1); + expect(pdf.getPage).toHaveBeenCalledTimes(index + 2); expect(pdf.getPage).toHaveBeenLastCalledWith(index + 1); expect(container.querySelectorAll('[data-pdf-page] canvas')).toHaveLength(1); } @@ -175,6 +189,7 @@ describe('PdfPreview lazy page rendering', () => { it('observes any intersection and restores huge pages after leaving the viewport', async () => { const page = { + getTextContent: vi.fn(async () => ({ items: [] })), getViewport: vi.fn(() => ({ width: 15_000, height: 15_000 })), render: vi.fn(() => ({ cancel: vi.fn(), promise: Promise.resolve() })), }; @@ -232,6 +247,7 @@ describe('PdfPreview lazy page rendering', () => { const renderDeferred = makeDeferred(); const renderTask = { cancel: vi.fn(), promise: renderDeferred.promise }; const page = { + getTextContent: vi.fn(async () => ({ items: [] })), getViewport: vi.fn(() => ({ width: 918, height: 1188 })), render: vi.fn(() => renderTask), }; @@ -277,6 +293,7 @@ describe('PdfPreview lazy page rendering', () => { const renderDeferred = makeDeferred(); const renderTask = { cancel: vi.fn(), promise: renderDeferred.promise }; const page = { + getTextContent: vi.fn(async () => ({ items: [] })), getViewport: vi.fn(() => ({ width: 918, height: 1188 })), render: vi.fn(() => renderTask), }; @@ -317,10 +334,12 @@ describe('PdfPreview lazy page rendering', () => { it('ignores queued observer callbacks from a replaced document', async () => { const oldPage = { + getTextContent: vi.fn(async () => ({ items: [] })), getViewport: vi.fn(() => ({ width: 918, height: 1188 })), render: vi.fn(() => ({ cancel: vi.fn(), promise: Promise.resolve() })), }; const nextPage = { + getTextContent: vi.fn(async () => ({ items: [] })), getViewport: vi.fn(() => ({ width: 918, height: 1188 })), render: vi.fn(() => ({ cancel: vi.fn(), promise: Promise.resolve() })), }; @@ -379,6 +398,7 @@ describe('PdfPreview lazy page rendering', () => { promise: renderDeferred.promise, }; const page = { + getTextContent: vi.fn(async () => ({ items: [] })), getViewport: vi.fn(() => ({ width: 918, height: 1188 })), render: vi.fn(() => renderTask), }; diff --git a/apps/desktop/src/renderer/i18n/locales/de/common.json b/apps/desktop/src/renderer/i18n/locales/de/common.json index 35a1c6eaaf7..233ffdf6e21 100644 --- a/apps/desktop/src/renderer/i18n/locales/de/common.json +++ b/apps/desktop/src/renderer/i18n/locales/de/common.json @@ -18828,5 +18828,32 @@ "unavailable": "Für diesen Build ist kein Upload-Ziel für Berichte festgelegt. Öffne die lokalen Dateien, um sie mit dem Support zu teilen.", "consent": "Akzeptiere vor dem Senden die Datenschutzerklärung in den Einstellungen. Deine lokalen Dateien bleiben erhalten.", "failed": "Der Vorgang wurde nicht abgeschlossen. Versuch es erneut oder öffne die lokalen Dateien." + }, + "attachmentPreview": { + "more": "Weitere Aktionen", + "retry": "Erneut versuchen", + "image": "Bild", + "video": "Video", + "file": "Datei", + "close": "Schließen", + "reset": "Ansicht zurücksetzen", + "open": "Vorschau öffnen", + "zoomIn": "Vergrößern", + "zoomOut": "Verkleinern", + "fit": "An Fenster anpassen", + "actualSize": "Originalgröße (1:1)", + "save": "Original speichern", + "share": "Teilen", + "previous": "Vorheriger Anhang", + "next": "Nächster Anhang", + "password": "PDF-Passwort eingeben", + "find": "Im Dokument suchen", + "matches": "{{count}} passende Seiten", + "previousPage": "Vorherige Seite", + "nextPage": "Nächste Seite", + "source": "Quelltext anzeigen", + "rendered": "Dokument anzeigen", + "wrap": "Zeilenumbruch", + "lineNumbers": "Zeilennummern" } } diff --git a/apps/desktop/src/renderer/i18n/locales/en/common.json b/apps/desktop/src/renderer/i18n/locales/en/common.json index cfb5bacc7e4..72e56086481 100644 --- a/apps/desktop/src/renderer/i18n/locales/en/common.json +++ b/apps/desktop/src/renderer/i18n/locales/en/common.json @@ -12703,7 +12703,41 @@ "unavailable-route": "{{plugin}} chose a model you can’t use, so this task uses" } }, - "teammateProcess": {"record":"Activity","operations":"Activity log","elapsed":"Working for {{duration}}","took":"Took {{duration}}","processing":"Working","worked":"Activity","thinking":"Thinking","thought":"Thought","failed_one":"{{count}} call failed","failed_other":"{{count}} calls failed","separator":", ","actions":{"running":{"read":"Reading files","edit":"Editing files","search":"Searching","web":"Browsing the web","command":"Running commands","tools":"Using tools","delegate":"Delegating work","plan":"Updating the plan"},"done":{"read":"Read files","edit":"Edited files","search":"Searched","web":"Browsed the web","command":"Ran commands","tools":"Used tools","delegate":"Delegated work","plan":"Updated the plan"}}}, + "teammateProcess": { + "record": "Activity", + "operations": "Activity log", + "elapsed": "Working for {{duration}}", + "took": "Took {{duration}}", + "processing": "Working", + "worked": "Activity", + "thinking": "Thinking", + "thought": "Thought", + "failed_one": "{{count}} call failed", + "failed_other": "{{count}} calls failed", + "separator": ", ", + "actions": { + "running": { + "read": "Reading files", + "edit": "Editing files", + "search": "Searching", + "web": "Browsing the web", + "command": "Running commands", + "tools": "Using tools", + "delegate": "Delegating work", + "plan": "Updating the plan" + }, + "done": { + "read": "Read files", + "edit": "Edited files", + "search": "Searched", + "web": "Browsed the web", + "command": "Ran commands", + "tools": "Used tools", + "delegate": "Delegated work", + "plan": "Updated the plan" + } + } + }, "imageDelivery": { "stored": "Image delivered", "failed": "Image delivery failed. Retry transferring the existing image.", @@ -18794,5 +18828,32 @@ "unavailable": "This build has no report upload destination. Open the local files to share them with support.", "consent": "Accept the privacy policy in Settings before submitting. Your local files are preserved.", "failed": "The operation did not finish. Retry or open the local files." + }, + "attachmentPreview": { + "more": "More actions", + "retry": "Retry", + "image": "Image", + "video": "Video", + "file": "File", + "close": "Close", + "reset": "Reset view", + "open": "Open preview", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fit": "Fit to window", + "actualSize": "Actual size (1:1)", + "save": "Save original", + "share": "Share", + "previous": "Previous attachment", + "next": "Next attachment", + "password": "Enter the PDF password", + "find": "Find in document", + "matches": "{{count}} matching pages", + "previousPage": "Previous page", + "nextPage": "Next page", + "source": "View source", + "rendered": "View document", + "wrap": "Wrap lines", + "lineNumbers": "Line numbers" } } diff --git a/apps/desktop/src/renderer/i18n/locales/es/common.json b/apps/desktop/src/renderer/i18n/locales/es/common.json index 7782f27070d..8475fdbf664 100644 --- a/apps/desktop/src/renderer/i18n/locales/es/common.json +++ b/apps/desktop/src/renderer/i18n/locales/es/common.json @@ -18998,5 +18998,32 @@ "unavailable": "Esta compilación no tiene un destino para subir informes. Abre los archivos locales para compartirlos con soporte.", "consent": "Acepta la política de privacidad en Configuración antes de enviar. Tus archivos locales se conservan.", "failed": "La operación no finalizó. Vuelve a intentarlo o abre los archivos locales." + }, + "attachmentPreview": { + "more": "Más acciones", + "retry": "Reintentar", + "image": "Imagen", + "video": "Vídeo", + "file": "Archivo", + "close": "Cerrar", + "reset": "Restablecer vista", + "open": "Abrir vista previa", + "zoomIn": "Acercar", + "zoomOut": "Alejar", + "fit": "Ajustar a la ventana", + "actualSize": "Tamaño real (1:1)", + "save": "Guardar original", + "share": "Compartir", + "previous": "Adjunto anterior", + "next": "Adjunto siguiente", + "password": "Introduce la contraseña del PDF", + "find": "Buscar en el documento", + "matches": "{{count}} páginas coincidentes", + "previousPage": "Página anterior", + "nextPage": "Página siguiente", + "source": "Ver fuente", + "rendered": "Ver documento", + "wrap": "Ajustar líneas", + "lineNumbers": "Números de línea" } } diff --git a/apps/desktop/src/renderer/i18n/locales/fr/common.json b/apps/desktop/src/renderer/i18n/locales/fr/common.json index e2ce1650122..573dab31aa7 100644 --- a/apps/desktop/src/renderer/i18n/locales/fr/common.json +++ b/apps/desktop/src/renderer/i18n/locales/fr/common.json @@ -18998,5 +18998,32 @@ "unavailable": "Cette version ne peut pas envoyer les rapports. Ouvrez les fichiers locaux pour les transmettre à l’assistance.", "consent": "Acceptez la politique de confidentialité dans Paramètres avant de soumettre. Vos fichiers locaux sont préservés.", "failed": "L'opération n'est pas terminée. Réessayez ou ouvrez les fichiers locaux." + }, + "attachmentPreview": { + "more": "Autres actions", + "retry": "Réessayer", + "image": "Image", + "video": "Vidéo", + "file": "Fichier", + "close": "Fermer", + "reset": "Réinitialiser la vue", + "open": "Ouvrir l’aperçu", + "zoomIn": "Zoom avant", + "zoomOut": "Zoom arrière", + "fit": "Ajuster à la fenêtre", + "actualSize": "Taille réelle (1:1)", + "save": "Enregistrer l’original", + "share": "Partager", + "previous": "Pièce jointe précédente", + "next": "Pièce jointe suivante", + "password": "Saisir le mot de passe du PDF", + "find": "Rechercher dans le document", + "matches": "{{count}} pages correspondantes", + "previousPage": "Page précédente", + "nextPage": "Page suivante", + "source": "Voir la source", + "rendered": "Afficher le document", + "wrap": "Retour à la ligne", + "lineNumbers": "Numéros de ligne" } } diff --git a/apps/desktop/src/renderer/i18n/locales/ja/common.json b/apps/desktop/src/renderer/i18n/locales/ja/common.json index 7ae4388a924..d64f5606eab 100644 --- a/apps/desktop/src/renderer/i18n/locales/ja/common.json +++ b/apps/desktop/src/renderer/i18n/locales/ja/common.json @@ -12651,7 +12651,40 @@ "unavailable-route": "{{plugin}} が選んだモデルは使えないため、このタスクは" } }, - "teammateProcess": {"record":"作業履歴","operations":"操作履歴","elapsed":"処理中 {{duration}}","took":"所要時間 {{duration}}","processing":"処理中","worked":"作業履歴","thinking":"考えています","thought":"考えました","failed_other":"{{count}} 件の呼び出しに失敗","separator":"・","actions":{"running":{"read":"ファイルを確認しています","edit":"ファイルを編集しています","search":"検索しています","web":"ウェブを調べています","command":"コマンドを実行しています","tools":"ツールを使用しています","delegate":"作業を依頼しています","plan":"計画を更新しています"},"done":{"read":"ファイルを確認しました","edit":"ファイルを編集しました","search":"検索しました","web":"ウェブを調べました","command":"コマンドを実行しました","tools":"ツールを使用しました","delegate":"作業を依頼しました","plan":"計画を更新しました"}}}, + "teammateProcess": { + "record": "作業履歴", + "operations": "操作履歴", + "elapsed": "処理中 {{duration}}", + "took": "所要時間 {{duration}}", + "processing": "処理中", + "worked": "作業履歴", + "thinking": "考えています", + "thought": "考えました", + "failed_other": "{{count}} 件の呼び出しに失敗", + "separator": "・", + "actions": { + "running": { + "read": "ファイルを確認しています", + "edit": "ファイルを編集しています", + "search": "検索しています", + "web": "ウェブを調べています", + "command": "コマンドを実行しています", + "tools": "ツールを使用しています", + "delegate": "作業を依頼しています", + "plan": "計画を更新しています" + }, + "done": { + "read": "ファイルを確認しました", + "edit": "ファイルを編集しました", + "search": "検索しました", + "web": "ウェブを調べました", + "command": "コマンドを実行しました", + "tools": "ツールを使用しました", + "delegate": "作業を依頼しました", + "plan": "計画を更新しました" + } + } + }, "imageDelivery": { "stored": "画像を受け取りました", "failed": "画像の転送に失敗しました。既存の画像の転送を再試行できます。", @@ -18723,5 +18756,32 @@ "unavailable": "このビルドにはレポートの送信先が設定されていません。ローカルファイルを開いてサポートに共有できます。", "consent": "設定でプライバシーポリシーに同意してから送信してください。ローカルファイルは保持されています。", "failed": "操作が完了しませんでした。再試行するか、ローカルファイルを確認してください。" + }, + "attachmentPreview": { + "more": "その他の操作", + "retry": "再試行", + "image": "画像", + "video": "動画", + "file": "ファイル", + "close": "閉じる", + "reset": "表示をリセット", + "open": "プレビューを開く", + "zoomIn": "拡大", + "zoomOut": "縮小", + "fit": "ウィンドウに合わせる", + "actualSize": "実際のサイズ(1:1)", + "save": "元のファイルを保存", + "share": "共有", + "previous": "前の添付ファイル", + "next": "次の添付ファイル", + "password": "PDF のパスワードを入力", + "find": "文書内を検索", + "matches": "一致するページ:{{count}}", + "previousPage": "前のページ", + "nextPage": "次のページ", + "source": "ソースを表示", + "rendered": "文書を表示", + "wrap": "行の折り返し", + "lineNumbers": "行番号" } } diff --git a/apps/desktop/src/renderer/i18n/locales/ko/common.json b/apps/desktop/src/renderer/i18n/locales/ko/common.json index ab133b8d83f..4832ac16fa7 100644 --- a/apps/desktop/src/renderer/i18n/locales/ko/common.json +++ b/apps/desktop/src/renderer/i18n/locales/ko/common.json @@ -12651,7 +12651,40 @@ "unavailable-route": "{{plugin}}이(가) 고른 모델을 쓸 수 없어 이 작업은" } }, - "teammateProcess": {"record":"작업 내역","operations":"작업 기록","elapsed":"처리 중 {{duration}}","took":"소요 시간 {{duration}}","processing":"처리 중","worked":"작업 내역","thinking":"생각 중","thought":"생각함","failed_other":"호출 {{count}}회 실패","separator":" · ","actions":{"running":{"read":"파일 확인 중","edit":"파일 수정 중","search":"검색 중","web":"웹 확인 중","command":"명령 실행 중","tools":"도구 사용 중","delegate":"작업 위임 중","plan":"계획 정리 중"},"done":{"read":"파일 확인함","edit":"파일 수정함","search":"검색함","web":"웹 확인함","command":"명령 실행함","tools":"도구 사용함","delegate":"작업 위임함","plan":"계획 정리함"}}}, + "teammateProcess": { + "record": "작업 내역", + "operations": "작업 기록", + "elapsed": "처리 중 {{duration}}", + "took": "소요 시간 {{duration}}", + "processing": "처리 중", + "worked": "작업 내역", + "thinking": "생각 중", + "thought": "생각함", + "failed_other": "호출 {{count}}회 실패", + "separator": " · ", + "actions": { + "running": { + "read": "파일 확인 중", + "edit": "파일 수정 중", + "search": "검색 중", + "web": "웹 확인 중", + "command": "명령 실행 중", + "tools": "도구 사용 중", + "delegate": "작업 위임 중", + "plan": "계획 정리 중" + }, + "done": { + "read": "파일 확인함", + "edit": "파일 수정함", + "search": "검색함", + "web": "웹 확인함", + "command": "명령 실행함", + "tools": "도구 사용함", + "delegate": "작업 위임함", + "plan": "계획 정리함" + } + } + }, "imageDelivery": { "stored": "이미지 전달 완료", "failed": "이미지 전달에 실패했습니다. 기존 이미지 전송을 다시 시도할 수 있습니다.", @@ -18723,5 +18756,32 @@ "unavailable": "이 빌드에는 보고서 업로드 대상이 설정되어 있지 않습니다. 로컬 파일을 열어 지원 담당자에게 공유할 수 있습니다.", "consent": "설정에서 개인정보 처리방침에 동의한 후 제출하세요. 로컬 파일은 보관되어 있습니다.", "failed": "작업이 완료되지 않았습니다. 다시 시도하거나 로컬 파일을 확인하세요." + }, + "attachmentPreview": { + "more": "추가 작업", + "retry": "다시 시도", + "image": "이미지", + "video": "동영상", + "file": "파일", + "close": "닫기", + "reset": "보기 초기화", + "open": "미리보기 열기", + "zoomIn": "확대", + "zoomOut": "축소", + "fit": "창에 맞추기", + "actualSize": "실제 크기(1:1)", + "save": "원본 저장", + "share": "공유", + "previous": "이전 첨부 파일", + "next": "다음 첨부 파일", + "password": "PDF 암호 입력", + "find": "문서에서 찾기", + "matches": "일치하는 페이지 {{count}}개", + "previousPage": "이전 페이지", + "nextPage": "다음 페이지", + "source": "소스 보기", + "rendered": "문서 보기", + "wrap": "자동 줄 바꿈", + "lineNumbers": "줄 번호" } } diff --git a/apps/desktop/src/renderer/i18n/locales/pt-BR/common.json b/apps/desktop/src/renderer/i18n/locales/pt-BR/common.json index d532b380231..cc92716b764 100644 --- a/apps/desktop/src/renderer/i18n/locales/pt-BR/common.json +++ b/apps/desktop/src/renderer/i18n/locales/pt-BR/common.json @@ -18998,5 +18998,32 @@ "unavailable": "Esta compilação não tem um destino de envio de relatórios. Abra os arquivos locais para compartilhá-los com o suporte.", "consent": "Aceite a política de privacidade nas Configurações antes de enviar. Seus arquivos locais serão preservados.", "failed": "A operação não foi concluída. Tente novamente ou abra os arquivos locais." + }, + "attachmentPreview": { + "more": "Mais ações", + "retry": "Tentar novamente", + "image": "Imagem", + "video": "Vídeo", + "file": "Arquivo", + "close": "Fechar", + "reset": "Redefinir visualização", + "open": "Abrir prévia", + "zoomIn": "Ampliar", + "zoomOut": "Reduzir", + "fit": "Ajustar à janela", + "actualSize": "Tamanho real (1:1)", + "save": "Salvar original", + "share": "Compartilhar", + "previous": "Anexo anterior", + "next": "Próximo anexo", + "password": "Digite a senha do PDF", + "find": "Buscar no documento", + "matches": "{{count}} páginas correspondentes", + "previousPage": "Página anterior", + "nextPage": "Próxima página", + "source": "Ver código-fonte", + "rendered": "Ver documento", + "wrap": "Quebra de linha", + "lineNumbers": "Números de linha" } } diff --git a/apps/desktop/src/renderer/i18n/locales/pt-PT/common.json b/apps/desktop/src/renderer/i18n/locales/pt-PT/common.json index a9e765955cd..d54c17b14d1 100644 --- a/apps/desktop/src/renderer/i18n/locales/pt-PT/common.json +++ b/apps/desktop/src/renderer/i18n/locales/pt-PT/common.json @@ -18998,5 +18998,32 @@ "unavailable": "Esta compilação não tem um destino para carregar relatórios. Abra os ficheiros locais para os partilhar com o apoio técnico.", "consent": "Aceite a política de privacidade nas Definições antes de enviar. Os seus ficheiros locais são preservados.", "failed": "A operação não foi concluída. Tente novamente ou abra os ficheiros locais." + }, + "attachmentPreview": { + "more": "Mais ações", + "retry": "Tentar novamente", + "image": "Imagem", + "video": "Vídeo", + "file": "Ficheiro", + "close": "Fechar", + "reset": "Repor vista", + "open": "Abrir pré-visualização", + "zoomIn": "Ampliar", + "zoomOut": "Reduzir", + "fit": "Ajustar à janela", + "actualSize": "Tamanho real (1:1)", + "save": "Guardar original", + "share": "Partilhar", + "previous": "Anexo anterior", + "next": "Anexo seguinte", + "password": "Introduzir palavra-passe do PDF", + "find": "Pesquisar no documento", + "matches": "{{count}} páginas correspondentes", + "previousPage": "Página anterior", + "nextPage": "Página seguinte", + "source": "Ver código-fonte", + "rendered": "Ver documento", + "wrap": "Moldar linhas", + "lineNumbers": "Números de linha" } } diff --git a/apps/desktop/src/renderer/i18n/locales/zh-CN/common.json b/apps/desktop/src/renderer/i18n/locales/zh-CN/common.json index 726b1567d1a..83f7156102d 100644 --- a/apps/desktop/src/renderer/i18n/locales/zh-CN/common.json +++ b/apps/desktop/src/renderer/i18n/locales/zh-CN/common.json @@ -12651,7 +12651,40 @@ "unavailable-route": "{{plugin}} 选的模型现在用不了,这个任务用" } }, - "teammateProcess": {"record":"处理记录","operations":"操作记录","elapsed":"已处理 {{duration}}","took":"用时 {{duration}}","processing":"正在处理","worked":"处理记录","thinking":"正在思考","thought":"已思考","failed_other":"{{count}} 次调用失败","separator":"、","actions":{"running":{"read":"正在查看文件","edit":"正在编辑文件","search":"正在查找内容","web":"正在查阅网页","command":"正在运行命令","tools":"正在使用工具","delegate":"正在委派工作","plan":"正在整理计划"},"done":{"read":"查看了文件","edit":"编辑了文件","search":"查找了内容","web":"查阅了网页","command":"运行了命令","tools":"使用了工具","delegate":"委派了工作","plan":"整理了计划"}}}, + "teammateProcess": { + "record": "处理记录", + "operations": "操作记录", + "elapsed": "已处理 {{duration}}", + "took": "用时 {{duration}}", + "processing": "正在处理", + "worked": "处理记录", + "thinking": "正在思考", + "thought": "已思考", + "failed_other": "{{count}} 次调用失败", + "separator": "、", + "actions": { + "running": { + "read": "正在查看文件", + "edit": "正在编辑文件", + "search": "正在查找内容", + "web": "正在查阅网页", + "command": "正在运行命令", + "tools": "正在使用工具", + "delegate": "正在委派工作", + "plan": "正在整理计划" + }, + "done": { + "read": "查看了文件", + "edit": "编辑了文件", + "search": "查找了内容", + "web": "查阅了网页", + "command": "运行了命令", + "tools": "使用了工具", + "delegate": "委派了工作", + "plan": "整理了计划" + } + } + }, "imageDelivery": { "stored": "图片已交付", "failed": "图片交付失败,可重试传输已有图片。", @@ -18723,5 +18756,32 @@ "unavailable": "当前版本未配置报告上传地址。可打开本地文件,交给支持人员。", "consent": "请先在设置中同意隐私政策,再提交报告。本地文件已保留。", "failed": "操作未完成,请重试或查看本地文件。" + }, + "attachmentPreview": { + "more": "更多操作", + "retry": "重试", + "image": "图片", + "video": "视频", + "file": "文件", + "close": "关闭", + "reset": "重置视角", + "open": "打开预览", + "zoomIn": "放大", + "zoomOut": "缩小", + "fit": "适配窗口", + "actualSize": "原始尺寸(1:1)", + "save": "保存原件", + "share": "分享", + "previous": "上一个附件", + "next": "下一个附件", + "password": "输入 PDF 密码", + "find": "在文档中查找", + "matches": "{{count}} 页匹配", + "previousPage": "上一页", + "nextPage": "下一页", + "source": "查看源码", + "rendered": "查看文稿", + "wrap": "自动换行", + "lineNumbers": "行号" } } diff --git a/apps/desktop/src/renderer/i18n/locales/zh-Hans-x-lzh/common.json b/apps/desktop/src/renderer/i18n/locales/zh-Hans-x-lzh/common.json index ccc8d095a58..d7f76c67339 100644 --- a/apps/desktop/src/renderer/i18n/locales/zh-Hans-x-lzh/common.json +++ b/apps/desktop/src/renderer/i18n/locales/zh-Hans-x-lzh/common.json @@ -18756,5 +18756,32 @@ "unavailable": "此版未设报告上传之址。可启此机之卷,交予支持人员。", "consent": "请先于规制中同意隐私权政策,再呈报告。此机之卷仍存。", "failed": "操作未毕,请再试或览此机之卷。" + }, + "attachmentPreview": { + "more": "更多操作", + "retry": "重试", + "image": "图", + "video": "影片", + "file": "档案", + "close": "关闭", + "reset": "复原视角", + "open": "开启预览", + "zoomIn": "放大", + "zoomOut": "缩小", + "fit": "适于视窗", + "actualSize": "原始尺寸(1:1)", + "save": "存原件", + "share": "分享", + "previous": "上一附件", + "next": "下一附件", + "password": "请输入 PDF 密钥", + "find": "查此文", + "matches": "合者 {{count}} 页", + "previousPage": "前页", + "nextPage": "后页", + "source": "观原码", + "rendered": "观文", + "wrap": "折行", + "lineNumbers": "行数" } } diff --git a/apps/desktop/src/renderer/i18n/locales/zh-Hant-x-lzh/common.json b/apps/desktop/src/renderer/i18n/locales/zh-Hant-x-lzh/common.json index 8d2233d2dc4..44474ac5a2a 100644 --- a/apps/desktop/src/renderer/i18n/locales/zh-Hant-x-lzh/common.json +++ b/apps/desktop/src/renderer/i18n/locales/zh-Hant-x-lzh/common.json @@ -18756,5 +18756,32 @@ "unavailable": "此版未設報告上傳之址。可啟此機之卷,交予支援人員。", "consent": "請先於規制中同意隱私權政策,再呈報告。此機之卷仍存。", "failed": "操作未畢,請再試或覽此機之卷。" + }, + "attachmentPreview": { + "more": "更多操作", + "retry": "重試", + "image": "圖", + "video": "影片", + "file": "檔案", + "close": "關閉", + "reset": "復原視角", + "open": "開啟預覽", + "zoomIn": "放大", + "zoomOut": "縮小", + "fit": "適於視窗", + "actualSize": "原始尺寸(1:1)", + "save": "存原件", + "share": "分享", + "previous": "上一附件", + "next": "下一附件", + "password": "請輸入 PDF 密鑰", + "find": "查此文", + "matches": "合者 {{count}} 頁", + "previousPage": "前頁", + "nextPage": "後頁", + "source": "觀原碼", + "rendered": "觀文", + "wrap": "折行", + "lineNumbers": "行數" } } diff --git a/apps/desktop/src/renderer/i18n/locales/zh-TW/common.json b/apps/desktop/src/renderer/i18n/locales/zh-TW/common.json index 39187bb14c2..7b3d65c7dc2 100644 --- a/apps/desktop/src/renderer/i18n/locales/zh-TW/common.json +++ b/apps/desktop/src/renderer/i18n/locales/zh-TW/common.json @@ -12651,7 +12651,40 @@ "unavailable-route": "{{plugin}} 選的模型現在無法使用,這個任務用" } }, - "teammateProcess": {"record":"處理紀錄","operations":"操作記錄","elapsed":"已處理 {{duration}}","took":"用時 {{duration}}","processing":"正在處理","worked":"處理紀錄","thinking":"正在思考","thought":"已思考","failed_other":"{{count}} 次呼叫失敗","separator":"、","actions":{"running":{"read":"正在檢視檔案","edit":"正在編輯檔案","search":"正在尋找內容","web":"正在查閱網頁","command":"正在執行指令","tools":"正在使用工具","delegate":"正在委派工作","plan":"正在整理計畫"},"done":{"read":"檢視了檔案","edit":"編輯了檔案","search":"尋找了內容","web":"查閱了網頁","command":"執行了指令","tools":"使用了工具","delegate":"委派了工作","plan":"整理了計畫"}}}, + "teammateProcess": { + "record": "處理紀錄", + "operations": "操作記錄", + "elapsed": "已處理 {{duration}}", + "took": "用時 {{duration}}", + "processing": "正在處理", + "worked": "處理紀錄", + "thinking": "正在思考", + "thought": "已思考", + "failed_other": "{{count}} 次呼叫失敗", + "separator": "、", + "actions": { + "running": { + "read": "正在檢視檔案", + "edit": "正在編輯檔案", + "search": "正在尋找內容", + "web": "正在查閱網頁", + "command": "正在執行指令", + "tools": "正在使用工具", + "delegate": "正在委派工作", + "plan": "正在整理計畫" + }, + "done": { + "read": "檢視了檔案", + "edit": "編輯了檔案", + "search": "尋找了內容", + "web": "查閱了網頁", + "command": "執行了指令", + "tools": "使用了工具", + "delegate": "委派了工作", + "plan": "整理了計畫" + } + } + }, "imageDelivery": { "stored": "圖片已交付", "failed": "圖片交付失敗,可重試傳輸已有圖片。", @@ -18723,5 +18756,32 @@ "unavailable": "目前版本未設定報告上傳位址。可開啟本機檔案,交給支援人員。", "consent": "請先在設定中同意隱私權政策,再提交報告。本機檔案已保留。", "failed": "操作未完成,請重試或查看本機檔案。" + }, + "attachmentPreview": { + "more": "更多操作", + "retry": "重試", + "image": "圖片", + "video": "影片", + "file": "檔案", + "close": "關閉", + "reset": "重設視角", + "open": "開啟預覽", + "zoomIn": "放大", + "zoomOut": "縮小", + "fit": "符合視窗", + "actualSize": "原始尺寸(1:1)", + "save": "儲存原件", + "share": "分享", + "previous": "上一個附件", + "next": "下一個附件", + "password": "輸入 PDF 密碼", + "find": "在文件中尋找", + "matches": "{{count}} 頁符合", + "previousPage": "上一頁", + "nextPage": "下一頁", + "source": "檢視原始碼", + "rendered": "查看文件", + "wrap": "自動換行", + "lineNumbers": "行號" } } diff --git a/apps/desktop/src/renderer/lib/filePreview.ts b/apps/desktop/src/renderer/lib/filePreview.ts index de7736c6334..12b9c9cb51d 100644 --- a/apps/desktop/src/renderer/lib/filePreview.ts +++ b/apps/desktop/src/renderer/lib/filePreview.ts @@ -1,44 +1,16 @@ -import { shouldShowOpenPathError } from '../../shared/openPathResult'; -import { toast } from './toast'; -import { isTextPreviewSupported } from './textPreview'; -import { i18n } from '@/i18n'; -import { isRemoteFileOrigin, type SessionFileOrigin } from './sessionFileOrigin'; -import { openRemoteChatFile } from './remoteFileOpen'; +import type { SessionFileOrigin } from './sessionFileOrigin'; -/** - * TextLightbox only handles text/code/markdown. For every other local file, - * hand off to the OS default application instead of trying to decode binary - * content as UTF-8. +/** Legacy callers share one in-app entry; the viewer dispatches by actual file type. + * No opening side effect here: non-text attachments must not silently leave Cindy. + * Resource access and remote resolution remain in the viewer's existing host adapters. */ export async function shouldOpenTextLightbox(filePath: string): Promise { - if (isTextPreviewSupported(filePath)) return true; - - try { - const res = await window.electronAPI.openPath(filePath); - if (shouldShowOpenPathError(res)) { - toast.error(res.error || i18n.t('logic.errors.openFileFailed')); - } - } catch (err) { - toast.error(err instanceof Error ? err.message : i18n.t('logic.errors.openFileFailed')); - } - - return false; + return filePath.length > 0; } -/** - * shouldOpenTextLightbox 的 origin-aware 版本(聊天流调用面统一走这里): - * - 文本类扩展名 → true,调用方打开 TextLightbox(其内部按 origin 决定读本机 - * 还是远端取回缓存副本); - * - 非文本 + 本地会话 → 原有行为:直接交系统默认应用打开; - * - 非文本 + 远程会话 → **绝不**对远端路径调本机 openPath(那是误开本机同 - * 路径文件的最大单点),改走「取回缓存副本 → 打开副本」。 - */ export async function shouldOpenTextLightboxForOrigin( - ctx: { origin: SessionFileOrigin; workingDir: string }, + _ctx: { origin: SessionFileOrigin; workingDir: string }, filePath: string, ): Promise { - if (isTextPreviewSupported(filePath)) return true; - if (!isRemoteFileOrigin(ctx.origin)) return shouldOpenTextLightbox(filePath); - await openRemoteChatFile(ctx.origin, ctx.workingDir, filePath); - return false; + return shouldOpenTextLightbox(filePath); } diff --git a/apps/mobile/app/files/preview/[sessionId].tsx b/apps/mobile/app/files/preview/[sessionId].tsx index faf037ad29d..489604fb67c 100644 --- a/apps/mobile/app/files/preview/[sessionId].tsx +++ b/apps/mobile/app/files/preview/[sessionId].tsx @@ -1,3 +1,12 @@ +import { TextDocumentPreview } from "@/session/TextDocumentPreview"; +import { + PreviewFooter, + PreviewCollectionContext, +} from "@/session/PreviewChrome"; +import { ModelDocumentPreview } from "@/session/ModelDocumentPreview"; +import { DiagramDocumentPreview } from "@/session/DiagramDocumentPreview"; +import { File } from "expo-file-system"; +import { PdfDocumentPreview } from "@/session/PdfDocumentPreview"; /** * 远程文件 Quick Look 预览。 * @@ -53,7 +62,6 @@ import { useMobileMakerTransport } from '@/device-link/useMobileMakerTransport'; import type { FileBrowserReadFileResult, MobileMakerTransport } from '@/device-link/mobileMakerTransport'; import { isAbsolutePathShape, pathDisplayName } from '@/session/chatPathCandidate'; import { adaptTextFilePreviewResult, fetchRemoteAbsFileToUrl } from '@/session/remoteAbsFileFetch'; -import { preparePdfPreviewSource } from '@/session/pdfPreviewSource'; import { pdfPreviewsInline } from '@/session/pdfSystemOpen'; import { formatByteSize, isHtmlFilePreviewCandidate } from '@/session/filePreview'; import { joinRemotePath } from '@/session/htmlLocalResources'; @@ -73,7 +81,6 @@ import type { MobileHtmlPreview } from '@/session/mobileHtmlPreview'; import { prepareMobileHtmlPreview, type PrepareMobileHtmlPreview } from '@/session/mobileHtmlPreview'; import { useHtmlSnapshot } from '@/session/useHtmlSnapshot'; import { MainWindowActionButton } from '@/components/MobilePrimitives'; -import { useHtmlBrowserViewport } from '@/session/useHtmlBrowserViewport'; import { MarkdownFileReader } from '@/session/MarkdownFileReader'; import { RemoteMediaPlayerWebView } from '@/session/mediaPlayerWebView'; import { NativeVideoPlayer } from '@/session/NativeVideoPlayer'; @@ -111,18 +118,20 @@ const PDF_PREVIEWS_INLINE = pdfPreviewsInline(Platform.OS); type TextPreviewState = | { status: 'loading' } - | { status: 'ready'; lines: string[]; truncated: boolean; totalLines: number; content?: string } - | { status: 'unavailable'; reason: string; oversize?: boolean; retryable?: boolean }; + | { status: 'ready'; lines: string[]; truncated: boolean; totalLines: number; content?: string; + } + | { status: 'unavailable'; reason: string; oversize?: boolean; retryable?: boolean; + }; /** * 「渲染态」可用的两类文本:markdown 与 HTML。两者共用同一套双态机(下面 * TextPreviewPage 的 richView),差别只在渲染载体 —— markdown 经 buildSelectableMarkdownHtml * 转成我们自己的 HTML,HTML 生成物则原样进 WebView。 */ -type RichTextKind = 'markdown' | 'html'; +type RichTextKind = 'markdown' | 'html' | "diagram"; type HtmlBrowserActions = Pick; + | 'meta' | 'busy' | 'notice' | 'onCancelNotice' | 'onClose' | 'onShare' | 'onCopyPath' | 'onAddToTask'>; interface SiblingListing { key: string; @@ -155,6 +164,8 @@ type ExportToUrl = (relPath: string, mtimeMs: number, options?: ExportToUrlOptio * 问题从来不是它归一化,而是它的输出被当成了语义值。 */ function richTextKindOf(pathOrName: string): RichTextKind | null { + if (/\.(mmd|mermaid|drawio(?:\.svg)?|dio)$/i.test(pathOrName)) + return "diagram"; if (/\.(md|mdx|markdown)$/i.test(pathOrName)) return 'markdown'; if (isHtmlFilePreviewCandidate(pathOrName)) return 'html'; return null; @@ -168,7 +179,8 @@ function avKindFor(pathOrName: string): 'video' | 'audio' | null { } export default function RemoteFilePreviewScreen() { - const { sessionId, deviceId } = useLocalSearchParams<{ sessionId: string; deviceId?: string }>(); + const { sessionId, deviceId } = useLocalSearchParams<{ sessionId: string; deviceId?: string; + }>(); return ( @@ -221,11 +233,10 @@ function RemoteFilePreviewContent() { const [initialSiblings] = useState(() => initialRelPath && workdir ? previewSiblings(getCachedListingSync(maker.fileBrowser.cacheScope, workdir, parentRelPath(initialRelPath) ?? '') ?? [], sortMode, initialRelPath) : null); - const [siblings, setSiblings] = useState(() => ( - initialSiblings ?? (singleAbsPath ? [absPathItem(singleAbsPath)] : null) - )); + const [siblings, setSiblings] = useState(() => + initialSiblings ?? (singleAbsPath ? [absPathItem(singleAbsPath)] : null)); const [siblingListing, setSiblingListing] = useState(null); - const [pageIndex, setPageIndex] = useState(() => Math.max(0, initialSiblings?.findIndex(item => item.relPath === initialRelPath) ?? 0)); + const [pageIndex, setPageIndex] = useState(() => Math.max(0, initialSiblings?.findIndex((item) => item.relPath === initialRelPath) ?? 0)); // 屏级焦点(HTML 可执行 WebView 的挂载门之一,见 renderItem 的 visible)。 // 本屏被压栈(点「发送到会话」→ router.navigate 把会话页推到根 Stack 上)时 // 路由仍挂载、pageIndex 也不变,只有 focus 会翻。 @@ -247,7 +258,9 @@ function RemoteFilePreviewContent() { // Markdown WebView 的纵向滚动和宽公式横移保留在内层;正文明确横滑由文档 // 回调后在这里驱动同一个 pager,避免 WebView 与 FlatList 争抢触摸序列。 const reportMarkdownPager = useCallback((key: string, wants: boolean) => { - setMarkdownPagerPageKey((prev) => (wants ? key : (prev === key ? null : prev))); + setMarkdownPagerPageKey((prev) => + wants ? key : prev === key ? null : prev, + ); }, []); const showNotice = useCallback((text: string) => { @@ -546,7 +559,8 @@ function RemoteFilePreviewContent() { // 进行中的「下载原文件 / 分享」:提示上点「取消」即标记本次已取消并立刻收起提示, // 之后的结果(含失败)一律丢弃,不再弹分享单;手机侧下载阶段随之中止。 - const exportRunRef = useRef<{ cancelled: boolean; abort: AbortController } | null>(null); + const exportRunRef = useRef<{ cancelled: boolean; abort: AbortController; + } | null>(null); const cancelExport = useCallback(() => { const run = exportRunRef.current; if (!run) return; @@ -628,10 +642,14 @@ function RemoteFilePreviewContent() { return ( - {error ? <> + {error ? ( + <> {t('files.preview.readFailed')} - : } + + ) : ( + + )} - {error ? <> + {error ? ( + <> {t('files.preview.readFailed')} { setError(null); setRecoveryEpoch((epoch) => epoch + 1); } }} /> - : } + + ) : ( + + )} setMeasuredPageWidth(Math.max(1, event.nativeEvent.layout.width))} + style={ + siblings.length > 1 + ? { marginBottom: 56 + chromeFrame.bottom } + : undefined + } + onLayout={(event) => setMeasuredPageWidth(Math.max(1, event.nativeEvent.layout.width))} data={siblings} getItemLayout={(_, index) => ({ index, length: pageWidth, offset: pageWidth * index })} ref={pagerRef} horizontal initialScrollIndex={pageIndex} - keyExtractor={(item) => ( + keyExtractor={(item) => item.previewKind === 'pdf' ? item.key : `${item.key}:${recoveryEpoch}` - )} + } onMomentumScrollEnd={(event) => { const next = Math.round(event.nativeEvent.contentOffset.x / pageWidth); if (next !== pageIndex && next >= 0 && next < siblings.length) setPageIndex(next); @@ -732,7 +759,6 @@ function RemoteFilePreviewContent() { onDownload={() => void downloadAndShare(item)} htmlBrowserActions={{ meta: [ - `${index + 1} / ${siblings.length}`, ...(item.sizeBytes > 0 ? [formatByteSize(item.sizeBytes)] : []), ].join(' · '), busy: !!busyLabel, @@ -775,17 +801,52 @@ function RemoteFilePreviewContent() { // 超出视口的内容永远看不到。手势仲裁(区分内层平移与翻页)在 RN 上要自己写一套 // 竞态裁决,属独立改动;这里沿用本文件对 PDF 已经采用的同一口径 —— 想翻页就切到 // 「源码」态(源码是竖向列表,不冲突),或 Done 返回列表。 - scrollEnabled={!(current.previewKind === 'pdf' && PDF_PREVIEWS_INLINE) && htmlPanPageKey !== current.key && markdownPagerPageKey !== current.key} + scrollEnabled={!/\.(glb|gltf|mmd|mermaid|drawio|dio)$/i.test(current.relPath) && + !(current.previewKind === 'pdf' && PDF_PREVIEWS_INLINE) && htmlPanPageKey !== current.key && markdownPagerPageKey !== current.key} showsHorizontalScrollIndicator={false} windowSize={3} /> + {!lightboxUrl ? ( + item.name), + onSelect: (next) => { + setPageIndex(next); + pagerRef.current?.scrollToOffset({ + animated: false, + offset: next * pageWidth, + }); + }, + }} + /> + ) : null} {lightboxImages.length > 0 ? ( - item.name), + onSelect: (next) => { + setLightboxUrl(null); + setPageIndex(next); + pagerRef.current?.scrollToOffset({ + animated: false, + offset: next * pageWidth, + }); + }, + }} + > + setLightboxUrl(null)} - /> + onShareImage={() => downloadAndShare(current)} + onClose={() => { + setLightboxUrl(null); + goBackGuarded(router); + }} + /> + ) : null} ); @@ -842,7 +903,7 @@ function FilePreviewPage({ const insets = useSafeAreaInsets(); const chromeFrame = usePreviewChromeFrame(); const contentPadding = { - paddingTop: chromeFrame.contentTop, + paddingTop: insets.top + 64, paddingBottom: insets.bottom + spacing.xs, paddingLeft: insets.left, paddingRight: insets.right, @@ -852,6 +913,7 @@ function FilePreviewPage({ if (item.thumb === 'image') { content = ( ); - } else if (item.previewKind === 'pdf' && PDF_PREVIEWS_INLINE) { - content = ; - } else if (item.previewKind === 'pdf') { - // 安卓 WebView 不渲染 PDF:不取件、不挂 WebView,直接给「导出 / 分享」交系统阅读器。 - content = ; + } else if (item.previewKind === 'pdf' || + /\.(glb|gltf)$/i.test(item.relPath) + ) { + content = ( + + ); } else if (avKind) { - content = ; - } else if (item.thumb === 'doc') { + content = ( + + ); + } else if (item.thumb === 'doc' || + richTextKindOf(item.relPath) === "diagram" + ) { return ( ); } else { - content = ; + content = ( + + ); } return ( {content} - {visible ? : null} + /> + ) : null} ); } @@ -988,7 +1059,9 @@ function AvPreviewPage({ }, [active, exportToUrl, item.mtimeMs, item.relPath, item.sizeBytes, kind, requestEpoch, workdir]); if (failed) { - return ; + return ( + + ); } if (!url) { return ( @@ -1051,13 +1124,7 @@ function PdfPreviewPage({ workdir: string; }) { const insets = useSafeAreaInsets(); - const chromeFrame = usePreviewChromeFrame(); - const { viewRef, measure, viewportStyle, obscuredContentInsets } = useHtmlBrowserViewport({ - top: chromeFrame.contentTop, - bottom: insets.bottom + spacing.xs, - left: insets.left, - right: insets.right, - }); + const model = /\.(glb|gltf)$/i.test(item.relPath); const styles = useThemedStyles(makeStyles); const { colors } = useTheme(); const { t } = useTranslation(); @@ -1090,7 +1157,26 @@ function PdfPreviewPage({ let cancelled = false; setFailure(null); void exportToUrl(item.relPath, item.mtimeMs, { stream: false }) - .then(preparePdfPreviewSource) + .then(async (url) => { + if (!model) return { uri: url, release() {} }; + const uri = await downloadRemoteMediaShareTemp( + url, + shareMimeForFileName(item.name), + item.name, + ); + if (!uri) throw new Error("PREVIEW_DOWNLOAD_FAILED"); + return { + uri, + release() { + try { + const file = new File(uri); + if (file.exists) file.delete(); + } catch { + /* cache reclamation */ + } + }, + }; + }) .then((source) => { if (cancelled) { source.release(); return; } releaseSourceRef.current?.(); @@ -1106,12 +1192,17 @@ function PdfPreviewPage({ return () => { cancelled = true; }; - }, [active, exportToUrl, item.mtimeMs, item.relPath, requestEpoch, workdir]); + }, [active, exportToUrl, item.mtimeMs, item.relPath, requestEpoch, workdir, + model, + item.name, + ]); const pdfSource = useMemo(() => (url ? { uri: url } : null), [url]); if (failure) { - return { requestedRef.current = false; setRequestEpoch((epoch) => epoch + 1); }} />; + return ( + { requestedRef.current = false; setRequestEpoch((epoch) => epoch + 1); }} /> + ); } if (!pdfSource) { return ( @@ -1121,14 +1212,20 @@ function PdfPreviewPage({ ); } - return - - ; + return ( + + <> + {model ? ( + + ) : ( + + )} + + + ); } /** @@ -1185,7 +1282,7 @@ function TextPreviewPage({ const chromeFrame = usePreviewChromeFrame(); const browserTop = chromeFrame.top; const browserBottom = chromeFrame.bottom; - const browserContentInset = { top: chromeFrame.contentTop, bottom: browserBottom + (richKind === 'html' ? HTML_BROWSER_CONTROL_SIZE : 0) + spacing.xs }; + const browserContentInset = { top: richKind === "html" ? chromeFrame.contentTop : insets.top + 64, bottom: browserBottom + (richKind === 'html' ? HTML_BROWSER_CONTROL_SIZE : 0) + spacing.xs }; const browserRef = useRef(null); const [website, setWebsite] = useState(null); const [browserNavigation, setBrowserNavigation] = useState<{ @@ -1222,8 +1319,7 @@ function TextPreviewPage({ loadedRef.current = true; let cancelled = false; const abort = new AbortController(); - const unavailable = (value: TextPreviewState) => setState((previous) => - previous.status === 'ready' ? previous : value); + const unavailable = (value: TextPreviewState) => setState((previous) => (previous.status === 'ready' ? previous : value)); void readTextFile(item.relPath, abort.signal) .then((res: FileBrowserReadFileResult) => { if (cancelled) return; @@ -1305,12 +1401,16 @@ function TextPreviewPage({ return () => onMarkdownPagerChange?.(item.key, false); }, [item.key, markdownPagerWanted, onMarkdownPagerChange]); - if (website) return visible ? { setBrowserNavigation(null); setWebsite(null); }} - /> : ; + /> + ) : ( + + ); const retryText = () => { loadedRef.current = false; @@ -1362,7 +1462,20 @@ function TextPreviewPage({ }} /> ) - ) : ( + ) : richKind === "diagram" ? ( + + + + ) : ( ) : ( - String(index)} - onLayout={() => { - // 内容搜索进入:列表就绪后跳到命中行(行高不定,先 scrollToIndex, - // 超出渲染窗时由 onScrollToIndexFailed 按估算行高兜底再补跳)。 - if (targetIndex === null || scrolledToTargetRef.current) return; - scrolledToTargetRef.current = true; - setTimeout(() => { - codeListRef.current?.scrollToIndex({ animated: false, index: targetIndex, viewPosition: 0.3 }); - }, 60); - }} - onScrollToIndexFailed={(info) => { - codeListRef.current?.scrollToOffset({ animated: false, offset: info.averageItemLength * info.index }); - setTimeout(() => { - codeListRef.current?.scrollToIndex({ animated: false, index: info.index, viewPosition: 0.3 }); - }, 220); - }} - ref={codeListRef} - testID={visible ? 'filePreview.sourceReady' : undefined} - renderItem={({ item: line, index }) => ( - - {String(index + 1).padStart(lineNumWidth, ' ')} - {line.length > 0 ? line : ' '} + + - )} - style={styles.codeList} - /> )} - {richKind !== 'html' && visible ? setRichView(value => value === 'rendered' ? 'source' : 'rendered') : undefined} - /> : null} + onToggleSource={canRenderRich ? () => setRichView((value) => value === 'rendered' ? 'source' : 'rendered') : undefined} + /> + ) : null} {richKind === 'html' && visible ? ( ; item: FileBrowserGridItem; maker: Pick; @@ -1524,7 +1617,12 @@ function ImagePreviewPage({ return () => { cancelled = true; }; - }, [active, attempt, exportToUrl, item.mtimeMs, item.name, item.relPath, workdir]); + }, [active, attempt, exportToUrl, item.mtimeMs, item.name, item.relPath, workdir, + ]); + + useEffect(() => { + if (visible && fullUrl) onOpenLightbox(fullUrl); + }, [visible, fullUrl, onOpenLightbox]); const displayUri = fullUrl ?? thumbUri; return ( @@ -1587,7 +1685,10 @@ function UnsupportedPage({ {item.name} {item.metaLabel} {reason} - {onRetry ? : + ) : ( + [styles.ctaBtn, pressed && styles.pressed]} @@ -1595,7 +1696,8 @@ function UnsupportedPage({ > {t('files.preview.exportShare')} - } + + )} ); } @@ -1615,9 +1717,11 @@ function GenericGlyph({ name }: { name: string }) { function previewSiblings(entries: FileBrowserRemoteOpEntry[], sort: FileBrowserSortMode, initialRelPath: string, anchorRelPath = initialRelPath): FileBrowserGridItem[] { const files = buildFileBrowserGridItems(entries, sort, Date.now()) - .filter(item => item.kind === 'file') - .filter(item => item.thumb !== 'image' || item.relPath === initialRelPath); - return files.some(item => item.relPath === anchorRelPath || item.relPath === initialRelPath) ? files : [fallbackItem(initialRelPath)]; + .filter((item) => item.kind === 'file') + .filter( + (item) => item.thumb !== 'image' || item.relPath === initialRelPath); + return files.some( + (item) => item.relPath === anchorRelPath || item.relPath === initialRelPath) ? files : [fallbackItem(initialRelPath)]; } function fallbackItem(relPath: string): FileBrowserGridItem { diff --git a/apps/mobile/app/sessions/[sessionId].tsx b/apps/mobile/app/sessions/[sessionId].tsx index 3580cc7864b..97e90fe9017 100644 --- a/apps/mobile/app/sessions/[sessionId].tsx +++ b/apps/mobile/app/sessions/[sessionId].tsx @@ -1,3 +1,4 @@ +import { ComposerAttachmentPreview } from "@/session/ComposerAttachmentPreview"; import { snapshotRichAnswerValues } from '@/session/RichAnswer'; import { PluginUiEnablePrompt } from '@/plugins/PluginUiEnablePrompt'; import { deviceDisplayName } from '@/device-link/deviceName'; @@ -885,7 +886,9 @@ const SESSION_READ_ACK_DWELL_MS = 200; /** 旧被控端没有 update-content 通道时的降级判定(与 mobileVoiceInput 同款字符串匹配)。 */ function isChannelNotAllowedError(err: unknown): boolean { const formatted = formatRemoteError(err); - return formatted.includes('CHANNEL_NOT_ALLOWED') || formatted.includes('DEVICE_LINK_CHANNEL_NOT_ALLOWED'); + return ( + formatted.includes('CHANNEL_NOT_ALLOWED') || formatted.includes('DEVICE_LINK_CHANNEL_NOT_ALLOWED') + ); } /** @@ -908,8 +911,10 @@ function isRetryableEnqueueTransportError(err: unknown): boolean { /** 引用 capability 查询是只读前置步骤,离线后可安全等待连接恢复再做。 */ function isAutoRecoveringSessionReferencePreparationError(err: unknown): boolean { - return isAutoRecoveringRemoteError(err) - || (err instanceof MobileSessionReferenceError && err.code === 'SESSION_REFERENCE_OFFLINE'); + return ( + isAutoRecoveringRemoteError(err) + || (err instanceof MobileSessionReferenceError && err.code === 'SESSION_REFERENCE_OFFLINE') + ); } /** enqueue 对可安全重发的瞬时传输错误做有界退避。 */ @@ -970,7 +975,9 @@ function attachmentIdSetsEqual( ): boolean { const idsA = (a ?? []).map((item) => item.id).sort(); const idsB = (b ?? []).map((item) => item.id).sort(); - return idsA.length === idsB.length && idsA.every((id, index) => id === idsB[index]); + return ( + idsA.length === idsB.length && idsA.every((id, index) => id === idsB[index]) + ); } /** @@ -1045,14 +1052,18 @@ function composerDraftScopeKey(sessionId: string, routeDraft: string | null): st } export default function SessionScreen() { - return {chrome => }; + return ( + {(chrome) => } + ); } function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: SessionTaskChrome) { const styles = useThemedStyles(makeStyles); const composerPullThrough = useComposerPullThroughBridge(); const renderMessageScroll = useCallback( - (props: ScrollViewProps) => , + (props: ScrollViewProps) => ( + + ), [composerPullThrough], ); const { colors, mode } = useTheme(); @@ -1195,7 +1206,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S // Unknown presence while connecting is loading, not evidence of a lost computer. const showCachedHistoryNotice = shouldShowCachedHistoryNotice(status, getPresenceAvailability(deviceId)); const currentSession = useRemoteSessionDetail(deviceId, sessionId); - const sideChatParent = useRemoteSessionDetail(deviceId, currentSession?.source === 'side-chat' ? currentSession.parentSessionId ?? '' : ''); + const sideChatParent = useRemoteSessionDetail(deviceId, currentSession?.source === 'side-chat' ? (currentSession.parentSessionId ?? '') + : ''); const sideChatBoundary = currentSession?.source === 'side-chat' ? Date.parse(currentSession.createdAt) : undefined; const unfilteredHistoryView = useRemoteHistoryView(deviceId, sessionId, maker, () => messageScreenFocusedRef.current && messageAppActiveRef.current, remoteHistoryAvailable); @@ -1207,7 +1219,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S }, [connectionEpoch, messageReloadRevision, historyView.view]); const inheritedMessages = useSessionMessages(sessionId, deviceId); const rawMessages = useMemo(() => sideChatBoundary === undefined ? inheritedMessages - : inheritedMessages.filter(message => Date.parse(message.createdAt) >= sideChatBoundary), [inheritedMessages, sideChatBoundary]); + : inheritedMessages.filter( + (message) => Date.parse(message.createdAt) >= sideChatBoundary), [inheritedMessages, sideChatBoundary]); const historyHandoff = useMemo(() => new HistoryViewHandoff( (row) => row.agentMeta?.isStreaming === true, ), [historyView.view]); @@ -1347,7 +1360,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S // limits,不改写为 null——改写会让 limitsTouched=true 显式提交「全部无限」覆盖 // 被控端默认;独立审核者 P2)。平时无参 → null,与旧行为一致(表单仍从 composer // 文字初始化)。 - const [goalRestore, setGoalRestore] = useState<{ sessionId: string; objective: string; limits?: MobileGoalLimitsInput } | null>(() => { + const [goalRestore, setGoalRestore] = useState<{ sessionId: string; objective: string; limits?: MobileGoalLimitsInput; + } | null>(() => { const objective = readRouteParam(params.goalObjective); if (!objective) return null; const limits = parseGoalLimitsRouteParam(readRouteParam(params.goalLimits)); @@ -1630,9 +1644,9 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S rememberSentMessageImagePreviews(sentImagePreviewsRef.current, sessionId, clientId, attachments, previewOf, galleryKeyOf, liveImagePreviewClientIdsRef.current); }, [sessionId]); - const getSentImagePreview = useCallback((clientId: string, imageIndex: number, name: string, attachmentId?: string, sha256?: string, sourceRef?: string) => ( - getSentMessageImagePreview(sentImagePreviewsRef.current, sessionId, clientId, imageIndex, name, attachmentId, sha256, sourceRef) - ), [sessionId]); + const getSentImagePreview = useCallback((clientId: string, imageIndex: number, name: string, attachmentId?: string, sha256?: string, sourceRef?: string) => + getSentMessageImagePreview(sentImagePreviewsRef.current, sessionId, clientId, imageIndex, name, attachmentId, sha256, sourceRef, + ), [sessionId]); // 「乐观气泡已上屏、enqueue RPC 尚未落定」的 clientId:这段窗口消息是否真的发出 // 还没有答案(弱网可达数秒,且失败会回滚摘除气泡),徽标必须是转圈而不是「排入 // 队尾」——后者是已确认入队的语义。成功 / 回滚 / 转失败任一落定即移除。 @@ -1897,8 +1911,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S const [readAckSyncedKey, setReadAckSyncedKey] = useState(null); const [contentSyncedKey, setContentSyncedKey] = useState(null); const subscriptionAck = deviceId && sessionId - ? getSubscriptionIdentity?.(deviceId, ['sessions', `session:${sessionId}`]) ?? null - : null; + ? (getSubscriptionIdentity?.(deviceId, ['sessions', `session:${sessionId}`]) ?? null) + : null; const contentRecoveryKey = subscriptionAck === null ? null : JSON.stringify([deviceId, sessionId, connectionEpoch, subscriptionAck]); const contentRecoveryKeyRef = useRef(contentRecoveryKey); @@ -1965,7 +1979,7 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S reserveBytes: PEER_FILE_DISK_RESERVE_BYTES, // Preserve manual image retention; only the new regenerable video copies // join pressure cleanup, with mounted players holding a protection lease. - canEvict: mimeType => mimeType.startsWith('video/'), + canEvict: (mimeType) => mimeType.startsWith('video/'), }); // 磁盘缓存源键加设备命名空间:缓存跨账号/设备存续,不同被控端可能产生相同的 // xdt-image:// url 字符串,裸 url 作键会把上一账号/设备的缓存文件当命中返回 @@ -2023,7 +2037,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S const bareDiskSource = diskCacheSourceOf(media.url); const diskSource = (media.thumbnail ? 'thumb\u0000' : '') + bareDiskSource; if (media.kind === 'image' && !opts?.skipCache && diskCache) { - const hit = await diskCache.lookup(diskSource).catch(() => null) + const hit = + (await diskCache.lookup(diskSource).catch(() => null)) ?? (media.thumbnail ? await diskCache.lookup(bareDiskSource).catch(() => null) : null); if (hit) { return { @@ -2142,7 +2157,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S () => ({ current: [] }), [activeComposerDraftScopeKey], ); - const sendButtonFrameRef = useRef<{ height: number; width: number; x: number; y: number } | null>(null); + const sendButtonFrameRef = useRef<{ height: number; width: number; x: number; y: number; + } | null>(null); const capabilitiesLoadSeqRef = useRef(0); const alternateCapabilitiesLoadSeqRef = useRef(0); const agentSwitchIntentLoadSeqRef = useRef(0); @@ -2292,7 +2308,7 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S // 用它——否则恢复后横幅消失了,composer 却仍被 stale 快照锁在不可用态, // 直到手动同步才解开。 const connectionError = resolveEffectiveConnectionError( - isDeviceAccessRevoked ? '[ACCESS_REVOKED] access revoked by target device' : syncError ?? error, + isDeviceAccessRevoked ? '[ACCESS_REVOKED] access revoked by target device' : (syncError ?? error), isDeviceUnresponsive, ); // dispatch / Stop 与恢复 edge 共用 Context 内随 connection epoch 重置的三态 verdict, @@ -2353,9 +2369,9 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S const hasRenderedMessages = historyView.snapshot.ready ? historyView.snapshot.items.length > 0 : messages.length > 0; const companionEntry = useRemoteResourceSession(deviceId, deviceName, sessionId, currentSession?.id === sessionId && hasRenderedMessages - && readAckSyncedKey === `${sessionId}:${connectionEpoch}` + && readAckSyncedKey === `${sessionId}:${connectionEpoch}` && // A pre-ACK snapshot may omit replies sent before the subscription took effect. - && contentRecoveryKey !== null && contentSyncedKey === contentRecoveryKey + contentRecoveryKey !== null && contentSyncedKey === contentRecoveryKey && !outboxRecoverySyncHeld && !loading, sessionMetadataSyncedKey === `${sessionId}:${connectionEpoch}` && (readRouteParam(params.resourceKind) !== 'bot' || currentSession?.source === 'bot')); @@ -2412,14 +2428,15 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S // while offers to continue on another computer. const cloudHome = useCloudCompanionHome(!!companionResource); const cloudHomeActor = cloudHome.supported && companionResource - ? cloudHome.actors.find(actor => actor.kind === 'bot' && actor.externalId === companionResource.ref.id && actor.homeDeviceId === deviceId) ?? null - : null; + ? (cloudHome.actors.find( + (actor) => actor.kind === 'bot' && actor.externalId === companionResource.ref.id && actor.homeDeviceId === deviceId) ?? null) + : null; const cloudHomeUnreachable = useSustained(!!cloudHomeActor && isMessageWaitingForCloudHome(durableOutboxRecords, { deviceId, sessionId }, outboxConnectionState)); const quietConnection = !!cloudHomeActor; const hasCurrentSession = currentSession !== null; const currentAgentKind = useMemo( - () => currentSession ? agentKindForSession(currentSession) : null, + () => (currentSession ? agentKindForSession(currentSession) : null), [currentSession?.agentKind, currentSession?.id], ); // —— 自动化 run「激活即已读」(对齐桌面端 CCAgentSidebarUpper):打开会话读完报告后, @@ -2429,7 +2446,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S // 同一轻量索引同时提供历史失败提示和未读记录;已读不会消除历史失败。 const scheduleNoticeSource = JSON.stringify([getActiveMobileSessionRealm(), auth.user?.id, deviceId, sessionId]); const sessionSourceForSchedule = currentSession?.source; - const [scheduleFailure, setScheduleFailure] = useState<{ source: string; run?: FailedScheduleRunSnapshot } | null>(null); + const [scheduleFailure, setScheduleFailure] = useState<{ source: string; run?: FailedScheduleRunSnapshot; + } | null>(null); // —— 会话未读「真实展示即已读」回执 —— // 手机端打开会话且**本次连接代已完成整窗同步**后,驻留满 dwell 把被控端该会话的 // 未读态(灵动岛 / Dock 角标 / 桌面侧栏红绿点)清掉;被控端清完经 sessions relay @@ -2660,9 +2678,10 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S const composerDisabledReason = companionEntry.readOnly ? t('session.presentation.overview.readOnly') : !companionEntry.canCompose ? t('shared.syncing') : composerDisabledReasonKey ? t(composerDisabledReasonKey) - : sessionOperationLayout.composerDisabledReason + : (sessionOperationLayout.composerDisabledReason ?? (sessionResourceCards.blocked - ? sessionResourceCards.blockedReason ?? t('shared.syncing') : null); + ? (sessionResourceCards.blockedReason ?? t('shared.syncing')) + : null)); // inline 队列操作可用性:旧队列弹层由 showQueue 整体隐藏(离线/被撤销、pending // interaction 等),inline 化后气泡必须留在消息流里,故改为保留渲染、按同一规则 // 禁用操作(取消/编辑/插话/重试/恢复),禁用理由沿用 composerDisabledReason。 @@ -2844,9 +2863,10 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S : usageAccount.kind === 'device' ? agentAccountMaker : null; /** 菜单上的来源与登录身份:Agent 在另一台电脑时按那台的目录,不借被控电脑的同名来源。 */ const menuAccountProvider = usageAccount.kind !== 'host' - ? (currentAgentCatalog.loading + ? currentAgentCatalog.loading ? undefined - : currentAgentCatalog.providers.find((provider) => provider.id === currentSession?.providerId)) + : currentAgentCatalog.providers.find((provider) => provider.id === currentSession?.providerId, + ) : accountProvider; const runtimeOptions = useMemo( () => currentSession ? buildSessionRuntimeOptions(currentSession, capabilities) : null, @@ -2975,18 +2995,13 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S : alternateCapabilitiesError; const modelSheetSelection = currentSession ? { - model: modelSheetIntent ? modelSheetIntent.model : ( - modelSheetAgentKind === sessionAgentKind ? currentSession.model : '' - ), - providerId: modelSheetIntent ? modelSheetIntent.providerId : ( - modelSheetAgentKind === sessionAgentKind ? currentSession.providerId ?? null : null - ), - effort: modelSheetIntent ? modelSheetIntent.effort ?? '' : ( - modelSheetAgentKind === sessionAgentKind ? currentSession.effort : '' - ), - fastMode: modelSheetIntent ? modelSheetIntent.fastMode ?? false : ( - modelSheetAgentKind === sessionAgentKind ? currentSession.fastMode : false - ), + model: modelSheetIntent ? modelSheetIntent.model : modelSheetAgentKind === sessionAgentKind ? currentSession.model : '', + providerId: modelSheetIntent ? modelSheetIntent.providerId : modelSheetAgentKind === sessionAgentKind ? (currentSession.providerId ?? null) + : null, + effort: modelSheetIntent ? (modelSheetIntent.effort ?? '') + : modelSheetAgentKind === sessionAgentKind ? currentSession.effort : '', + fastMode: modelSheetIntent ? (modelSheetIntent.fastMode ?? false) + : modelSheetAgentKind === sessionAgentKind ? currentSession.fastMode : false, } : null; const modelSheetRuntimeOptions = useMemo( @@ -2998,8 +3013,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S // composer 模型药丸(对齐桌面 trigger):当前来源官方 mark + 「模型 · effort」+ Fast 闪电。 const composerActiveSourceProvider = useMemo( () => composerModelSections - ? composerModelSections.connected.find((p) => p.id === composerModelSections.activeSourceId) ?? null - : null, + ? (composerModelSections.connected.find((p) => p.id === composerModelSections.activeSourceId) ?? null) + : null, [composerModelSections], ); // 会话持久化的显式来源可能在电脑端被断开。此时不能把 activeSourceId 的默认回退 @@ -3024,9 +3039,11 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S ]); const composerPillSourceProvider = useMemo(() => { if (!composerSelectedSourceDisconnected) return composerActiveSourceProvider; - return composerAgentCatalog.providers.find( + return ( + composerAgentCatalog.providers.find( (provider) => provider.id === composerDisplaySession?.providerId, - ) ?? null; + ) ?? null + ); }, [ composerActiveSourceProvider, composerAgentCatalog.providers, @@ -3034,8 +3051,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S composerDisplaySession?.providerId, ]); const composerPillSourceId = composerSelectedSourceDisconnected - ? composerDisplaySession?.providerId ?? null - : composerPillSourceProvider?.id ?? null; + ? (composerDisplaySession?.providerId ?? null) + : (composerPillSourceProvider?.id ?? null); const composerPillFastOn = agentSwitchIntent ? (agentSwitchIntent.fastMode ?? false) : !composerSelectedSourceDisconnected && rowFastEditable({ @@ -3059,7 +3076,7 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S // 下一条消息时 Agent 所在的另一台电脑名(药丸带远程标记,读屏一并读出);目录在缺名时以 deviceId // 兜底,那种情况不读出来。 const nextAgentDeviceName = nextAgentDeviceId - ? remoteAgentCatalogs.find((item) => item.deviceId === nextAgentDeviceId)?.name ?? null + ? (remoteAgentCatalogs.find((item) => item.deviceId === nextAgentDeviceId)?.name ?? null) : null; const composerRuntimeAccessibilityLabel = nextAgentDeviceName && nextAgentDeviceName !== nextAgentDeviceId ? [composerRuntimeAccessibilitySummary, nextAgentDeviceName].filter(Boolean).join(', ') @@ -3273,10 +3290,12 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S if (!composerSendTargetEnabledRef.current || !frame || !canUseComposer) return false; const { pageX, pageY } = event.nativeEvent; const pad = 10; - return pageX >= frame.x - pad + return ( + pageX >= frame.x - pad && pageX <= frame.x + frame.width + pad && pageY >= frame.y - pad - && pageY <= frame.y + frame.height + pad; + && pageY <= frame.y + frame.height + pad + ); }, [canUseComposer]); const updateVoiceReleaseToSendTarget = useCallback((event: GestureResponderEvent): boolean => { const active = voiceLongPressActiveRef.current && isPointInsideSendButton(event); @@ -3352,7 +3371,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S kind: readRouteParam(params.resourceKind) ?? '', id: readRouteParam(params.resourceId) ?? '', })], ready: composerDraftHydrated && canUseComposer && companionEntry.ready && !queueEditingRef.current && !messageEditingRef.current, - appendText: text => setComposerDraft(current => [current, text].filter(Boolean).join('\n\n')), + appendText: (text) => + setComposerDraft((current) => [current, text].filter(Boolean).join('\n\n')), attachmentCount: attachments.length, enqueueUploads, getPendingUploadCount, onError: setAttachmentError, }); @@ -3743,8 +3763,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S const targetAgentKind = modelSheetOpen && modelSheetAgentKind !== sessionAgentKind ? modelSheetAgentKind : agentSwitchIntent?.targetAgentKind !== sessionAgentKind - ? agentSwitchIntent?.targetAgentKind ?? null - : null; + ? (agentSwitchIntent?.targetAgentKind ?? null) + : null; if (!targetAgentKind) { alternateCapabilitiesLoadSeqRef.current += 1; setAlternateCapabilities(null); @@ -3883,10 +3903,9 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S sessionId, readAckEpochAtStart, ]); - const subscriptionRetryIsStale = () => ( - syncRun.isStale() - || sessionSubscriptionIdentityRef.current !== subscriptionIdentityAtStart - ); + const subscriptionRetryIsStale = () => + syncRun.isStale() + || sessionSubscriptionIdentityRef.current !== subscriptionIdentityAtStart; const syncReopenCoordinator = createRemoteSyncReopenCoordinator(async () => { await reopenLink(deviceId); }); @@ -3895,11 +3914,10 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S const readAckGateGenAtStart = readAckGateGenRef.current; const fetchSessionMetadata = () => runConnectionScopedSessionMetadataRead( () => maker.getSession(sessionId), - () => ( - !subscriptionRetryIsStale() + () => + !subscriptionRetryIsStale() && messageAuthorityCurrent() - && readAckGateGenRef.current === readAckGateGenAtStart - ), + && readAckGateGenRef.current === readAckGateGenAtStart, (sessionMeta) => { // A roster link only selects the initial page. Preserve the resolver's // task/host checks before publishing authoritative entry metadata. @@ -4259,11 +4277,11 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S focused: creationScreenFocused, sending, connectionEpoch, - onReady: params => router.replace({ pathname: '/sessions/new', params: { + onReady: (params) => router.replace({ pathname: '/sessions/new', params: { ...params, creationError: creationTask?.error ?? t('session.screen.createFailedDefault'), } }), - onError: err => setError(t('session.screen.createFailedNotice', { message: formatRemoteError(err) })), + onError: (err) => setError(t('session.screen.createFailedNotice', { message: formatRemoteError(err) })), beforeNavigate: async () => { const owner = getMobileAuthOwner(); if (outboxSessionAliveRef.current !== sessionId) return; @@ -4281,7 +4299,7 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S if (!isMobileAuthOwnerCurrent(owner) || outboxSessionAliveRef.current !== sessionId) return; // Ownership changed only after the save succeeded. Unmount cleanup must // not delete attachments now owned by the recovered new-task draft. - releaseUploadedSources(files.map(file => file.id)); + releaseUploadedSources(files.map((file) => file.id)); attachmentsRef.current = []; setAttachments([]); replaceComposerDraft(''); @@ -4676,7 +4694,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S // 新建任务的创建管线也走这一行:慢的时候同一行显示阶段,完成后原地接上「思考中」。 const creationRunning = creationTask?.status === 'running'; const creationStartedAt = creationRunning ? creationTask.startedAt : null; - const creationSlowPhase = useSlowSendVisible(creationStartedAt) ? creationTask?.phase ?? null : null; + const creationSlowPhase = useSlowSendVisible(creationStartedAt) ? (creationTask?.phase ?? null) + : null; const composerActivitySignal = isSessionStreaming || messageHandoffActive || creationRunning; // 活动条信号去抖:composerActivitySignal 由多个来源(sending / 消息交接 / canStopQueue / // remoteSessionRunning / currentTurnStreaming)拼成,它们交接时会漏出一两帧空隙 @@ -4715,7 +4734,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S // 新建任务这一轮从新建页点发送时起算:进任务页、电脑开始运行(远端 startedAt 晚于点击) // 都不归零,直到活动行熄灭(本轮结束)才放开。按会话绑定:侧栏原地切到别的任务即丢弃, // 切回来不会拿旧创建起点续算(同一 SessionScreen 实例复用,review P2)。 - const [creationTurnStart, setCreationTurnStart] = useState<{ sessionId: string; startedAt: number } | null>(null); + const [creationTurnStart, setCreationTurnStart] = useState<{ sessionId: string; startedAt: number; + } | null>(null); useEffect(() => { if (creationStartedAt !== null && sessionId) setCreationTurnStart({ sessionId, startedAt: creationStartedAt }); else setCreationTurnStart((prev) => (prev?.sessionId === sessionId && showComposerActivity ? prev : null)); @@ -4724,15 +4744,14 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S const composerActivityStartedAtMs = creationTurnStartedAt ?? remoteSessionRunStatus.startedAt ?? composerActivityStartedAt; const composerActivityTokenUsage = remoteSessionRunStatus.tokenUsage; const forkOrigin = useMemo( - () => ( + () => currentSession?.source !== 'side-chat' && currentSession?.parentSessionId && currentSession.forkedAtMessageId ? { parentSessionId: currentSession.parentSessionId, forkedAtMessageId: currentSession.forkedAtMessageId, forkedSessionCreatedAt: currentSession.createdAt, } - : null - ), + : null, [currentSession?.source, currentSession?.createdAt, currentSession?.forkedAtMessageId, currentSession?.parentSessionId], ); // inline 排队区去重集:已回流进消息流的 clientId 不再渲染排队气泡(排队气泡消失的 @@ -5021,7 +5040,7 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S : [], [renderItems, searchOpen, searchQuery], ); - const activeSearchHit = activeSearchIndex >= 0 ? searchHits[activeSearchIndex] ?? null : null; + const activeSearchHit = activeSearchIndex >= 0 ? (searchHits[activeSearchIndex] ?? null) : null; const routeFocusedItemKey = useMemo( () => findMobileRenderItemKeyByClientId(renderItems, routeFocusedClientId), [renderItems, routeFocusedClientId], @@ -5053,7 +5072,9 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S return; } - setRouteFocusedClientId((current) => (current === routeFocusClientId ? current : null)); + setRouteFocusedClientId((current) => + current === routeFocusClientId ? current : null, + ); if (loadedRouteFocusKeyRef.current === routeFocusKey) return; loadedRouteFocusKeyRef.current = routeFocusKey; @@ -5167,7 +5188,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S // 全屏查看器的分享:确保拿到本地 file://(磁盘缓存命中或先落盘)再唤起系统分享单。 // 分享失败静默提示——旧 dev client 未包含 expo-sharing 原生模块时也走这条兜底。 const shareLightboxImage = useCallback(async ( - media: { kind: 'image' | 'video' | 'audio'; url: string; previewable: boolean }, + media: { kind: 'image' | 'video' | 'audio'; url: string; previewable: boolean; + }, displayUri: string, mimeType?: string, sizeBytes?: number, @@ -5527,7 +5549,7 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S } finally { sideChatCreatingRef.current = false; } }, [canUseSideChats, currentSession, deviceId, deviceName, maker, sessionId, t]); const askInSideChat = useCallback((text?: string) => { - void createSideChat(text).then(child => { + void createSideChat(text).then((child) => { if (child && messageScreenFocusedRef.current) openSideChat(child); }); }, [createSideChat, openSideChat]); @@ -5728,7 +5750,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S connectionProvider: (providerId: string) => voiceContext.createAsrConnection(providerId), refinerTargetProvider: (providerId: string, options?: { refreshAccessToken?: boolean }) => voiceContext.createRefinerTarget(providerId, options), - warmRefiner: (input: { system: string; user: unknown; promptCacheKey: string }) => + warmRefiner: (input: { system: string; user: unknown; promptCacheKey: string; + }) => voiceContext.warmRefiner(input), initialDraft, initialSelection, @@ -6080,14 +6103,15 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S return; } pendingBottomOverlayHeightRef.current = null; - setBottomOverlayContentHeight((currentHeight) => ( + setBottomOverlayContentHeight((currentHeight) => Math.abs(currentHeight - nextHeight) > 1 ? nextHeight : currentHeight - )); + ); }, []); const handleComposerInputTopLayout = useCallback((event: LayoutChangeEvent) => { const nextTop = Math.floor(event.nativeEvent.layout.y); - setComposerInputTop((currentTop) => (Math.abs(currentTop - nextTop) > 1 ? nextTop : currentTop)); + setComposerInputTop((currentTop) => + Math.abs(currentTop - nextTop) > 1 ? nextTop : currentTop); }, []); const handleComposerDragActiveChange = useCallback((active: boolean) => { @@ -6103,9 +6127,9 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S // (滚到历史最顶端时第一条消息不被工具栏盖住),与 bottomOverlayHeight 同款模式。 const handleTopOverlayLayout = useCallback((event: LayoutChangeEvent) => { const nextHeight = Math.ceil(event.nativeEvent.layout.height); - setTopOverlayHeight((currentHeight) => ( + setTopOverlayHeight((currentHeight) => Math.abs(currentHeight - nextHeight) > 1 ? nextHeight : currentHeight - )); + ); }, []); @@ -6134,8 +6158,10 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S const outboxConnectionBlockedNow = () => shouldHoldOutboxDispatchForConnection(outboxConnectionStateRef.current); const outboxDispatchBlockedNow = () => { const row = readSessionRowNow(); - return outboxConnectionBlockedNow() || isRemoteSessionMissing(row) || row?.cacheSeeded === true - || getNewSessionCreationTask(sessionId) !== null; + return ( + outboxConnectionBlockedNow() || isRemoteSessionMissing(row) || row?.cacheSeeded === true + || getNewSessionCreationTask(sessionId) !== null + ); }; const { findDurableOutboxRecord, retryOutboxItem, removeOutboxItem } = usePendingOutboxActions({ deviceId, deviceName, sessionId, router, outboxSessionAliveRef, @@ -6256,8 +6282,9 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S }, () => { void loadEarlierMessages(); }); // Task links and notifications carry no teammate resource; the cached roster row supplies name and avatar. const companionDisplay = useCompanionDisplayResource(deviceId, sessionId, companionResource, companionChat); - const [companionSettingsRequest, setCompanionSettingsRequest] = useState<{ sessionId: string; page: 'memory' | 'capabilities'; sequence: number }>(); - const openCompanionSettings = useCallback((page: 'memory' | 'capabilities') => setCompanionSettingsRequest(previous => ({ sessionId, page, sequence: (previous?.sequence ?? 0) + 1 })), [sessionId]); + const [companionSettingsRequest, setCompanionSettingsRequest] = useState<{ sessionId: string; page: 'memory' | 'capabilities'; sequence: number; + }>(); + const openCompanionSettings = useCallback((page: 'memory' | 'capabilities') => setCompanionSettingsRequest((previous) => ({ sessionId, page, sequence: (previous?.sequence ?? 0) + 1 })), [sessionId]); const routeCompanionSettings = readRouteParam(params.companionSettings); const openedCompanionSettings = useRef(''); useEffect(() => { @@ -6277,8 +6304,10 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S // One portrait for replies and the composer status. Keyed by the avatar's fields, not the // resource object, so unrelated resource refreshes do not re-render every reply row. const companionPortrait = useCallback((size: number) => companionAvatarData - ? : undefined, + ? ( + + ) : undefined, [companionAvatarData?.kind, companionAvatarData?.value, companionAvatarData?.color, companionAvatarData?.fallbackText, companionName, companionOnline, deviceId]); const companionReplyAvatar = useMemo(() => companionChat ? companionPortrait(COMPANION_AVATAR_SIZE) : undefined, @@ -6287,13 +6316,16 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S const companionThreads = useCompanionThreads({ deviceId, deviceName, sessionId, botId: companionDisplay?.ref.id ?? '', avatar: companionThreadAvatar, enabled: companionChat && !companionEntry.readOnly && !companionEntry.threadRootSessionId, controlsReady: companionEntry.ready }); - const companionThreadRoot = companionEntry.threadRootSessionId ? : undefined; + companionName={companionName} enabled={companionEntry.ready} /> + ) : undefined; const showCompanionReplyTargets = useCompanionReplyTargetPreference(); const pendingCompanionReplyQuote = usePendingCompanionReplyQuote(companionChat ? sessionId : null); const companionQuoteNames = useMemo(() => ({ owner: t('message.renderer.you'), companion: companionName }), [companionName, t]); - const companionQuoteOptions = useMemo(() => (companionChat && sessionId ? { + const companionQuoteOptions = useMemo(() => + companionChat && sessionId ? { names: companionQuoteNames, showReplyTargets: showCompanionReplyTargets, onQuote: (quote) => { @@ -6301,7 +6333,7 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S requestAnimationFrame(() => composerInputRef.current?.focus()); }, onLocate: (clientId) => router.setParams({ focusClientId: clientId, focusRequestKey: String(Date.now()) }), - } : undefined), [companionChat, companionQuoteNames, router, sessionId, showCompanionReplyTargets]); + } : undefined, [companionChat, companionQuoteNames, router, sessionId, showCompanionReplyTargets]); const pendingCompanionQuoteOriginal = useMemo(() => { const clientId = pendingCompanionReplyQuote?.clientId; if (!clientId) return undefined; @@ -6321,13 +6353,14 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S : activePendingKind === 'plan_review' ? 'planReview' : 'attention'}`) : companionWorkingLabel; // Hiding progress can change visible rows without changing the underlying stream structure. - const companionItemsStructureKey = companionChat ? messageListItems.map(item => item.key).join('\u0000') : null; + const companionItemsStructureKey = companionChat ? messageListItems.map((item) => item.key).join('\u0000') : null; const messageListStructureKey = useMemo( () => ({}), [pendingSendItems, renderItemsStructureKey, optimisticClientIds, companionItemsStructureKey], ); const shareExpandableBlockIds = useMemo( - () => (shareSelectionActive ? collectConversationShareBlockIds(messageListItems) : []), + () => + shareSelectionActive ? collectConversationShareBlockIds(messageListItems) : [], [messageListItems, shareSelectionActive], ); const shareExpansionSnapshot = useFoldableExpandedBlocksSnapshot(shareExpandableBlockIds); @@ -6560,10 +6593,11 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S if (!flushScopeAlive()) return; } const commandsAtSend = slashCommandsRef.current; - const documentAtSend = independent ? textComposerDocument(options.independentText!) : options.documentOverride + const documentAtSend = independent ? textComposerDocument(options.independentText!) : (options.documentOverride ?? (options.draftOverride === undefined - ? visibleDocument ?? composerDocumentRef.current - : reconcileComposerProjectedText(composerDocumentRef.current, options.draftOverride)); + ? (visibleDocument ?? composerDocumentRef.current) + : reconcileComposerProjectedText(composerDocumentRef.current, options.draftOverride, + ))); const visibleDraft = composerDocumentProjectedText(documentAtSend); const body = visibleDraft.trim(); const serializedAtSend = serializeComposerDocument(documentAtSend); @@ -6576,7 +6610,7 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S const quotesEncodedAtSend = queueEditSubmission?.quotesEncoded ?? serializedAtSend.quotesEncoded; let agentReferencesAtSend = queueEditSubmission?.agentReferences ?? serializedAtSend.agentReferences; const pastedTextRangesAtSend = queueEditSubmission?.pastedTextRanges ?? serializedAtSend.pastedTextRanges; - const slashCommandRangesAtSend = independent ? [] : queueEditSubmission?.slashCommandRanges ?? serializedAtSend.slashCommandRanges; + const slashCommandRangesAtSend = independent ? [] : (queueEditSubmission?.slashCommandRanges ?? serializedAtSend.slashCommandRanges); if (!canUseComposer) { if (options.documentOverride) applyComposerDocument(options.documentOverride); else if (options.draftOverride !== undefined) setComposerDraft(options.draftOverride); @@ -6601,7 +6635,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S if (!connectionDispatchBlockedAtSend) setError(null); const outboxEligible = !queueEditAtSendStart; // 伙伴私聊的待发引用随这条消息写进信封;送达交接后才撤下,失败时保留以便重发。 - const replyQuoteAtSend = outboxEligible && !independent ? getPendingCompanionReplyQuote(sessionId) ?? undefined : undefined; + const replyQuoteAtSend = outboxEligible && !independent ? (getPendingCompanionReplyQuote(sessionId) ?? undefined) + : undefined; const uploadsInFlight = outboxEligible && !independent ? getPendingUploadCount() : 0; const willHaveAttachments = !independent && (attachmentsRef.current.length > 0 || uploadsInFlight > 0); // 编辑重发的是已捕获的原文,不再按命令解释(/side、/goal 等会改道并清空输入框);与桌面 @@ -6880,7 +6915,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S ...(replyQuoteAtSend ? { replyQuote: replyQuoteAtSend } : {}), permissionModeAtSend, planModeAtSend, readyAttachments, readyPreviews, attachmentOrder: incomingShareSlotOrder([ - ...readyAttachments.map(attachment => getUploadedSource(attachment.id)?.incomingShareOrder), + ...readyAttachments.map( + (attachment) => getUploadedSource(attachment.id)?.incomingShareOrder), ...pendingSources.map(({ source }) => source.incomingShareOrder), ]), claimedUploads: pendingSources.map(({ localId, source }) => ({ localId, failed: false, kind: source.kind, previewUri: source.uri })), @@ -6897,7 +6933,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S const source = getUploadedSource(readyAttachments[slot].id); if (!source && !outboxAttachmentNeedsLocalBytes(readyAttachments[slot])) continue; if (!source) throw new Error(t('session.screen.attachmentsNotCarriedBack', { count: 1 })); - record.uploads.push(await retainOutboxFile(record, item.attachmentSlots.findIndex(attachment => attachment?.id === readyAttachments[slot].id), source)); + record.uploads.push(await retainOutboxFile(record, item.attachmentSlots.findIndex( + (attachment) => attachment?.id === readyAttachments[slot].id), source)); } for (const { localId, source } of pendingSources) { record.uploads.push(await retainOutboxFile(record, item.slotByLocalId[localId]!, source)); @@ -6925,7 +6962,7 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S rememberSentAttachmentPreviews(record.item.clientId, readyAttachments, (attachment) => { const slot = item.attachmentSlots.findIndex((a) => a?.id === attachment.id); const upload = record?.uploads.find((u) => u.slot === slot); - return record && upload ? durableOutboxUploadUri(record, upload) : readyPreviews[readyAttachments.findIndex(a => a.id === attachment.id)] ?? null; + return record && upload ? durableOutboxUploadUri(record, upload) : (readyPreviews[readyAttachments.findIndex((a) => a.id === attachment.id)] ?? null); }, (attachment) => { const slot = item.attachmentSlots.findIndex((entry) => entry?.id === attachment.id); return slot >= 0 ? `${item.clientId}-slot-${slot}` : undefined; @@ -7549,7 +7586,9 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S testID="session.composerModelButton" /> ) : null} - {Platform.OS === 'ios' && nativeComposerAvailable && (composerDocumentRef.current.nodes.reduce((count, node) => count + (node.type === 'text' ? node.text.length : 0), 0) > 300 || composerDocumentRef.current.nodes.some((node) => node.type === 'text' && node.text.split('\n').length > 4)) ? composerInputRef.current?.expand?.()} /> : null} + {Platform.OS === 'ios' && nativeComposerAvailable && (composerDocumentRef.current.nodes.reduce((count, node) => count + (node.type === 'text' ? node.text.length : 0), 0) > 300 || composerDocumentRef.current.nodes.some((node) => node.type === 'text' && node.text.split('\n').length > 4)) ? ( + composerInputRef.current?.expand?.()} /> + ) : null} {/* 工具排右段顺序:[停止任务][语音占位][发送槽]。停止任务在语音左边(对齐桌面), @@ -7557,8 +7596,9 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S 的邻接关系全程不变。模型在 spacer 左侧,不随右段显隐横向跳动。 */} {renderComposerInlineStop()} {composerVoicePlacement?.inline || composerVoicePlacement?.floating - ? - : null} + ? ( + + ) : null} {renderComposerSendSlot()} ); @@ -7691,7 +7731,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S ); // 收起态附件徽标(leading 仅在非 card 态渲染);点击聚焦输入框展开完整托盘。 - const renderComposerCollapsedAttachmentBadge = () => (attachments.length > 0 || pendingUploads.length > 0 || pastePlaceholderCount > 0 ? ( + const renderComposerCollapsedAttachmentBadge = () => + attachments.length > 0 || pendingUploads.length > 0 || pastePlaceholderCount > 0 ? ( composerInputRef.current?.focus()} @@ -7700,7 +7741,7 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S previews={attachmentPreviews} testID="session.attachmentCollapsedBadge" /> - ) : null); + ) : null; // 收起态把附件 + 号放在输入框左侧,避免用户必须先聚焦才能打开附件面板; // 卡片态仍由 renderComposerToolbar() 渲染同一入口。 @@ -7816,8 +7857,9 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S <> {renderComposerInlineStop()} {composerShowInlineStop && composerVoicePlacement?.floating - ? - : null} + ? ( + + ) : null} {renderComposerSendSlot()} ); @@ -8495,7 +8537,9 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S ); } catch (err) { if (state.kind === 'error-tail') { - setRetryHiddenTailClientId((current) => (current === state.clientId ? null : current)); + setRetryHiddenTailClientId((current) => + current === state.clientId ? null : current, + ); } else { setTailInterruptAcked(false); } @@ -8719,10 +8763,10 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S // 本就与 plan 正交,直接展示;仅老被控端 permissionMode='plan' 时替换为进入前的底层权限档 // (无记录时回退首个非 plan 档),激活态由 composer 的 PlanModeChip 表达。 const displayPermissionMode = legacyPlanModeOn - ? ((prePlanPermissionModeRef.current && prePlanPermissionModeRef.current !== 'plan') + ? prePlanPermissionModeRef.current && prePlanPermissionModeRef.current !== 'plan' ? prePlanPermissionModeRef.current - : runtimeOptions?.permissionOptions.find((option) => option.id !== 'plan')?.id ?? 'ask') - : currentSession?.permissionMode ?? 'ask'; + : (runtimeOptions?.permissionOptions.find((option) => option.id !== 'plan')?.id ?? 'ask') + : (currentSession?.permissionMode ?? 'ask'); // 权限模式独立浮窗(2026-07-29 用户裁决,对齐 Codex 与新建页):composer 左侧图标钮 // 点开;选择走既有 confirmFullAccessChange + maker:set-permission-mode 链路。 const displayPermissionLabel = @@ -8731,7 +8775,7 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S const selectSessionPermissionMode = useCallback((mode: string) => { void (async () => { if (!currentSession) return; - if (!await confirmFullAccessChange(currentSession.permissionMode, mode)) return; + if (!(await confirmFullAccessChange(currentSession.permissionMode, mode))) return; await runControlAction( () => maker.setPermissionMode(sessionId, mode), { permissionMode: mode }, @@ -8816,9 +8860,6 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S // 依赖具体的稳定回调而非整个 hook 对象:烧录 host 挂载/卸载不应重建图集 // (images 引用变化会重置 lightbox 里正在画的笔迹,review P1)。 }, [attachments, attachmentPreviews, composerAnnotations.trayImageSourceUri]); - const composerPreviewUrl = composerPreviewAttachmentId - ? (composerGalleryImages.find((image) => image.key === composerPreviewAttachmentId)?.url ?? null) - : null; // 每次打开允许一次添加;同步门防止收起动画期间连续点按重复入队。 useEffect(() => { if (contextSheetOpen) mediaTapAcceptedRef.current = false; @@ -9139,8 +9180,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S // (同 id 模型两台电脑的档位 / Fast 可以不同)。 const catalogDeviceId = source?.deviceId ?? null; const catalogProviders = catalogDeviceId - ? remoteAgentCatalogs.find((item) => item.deviceId === catalogDeviceId)?.providers ?? [] - : composerDeviceProviders.providers; + ? (remoteAgentCatalogs.find((item) => item.deviceId === catalogDeviceId)?.providers ?? []) + : composerDeviceProviders.providers; const capability = resolveAgentCapability(catalogProviders, config.providerId, config.modelId, config.agent); if (!capability) return false; const targetCapabilities = normalizeMobileAgentCapabilities( @@ -9164,7 +9205,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S if (config.agent !== sessionAgentKind) { if (!sessionAgentSwitchSupported) return false; // 换电脑与换引擎是同一份交接风险:换电脑那一步已经确认过,不再叠一层换引擎确认。 - if (!relocates && agentSwitchIntent?.targetAgentKind !== config.agent && !await confirmMobileSessionAgentSwitch(config.agent, !!agentSwitchIntent)) return false; + if (!relocates && agentSwitchIntent?.targetAgentKind !== config.agent && !(await confirmMobileSessionAgentSwitch(config.agent, !!agentSwitchIntent, + ))) return false; const applied = await writeSessionAgentSwitchIntent({targetAgentKind:config.agent,model:config.modelId, providerId:config.providerId,effort:config.effort,fastMode:config.fast,...location}); if (applied) setModelSheetAgentKind(config.agent); @@ -9571,7 +9613,9 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S const sharing = await import('expo-sharing'); await sharing.shareAsync(localUri, { mimeType: mime }); // 只关本次分享对应的菜单:分享期间用户可能已关闭并长按另一 chip 打开新菜单 - setChipMenuTarget((prev) => (prev?.absPath === target.absPath ? null : prev)); + setChipMenuTarget((prev) => + prev?.absPath === target.absPath ? null : prev, + ); } catch (err) { Alert.alert(t('session.screen.shareFailedTitle'), formatRemoteError(err)); } finally { @@ -9658,7 +9702,9 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S if (!isCurrent()) return; return patchSessionFromList(session, patch, async (isLatest) => { const canConfirm = () => isCurrent() && isLatest(); - return canConfirm() && (await (prepare?.(canConfirm) ?? true)) && canConfirm(); + return ( + canConfirm() && (await (prepare?.(canConfirm) ?? true)) && canConfirm() + ); }, goBackToHome).catch((err: unknown) => { Alert.alert(t('session.screen.operationFailed'), humanizeRemoteError(err)); }); @@ -9844,7 +9890,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S }, [deviceId, messageActionBusy, performForkAtMessage, t]); const historyRecoveryError = inputProjection.error ?? (tailBannerState?.kind === 'error-tail' - ? tailBannerState.rawError ?? tailBannerState.text : ''); + ? (tailBannerState.rawError ?? tailBannerState.text) + : ''); const canRecoverHistory = !isSharedTaskPeer(deviceId) && canUseRemoteSessionControls && !queueAvailabilityReason && !messageActionBusy && !tailBannerBusy && !queueBusy && Boolean(currentSession?.sdkSessionId) && canOfferCodexHistoryRecovery({ @@ -9971,9 +10018,7 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S const resolvePastedSessionLinkLabel = useCallback(async (href: string) => { const target = parseSessionDeepLinkUrl(href); if (!target) return null; - const targetSession = remoteSessionStore.getSessions().find((item) => ( - item.id === target.sessionId - )); + const targetSession = remoteSessionStore.getSessions().find((item) => item.id === target.sessionId); const targetDeviceId = resolveSessionLinkDeviceId({ listedDeviceId: targetSession?.canonicalDeviceId ?? targetSession?.deviceLinkDeviceId @@ -9982,16 +10027,16 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S currentDeviceId: deviceId, }); const targetMaker = targetDeviceId - ? (targetDeviceId === deviceId + ? targetDeviceId === deviceId ? maker - : createMobileMakerTransport({ deviceId: targetDeviceId, invoke })) + : createMobileMakerTransport({ deviceId: targetDeviceId, invoke }) : null; try { if (target.messageClientId) { - let targetMessage = remoteSessionStore.getMessages(target.sessionId).find((message) => ( - message.clientId === target.messageClientId || message.id === target.messageClientId - )); + let targetMessage = remoteSessionStore.getMessages(target.sessionId).find((message) => + message.clientId === target.messageClientId || message.id === target.messageClientId, + ); if (!targetMessage && targetMaker && targetDeviceId) { await openLink(targetDeviceId); const around = await targetMaker.aroundMessagesByClientId( @@ -9999,9 +10044,9 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S target.messageClientId, { radius: 1 }, ); - targetMessage = around.find((message) => ( - message.clientId === target.messageClientId || message.id === target.messageClientId - )); + targetMessage = around.find((message) => + message.clientId === target.messageClientId || message.id === target.messageClientId, + ); } const text = targetMessage ? mobileSessionMessageDisplayText(targetMessage) : null; if (!text) return null; @@ -10027,9 +10072,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S const addMessageToComposer = useCallback((clientId: string) => { if (!sessionId || !canUseComposer) return; - const target = messages.find((message) => ( - message.clientId === clientId || message.id === clientId - )); + const target = messages.find((message) => message.clientId === clientId || message.id === clientId, + ); const summary = target ? mobileSessionMessageDisplayText(target) : null; const bounded = summary ? boundAgentReferenceText(summary) : null; const node = sessionLinkComposerNode({ @@ -10167,11 +10211,17 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S const headerNode = ( <> - {companionHeaderShown ? <> - {horizontalSystemHeader ? null : } - {companionEntry.threadRootSessionId ? + {horizontalSystemHeader ? null : ( + + )} + {companionEntry.threadRootSessionId ? ( + : + ) : ( + setSearchOpen(true)} - onBack={goBackToHome} />} - : + )} + + ) : ( + } + || (connectionError ? t('session.screen.sessionNotSynced') : deviceName || t('session.screen.conversationFallback')) + } + /> + )} ); return ( @@ -10312,8 +10367,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S {sharedTaskExit.dialog} - {() => ( - currentSession ? ( + {() => + currentSession ? ( { setSideChatsPendingOpen(true); @@ -10357,7 +10412,7 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S onLoadExtraDirPath={(path) => void loadExtraDirBrowsePath(path)} onRefreshAccountUsage={() => void refreshAccountUsage()} onResetCodexRateLimits={() => void resetCodexRateLimits()} - onSaveCodexResetAutoUse={async patch => { + onSaveCodexResetAutoUse={async (patch) => { const key = codexRateLimits?.autoUse?.accountKey; if (!key || !maker || !localCodexRateLimitControl) throw new Error('Account unavailable'); const result = await maker.setCodexResetAutoUse(patch, key, accountProviderId); @@ -10392,10 +10447,12 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S visible={settingsOpen} /> ) : null - )} - {currentSession ? + {currentSession ? ( + setSideChatsOpen(false)} - onCreate={() => createSideChat()} onSelect={openSideChat} /> : null} + onCreate={() => createSideChat()} onSelect={openSideChat} /> + ) : null} - {!isSharedTaskPeer(deviceId) && !sessionManagedByHost ? + {!isSharedTaskPeer(deviceId) && !sessionManagedByHost ? ( + {planModeSupported ? ( // 点击即切换计划模式并关面板(产品决策,不做开关);已开启时显示 ✓,再点退出。 : null} + trailing={planModeOn ? ( + + ) : null} trailingSize={iconSize.md} /> ) : null} @@ -10532,12 +10592,15 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S trailing="chevron" /> ) : null} - : null} - {!isSharedTaskPeer(deviceId) ? + ) : null} + {!isSharedTaskPeer(deviceId) ? ( + : null} + /> + ) : null} ) : contextSheetView === 'collab' && collab.isLead ? ( )} - {() => ( - currentSession && canConfigureSessionModel && runtimeOptions && modelSheetSelection && modelSheetRuntimeOptions ? ( + {() => + currentSession && canConfigureSessionModel && runtimeOptions && modelSheetSelection && modelSheetRuntimeOptions ? ( { + loadCapabilities: async (agent) => { const result = normalizeMobileAgentCapabilities(await loadComposerAgentCapabilities(agent)); if (!result) throw new Error('Capabilities unavailable'); return result; @@ -10633,7 +10697,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S disabled={controlBusy || !canUseRemoteSessionControls} emptyHint={!orcaRemoteAgentDeviceId && composerDeviceProviders.error && !composerDeviceProviders.unsupported ? humanizeRemoteError(composerDeviceProviders.error) - : modelSheetCapabilitiesError ?? undefined} + : (modelSheetCapabilitiesError ?? undefined) + } flatOptions={orcaRemoteAgentDeviceId ? [] : modelSheetRuntimeOptions.modelOptions} providersReady={composerDeviceProviders.ready} providersUnsupported={orcaRemoteAgentDeviceId ? false : composerDeviceProviders.unsupported} @@ -10662,7 +10727,7 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S visible={modelSheetOpen && canConfigureSessionModel} /> ) : null - )} + } {() => currentSession && collab.eligible ? ( @@ -10679,7 +10744,7 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S } : undefined, scope: JSON.stringify([auth.user?.id, collabWorkerDeviceId, 'orca-worker']), agents: collab.workerForm.pickerAgents, - loadCapabilities: async agent => { + loadCapabilities: async (agent) => { const result = normalizeMobileAgentCapabilities(await collab.workerForm.maker.getCapabilities(agent)); if (!result) throw new Error('Capabilities unavailable'); return result; @@ -10740,17 +10805,16 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S /> ) : null} {pendingImagePreview.host} - {composerPreviewUrl && composerGalleryImages.length > 0 ? ( - // composer 托盘图片的全屏查看(沿用聊天消息同款 ImageLightbox;本地图无需远端取件)。 - // annotation:托盘图可圈点标注 / 再编辑,保存后烧录替换附件重新上传。 - setComposerPreviewAttachmentId(null)} /> - ) : null} - {composerAnnotations.host} + {composerAnnotations.host} {isSharedTaskAccessRevoked ? ( @@ -10794,12 +10858,13 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S bottomInset={androidFrostedComposer ? 0 : bottomOverlayHeight} interactive={!sessionListDrawerOverlayMounted}> - (await send({ independentText: text })) === true : null}> + (await send({ independentText: text })) === true : null}> 0 ? bottomOverlayHeight : MOBILE_MESSAGE_LIST_BOTTOM_PADDING) - : undefined} + ? bottomOverlayHeight > 0 ? bottomOverlayHeight : MOBILE_MESSAGE_LIST_BOTTOM_PADDING + : undefined} topOverlayHeight={topOverlayHeight} busyAction={messageActionBusy?.kind ?? null} busyClientId={messageActionBusy?.clientId ?? null} @@ -10863,9 +10928,10 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S onResolveRemoteMedia={resolveRemoteMedia} onShareImage={shareLightboxImage} imageAnnotation={composerAnnotations.chatAnnotation} - queueFooter={( - <> - {companionInlineInteraction && !shareSelectionActive ? + {companionInlineInteraction && !shareSelectionActive ? ( + : null} + /> + ) : null} {/* error-tail / interrupted 收尾提示:live 错误与队列区互斥 (resolveSessionTailBanner 内部已按 projection.error 抑制)。 */} {tailBannerState ? ( @@ -10918,7 +10985,7 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S readOnlyReason={queueAvailabilityReason} /> - )} + } scrollResetKey={sessionId} isReadingPositionActive={isReadingPositionActive} syncingWhileEmpty={syncingWhileEmpty || chasingHiddenHistory} @@ -10955,7 +11022,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S viewport={detailViewport} width={paneLayout.detail.width} > - {!isSharedTaskAccessRevoked && 0 ? windowDimensions.height - composerRegion.y - composerRegion.height - insets.bottom @@ -11074,7 +11142,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S > - {t('session.common.goalMode')} · {goalStatusLabel(goalStatus.status, goalStatus.lastReason)} + {t('session.common.goalMode')} ·{" "} + {goalStatusLabel(goalStatus.status, goalStatus.lastReason)} @@ -11229,14 +11298,16 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S ) : null} {companionChat && companionResource?.ref.id === 'cindy-default' - && companionResource.links.some(link => link.rel === 'conversation' && link.target.kind === 'session' && link.target.sessionId === sessionId) + && companionResource.links.some( + (link) => link.rel === 'conversation' && link.target.kind === 'session' && link.target.sessionId === sessionId) && followUpEngaged === followUpScope && !remoteSessionRunning && !isSessionStreaming && pending.length === 0 && !shareSelectionActive && !remoteUnavailableReason ? ( ) : null} {/* Desktop BotWorkingStatus: a teammate's paced public status stays above the composer, also while the user reads older messages. Interactions carry their own panel. */} - {companionChat && companionWorkingLabel && !messageListItems.some(item => item.type === 'work_group' && item.teammateJournal && item.isStreaming) && !companionInlineInteraction + {companionChat && companionWorkingLabel && !messageListItems.some( + (item) => item.type === 'work_group' && item.teammateJournal && item.isStreaming) && !companionInlineInteraction && !(syncingWhileEmpty && !hasRenderedMessages) ? ( ) : null} - {!sessionResourceCards.blocked ? : null} + /> + ) : null} )} {shareSelectionActive ? ( @@ -11465,7 +11538,8 @@ function SessionScreenContent({ beforeSessionSwitchRef, newSessionActionRef }: S ) : null} - } + + )} @@ -11651,12 +11725,12 @@ function SessionHeaderBar({ settings: onOpenSettings, usage: onOpenUsage, } satisfies Record void>; - const notice = sharedTaskEnded ? null : noticeOverride ?? compactSessionHeaderNotice({ + const notice = sharedTaskEnded ? null : (noticeOverride ?? compactSessionHeaderNotice({ isDeviceAccessRevoked, pendingCount, queuePaused, session: currentSession, - }); + })); const nativeHeader = Platform.OS === 'ios'; const titleDoubleTap = sharedTaskEnded ? undefined : onTitleDoubleTap; const handleTitlePress = useDoubleTap(titleDoubleTap); @@ -11681,8 +11755,9 @@ function SessionHeaderBar({ if (horizontalSystemHeader) { // Stack.Toolbar left/right forces headerShown: true; drop them while the bar is hidden. if (systemBarHidden) return ; - const files = overview?.actions.find(action => action.id === 'files'); - return <> + const files = overview?.actions.find((action) => action.id === 'files'); + return ( + <> actionHandlers[id]()} /> + onAction={(id) => actionHandlers[id]()} /> - ; + + ); } // 分享选择模式只保留全选;底部关闭按钮负责退出。 @@ -11737,18 +11813,23 @@ function SessionHeaderBar({ {sessionListButton} - {systemBack ? - {workerHeader ? null : - } - {workerHeader ? null : - + {workerHeader ? null : ( + + )} + {workerHeader ? null : ( + action.id === 'files' && !action.disabled)} - onPress={onOpenFiles} />} + disabled={!currentSession || !overview?.actions.some( + (action) => action.id === 'files' && !action.disabled)} + onPress={onOpenFiles} /> + )} - : null} + + ) : null} {systemBack ? null : nativeHeader ? ( ) : ( @@ -11805,13 +11886,14 @@ function SessionHeaderBar({ desktopLabel={t('remoteDesktop.title')} filesLabel={t('session.presentation.overview.actions.files.a11y')} moreLabel={t('session.menu.details')} - files={overview?.actions.find(action => action.id === 'files')} + files={overview?.actions.find((action) => action.id === 'files')} detailsOnly={workerHeader} onDetails={onOpenSettings} onDesktop={onOpenRemoteDesktop} - onAction={id => actionHandlers[id]()} + onAction={(id) => actionHandlers[id]()} /> - ) : + ) : ( + {workerHeader ? null : ( - } + + )} ); } @@ -11981,7 +12064,8 @@ function SessionComposerPalette({ () => canUseComposer && composerTrigger.kind === 'slash' ? filterSlashCommands(mergeMobileLocalSlashCommands(mergeMobileGoalCommand( canUseSideChats ? mergeMobileSideChatCommand(slashCommands, t('session.sideChat.commandDescription')) - : slashCommands.filter(command => command.kind !== 'desktop' || command.name !== 'side'), + : slashCommands.filter( + (command) => command.kind !== 'desktop' || command.name !== 'side'), t('session.common.goalMode'))), composerTrigger.query, 5) : [], [canUseComposer, canUseSideChats, composerTrigger, slashCommands, t], @@ -12035,8 +12119,8 @@ function SessionComposerPalette({ // 让行仲裁,见 desktopSlashCommands):拉取失败(含老被控端无此通道)静默降级 // 为不展示,不能拖垮 builtin/skill 两路。 maker.listDesktopCommands().catch( - () => ({ success: false } satisfies MobileDesktopCommandListResult), - ), + () => ({ success: false }) satisfies MobileDesktopCommandListResult, + ), ]); return { builtins, skills, desktop }; }) @@ -12114,7 +12198,8 @@ function SessionComposerPalette({ return; } } - const remoteQuery = cachedScan?.result.truncated ? (query || undefined) : undefined; + const remoteQuery = cachedScan?.result.truncated ? query || undefined + : undefined; const seq = ++atLoadSeqRef.current; // 缓存已画时 loading 置 false(而非跳过):ComposerPaletteFrame 的 spinner 会整体 // 顶掉 children,置 true 会把刚画的缓存闪成「读取中」,而上一轮无缓存请求残留的 @@ -12404,7 +12489,7 @@ function SessionComposerInput({ || (Platform.OS === 'ios' && !composerLayout.send.visible && !voiceStartPending); const composerStopDisabled = composerLayout.stop.disabled || !canUseRemoteSessionControls; const composerStopDisabledReason = !canUseRemoteSessionControls - ? remoteUnavailableReason ?? t('session.menu.aiRenameOffline') + ? (remoteUnavailableReason ?? t('session.menu.aiRenameOffline')) : composerLayout.stop.disabledReason; const composerShowInlineStop = composerLayout.stop.visible && !composerSendSlotIsStop && !sending; // send.visible 在语音生命周期内恒 true(sessionOperation.ts),这里不再按 @@ -12471,7 +12556,9 @@ function SessionComposerInput({ ? COMPOSER_CARD_ROW_CHROME_HEIGHT : COMPOSER_INPUT_ROW_CHROME_HEIGHT; // 附件托盘与文字同在卡片里:计入 chrome,文字超出时由输入框自己滚、裁掉上方文字。 - return COMPOSER_VERTICAL_PADDING_HEIGHT + statusReserve + rowChrome + composerAttachmentTrayHeight; + return ( + COMPOSER_VERTICAL_PADDING_HEIGHT + statusReserve + rowChrome + composerAttachmentTrayHeight + ); }, [composerAttachmentTrayHeight, composerCardActive, voiceStatusVisible]); const composerInputMaxContentHeight = useMemo(() => { const availableHeight = nativeShellLayout.composerMaxHeight - composerChromeHeight; @@ -12553,15 +12640,15 @@ function SessionComposerInput({ COMPOSER_INPUT_SINGLE_LINE_CONTENT_HEIGHT, Math.ceil(event.nativeEvent.contentSize.height), ); - setComposerInputContentHeight((currentHeight) => ( - Math.abs(currentHeight - nextHeight) < 1 ? currentHeight : nextHeight - )); + setComposerInputContentHeight((currentHeight) => + Math.abs(currentHeight - nextHeight) < 1 ? currentHeight : nextHeight + ); }, []); const handleComposerRichInputHeight = useCallback((height: number) => { const nextHeight = Math.max(COMPOSER_INPUT_SINGLE_LINE_CONTENT_HEIGHT, Math.ceil(height)); - setComposerInputContentHeight((currentHeight) => ( - Math.abs(currentHeight - nextHeight) < 1 ? currentHeight : nextHeight - )); + setComposerInputContentHeight((currentHeight) => + Math.abs(currentHeight - nextHeight) < 1 ? currentHeight : nextHeight, + ); }, []); const handleVoiceDraftTextLayout = useCallback(() => { const block = voiceDraftMeasuredBlockRef.current; @@ -12748,8 +12835,8 @@ function SessionComposerInput({ // 听写期间把输入区撑到 44pt 触控目标:命中层盖在 inputFrame 上, // hitSlop 越不过父边界(见常量注释)。 inputFrameMinHeight={Platform.OS !== 'ios' && voiceIsListening ? MOBILE_COMPOSER_MIN_TOUCH_TARGET : undefined} - inputElement={( - - )} + } inputOverlay={renderComposerInputOverlay()} inputStyle={voiceIsListening ? styles.inputVoiceHidden : undefined} inputTestID="session.composerInput" @@ -12868,7 +12955,8 @@ function SessionSearchSheet({ ? hasHits ? `${activeIndex + 1} / ${hitCount}` : '0 / 0' : t('session.screen.searchEnterKeyword'); if (Platform.OS === 'ios') { - return ; + /> + ); } return ( | null { return value && typeof value === 'object' && !Array.isArray(value) - ? value as Record + ? (value as Record) : null; } @@ -13242,7 +13331,9 @@ function ComposerRuntimePill({ testID={testID} > {leading ?? null} - {Icon ? : null} + {Icon ? ( + + ) : null} {label} - {fastOn ? : null} + {fastOn ? ( + + ) : null} ); @@ -13280,7 +13373,8 @@ function ComposerActivityStatus({ sessionKey: string; reconnectAttempt: RemoteSessionRunStatus['reconnectAttempt']; /** 自愈 / 供应商组换电脑进行中:只显示这一行状态(与消息流里的进行中行同一个说法)。 */ - pendingRecovery?: { label: string; attempt?: number; maxAttempts?: number } | null; + pendingRecovery?: { label: string; attempt?: number; maxAttempts?: number; + } | null; sideTaskRunning: boolean; startedAt: number | null; rateStartedAt?: number | null; @@ -13407,8 +13501,8 @@ function ComposerActivityStatus({ ? 'session.screen.rateLimitRetrying' : 'session.screen.networkReconnecting', ) - // 发送等待超过阈值时,同一行先说明卡在哪一步;创建完成后回到「思考中」。 - : slowSendPhase ? t(`session.new.slowSend.${slowSendPhase}`) + : // 发送等待超过阈值时,同一行先说明卡在哪一步;创建完成后回到「思考中」。 + slowSendPhase ? t(`session.new.slowSend.${slowSendPhase}`) : t('session.screen.thinking'); return ( diff --git a/apps/mobile/app/sessions/new.tsx b/apps/mobile/app/sessions/new.tsx index a2ce06091d7..03807ce8846 100644 --- a/apps/mobile/app/sessions/new.tsx +++ b/apps/mobile/app/sessions/new.tsx @@ -1,3 +1,5 @@ +import { useBotGroupRemoteMedia } from "@/session/useBotGroupRemoteMedia"; +import { ComposerAttachmentPreview } from "@/session/ComposerAttachmentPreview"; import { PluginUiEnablePrompt } from '@/plugins/PluginUiEnablePrompt'; import { deviceDisplayName } from '@/device-link/deviceName'; import { @@ -444,7 +446,9 @@ const NEW_SESSION_SCREEN_TOP_PADDING = Platform.OS === 'android' ? 0 : spacing.x * 目标 caps 就绪后补评。 */ function targetAgentHasFast(deviceId: string, agentKind: NewSessionAgentKind): boolean { - return getCachedAgentCapabilities(buildAgentCapabilitiesCacheKey(deviceId, agentKind))?.hasFastMode === true; + return ( + getCachedAgentCapabilities(buildAgentCapabilitiesCacheKey(deviceId, agentKind))?.hasFastMode === true + ); } /** 旧被控端(明确不支持 provider:list)才用能力表模型兜底选默认;支持目录的电脑一律走目录。 */ @@ -479,10 +483,13 @@ interface WorktreeBranchCompatibilitySelection { export default function NewRemoteSessionScreen() { const creatingRef = useRef(false); - return ; + return ( + + ); } -function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: boolean } }) { +function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: boolean }; +}) { const styles = useThemedStyles(makeStyles); const { colors } = useTheme(); const { t, i18n: i18nInstance } = useTranslation(); @@ -844,9 +851,9 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool getToken: auth.getAccessToken, hasInput: hasRecoveryInput, leaveForeignRecovery: leaveForeignOutboxRecovery, - onError: error => setError(formatRemoteError(error)), - onEdited: record => { outboxRecoveryRef.current = record; }, - onRestore: record => { + onError: (error) => setError(formatRemoteError(error)), + onEdited: (record) => { outboxRecoveryRef.current = record; }, + onRestore: (record) => { outboxRecoveryRef.current = record; userTouchedDeviceRef.current = true; restoreCreationDraft(record.creation.draft, @@ -944,7 +951,7 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool || visualInitialDraft || !newSessionPreferencesLoaded), deviceId: selectedDeviceId, workingDir: initialWorkingDir, owner: outboxOwner, hasInput: hasRecoveryInput, - onResume: next => router.setParams(next), + onResume: (next) => router.setParams(next), }); const pendingGoalSkillRef = useRef(null); useEffect(() => { @@ -1116,7 +1123,7 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool keepDeviceIds: workerAgentKeepDeviceIds, }); const remoteAgentCatalog = remoteAgentPick - ? remoteAgentCatalogs.find((catalog) => catalog.deviceId === remoteAgentPick.deviceId) ?? null + ? (remoteAgentCatalogs.find((catalog) => catalog.deviceId === remoteAgentPick.deviceId) ?? null) : null; // 药丸用那台电脑目录里的来源与模型名;目录还没读到时退回模型 id、不画来源标。 const remoteAgentPillProvider = useMemo(() => { @@ -1128,7 +1135,9 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool selectedProviderId: null, visibilityOverrides: remoteAgentCatalog.modelVisibilityOverrides, }).activeSourceId; - return remoteAgentCatalog.providers.find((provider) => provider.id === sourceId) ?? null; + return ( + remoteAgentCatalog.providers.find((provider) => provider.id === sourceId) ?? null + ); }, [remoteAgentCatalog, remoteAgentPick]); // 恢复上次在这台被控电脑上给当前 agent 选的另一台电脑 / 分享来的模型。每个(电脑, agent)只试一次, // 本页用户自己改过运行配置就不再恢复。 @@ -1164,7 +1173,8 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool // 供应商时提前提示 + 拦截创建,不让用户发出注定失败的首条消息。unknown 不拦截。 // 选了另一台电脑的模型时不按被控电脑判:那一行来自那台已连接的供应商,登录由那台核对。 const agentAuthVerdict = useMemo( - () => remoteAgentPick ? 'ready' as const : agentAuthGateVerdict({ + () => remoteAgentPick ? ("ready" as const) + : agentAuthGateVerdict({ providers: deviceProviders.providers, loading: deviceProviders.loading, error: deviceProviders.error, @@ -1314,8 +1324,8 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool ); // 选了另一台电脑的模型:药丸写那台目录里的模型名与档位,读屏一并读出电脑名(与已建任务同口径)。 const remoteAgentModelName = remoteAgentPick && remoteAgentPillProvider - ? getModel(remoteAgentPillProvider, remoteAgentPick.model, remoteAgentPick.agentKind)?.name ?? null - : null; + ? (getModel(remoteAgentPillProvider, remoteAgentPick.model, remoteAgentPick.agentKind)?.name ?? null) + : null; const pillModelSummary = remoteAgentPick ? [ remoteAgentModelName || remoteAgentPick.model, @@ -1332,9 +1342,9 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool // 权限按钮 / 权限下拉不体现 plan(对齐桌面 PR#494 / Cursor):计划模式激活时展示 // 进入前的底层权限档(无记录时回退首个非 plan 档),激活态由 composer 的 PlanModeChip 表达。 const displayPermissionMode = draft.permissionMode === 'plan' - ? ((prePlanPermissionModeRef.current && prePlanPermissionModeRef.current !== 'plan') - ? prePlanPermissionModeRef.current - : runtimeOptions.permissionOptions.find((option) => option.id !== 'plan')?.id ?? 'ask') + ? prePlanPermissionModeRef.current && prePlanPermissionModeRef.current !== 'plan' + ? prePlanPermissionModeRef.current + : (runtimeOptions.permissionOptions.find((option) => option.id !== 'plan')?.id ?? 'ask') : draft.permissionMode; const displayPermissionLabel = runtimeOptions.permissionOptions .find((option) => option.id === displayPermissionMode)?.label ?? runtimeSummary.permissionLabel; @@ -1744,7 +1754,7 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool useIncomingShareComposer({ destination: 'new', ready: isShareTargetFocused && newSessionPreferencesLoaded && !creating, - appendText: text => appendNewSessionSharedText({ firstMessageRef, creatingRef, editRecoveredDraft, + appendText: (text) => appendNewSessionSharedText({ firstMessageRef, creatingRef, editRecoveredDraft, voiceDictionaryLearningTrackerRef, setDraft }, text), attachmentCount: attachments.length, enqueueUploads, getPendingUploadCount, onError: setAttachmentError, @@ -1755,9 +1765,9 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool MOBILE_COMPOSER_INPUT_SINGLE_LINE_HEIGHT, Math.ceil(event.nativeEvent.contentSize.height), ); - setFirstMessageInputContentHeight((currentHeight) => ( - Math.abs(currentHeight - nextHeight) < 1 ? currentHeight : nextHeight - )); + setFirstMessageInputContentHeight((currentHeight) => + Math.abs(currentHeight - nextHeight) < 1 ? currentHeight : nextHeight, + ); }, []); const handleVoiceDraftTextLayout = useCallback(() => { @@ -2172,7 +2182,8 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool return; } } - const remoteQuery = cachedScan?.result.truncated ? (query || undefined) : undefined; + const remoteQuery = cachedScan?.result.truncated ? query || undefined + : undefined; const seq = ++atLoadSeqRef.current; // 缓存已画时 loading 置 false(而非跳过):ComposerPaletteFrame 的 spinner 会整体 // 顶掉 children,置 true 会把刚画的缓存闪成「读取中」,而上一轮无缓存请求残留的 @@ -2367,10 +2378,10 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool if (!targetCapabilities) return false; const storedPermission = newSessionPreferences?.permissionModeByAgent[config.agent]; const permission = config.agent === draft.agentKind ? draft.permissionMode - : storedPermission ?? defaultPermissionModeForNewSessionAgent(config.agent); - if (config.agent !== draft.agentKind && !await confirmFullAccessChange(draft.permissionMode, permission, { + : (storedPermission ?? defaultPermissionModeForNewSessionAgent(config.agent)); + if (config.agent !== draft.agentKind && !(await confirmFullAccessChange(draft.permissionMode, permission, { restoringRememberedChoice: storedPermission !== undefined, - })) return false; + }))) return false; if (deviceAtStart !== selectedDeviceRef.current || sequence !== runtimeActionSeqRef.current) return false; userTouchedRuntimeRef.current = true; const fastMode = targetCapabilities.hasFastMode === true && config.fast; @@ -2381,11 +2392,11 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool pick: { deviceId: remoteDeviceId, agentKind: config.agent, model: config.modelId, providerId: config.providerId, effort: config.effort, fastMode }, }); - setDraft(current => ({ ...current, agentKind: config.agent, permissionMode: permission })); + setDraft((current) => ({ ...current, agentKind: config.agent, permissionMode: permission })); } else { explicitProviderModelSelectionRef.current = config.modelId; setRemoteAgentChoice(null); - setDraft(current => ({ ...current, agentKind: config.agent, model: config.modelId, + setDraft((current) => ({ ...current, agentKind: config.agent, model: config.modelId, providerId: config.providerId, effort: config.effort, fastMode, permissionMode: permission })); requestHostChoiceWrite(true); } @@ -2459,7 +2470,7 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool // 非 plan 档写 per-agent 记忆(内存 + 落盘;对齐桌面 lastByVendor)。 const selectPermissionMode = useCallback((mode: string) => { void (async () => { - if (!await confirmFullAccessChange(draft.permissionMode, mode)) return; + if (!(await confirmFullAccessChange(draft.permissionMode, mode))) return; patchDraft({ permissionMode: mode }); if (mode === 'plan') return; // 老被控端兼容档,不入记忆 // 同步进本地 state:本次会话内切走再切回也能拿到最新记忆(落盘不回写 state)。 @@ -3140,7 +3151,8 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool connectionProvider: (providerId: string) => voiceContext.createAsrConnection(providerId), refinerTargetProvider: (providerId: string, options?: { refreshAccessToken?: boolean }) => voiceContext.createRefinerTarget(providerId, options), - warmRefiner: (input: { system: string; user: unknown; promptCacheKey: string }) => + warmRefiner: (input: { system: string; user: unknown; promptCacheKey: string; + }) => voiceContext.warmRefiner(input), initialDraft: currentDraft, initialSelection, @@ -3474,15 +3486,17 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool ) : null} {pillModelSummary} {(remoteAgentPick ? remoteAgentPick.fastMode : triggerFastOn) - ? - : null} + ? ( + + ) : null} {composerVoicePlacement?.inline || composerVoicePlacement?.floating - ? - : null} + ? ( + + ) : null} {composerShowCreateButton ? renderCreateButton() : null} ); @@ -3617,8 +3631,8 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool flatModels: flatModelsForUnsupportedHost(selectedDeviceId, nextKind, deviceProvidersRef.current.unsupported), }); const rememberedFast = next.providerId - ? draftMemory.getFast(next.agentKind, next.providerId, next.model) ?? false - : false; + ? (draftMemory.getFast(next.agentKind, next.providerId, next.model) ?? false) + : false; return { ...current, agentKind: next.agentKind, @@ -3780,6 +3794,7 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool }, [attachments, mediaAssetAttachments]); // composer 托盘图片附件 → 全屏查看器图集(本地 file:// 直接可显示,不走远端取件)。 // 标注附件点开显示**原图**(叠矢量笔迹可继续编辑/撤销)而非烧录预览图,同会话页。 + const resolveComposerMedia = useBotGroupRemoteMedia(selectedDeviceId ?? ""); const composerGalleryImages = useMemo(() => { const images: MobileMessageGalleryImage[] = []; for (const attachment of attachments) { @@ -3800,9 +3815,6 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool // 依赖具体的稳定回调而非整个 hook 对象:烧录 host 挂载/卸载不应重建图集 // (images 引用变化会重置 lightbox 里正在画的笔迹,review P1;同会话页)。 }, [attachments, attachmentPreviews, composerAnnotations.trayImageSourceUri]); - const composerPreviewUrl = composerPreviewAttachmentId - ? (composerGalleryImages.find((image) => image.key === composerPreviewAttachmentId)?.url ?? null) - : null; // 展开(card)态输入卡内的附件缩略图托盘(对照 Cursor,图片在输入卡里、文字上方)。 const renderComposerAttachmentTray = () => ( ); // 收起态附件徽标(leading 仅在非 card 态渲染);点击聚焦输入框展开完整托盘。 - const renderComposerCollapsedAttachmentBadge = () => (attachments.length > 0 || pendingUploads.length > 0 || pastePlaceholderCount > 0 ? ( + const renderComposerCollapsedAttachmentBadge = () => + attachments.length > 0 || pendingUploads.length > 0 || pastePlaceholderCount > 0 ? ( firstMessageInputRef.current?.focus()} @@ -3829,7 +3842,7 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool previews={attachmentPreviews} testID="newSession.attachmentCollapsedBadge" /> - ) : null); + ) : null; // 收起态把附件 + 号放在输入框左侧,避免用户必须先聚焦才能打开附件面板; // 卡片态仍由 renderComposerToolbar() 渲染同一入口。 @@ -3940,7 +3953,7 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool setModelSheetOpen(true); return; } - const worktreeIntent = prepareWorktreeCreateIntent(key => setError(t(key))); + const worktreeIntent = prepareWorktreeCreateIntent((key) => setError(t(key))); if (!worktreeIntent) return; creatingRef.current = true; const startedAt = Date.now(); @@ -3954,7 +3967,8 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool // 建出重复会话(codex review P2)。交接成功后锁保持到组件销毁;「返回编辑」 // 回来的是新 mount,creatingRef 天然复位。 let handedOff = false; - const creationLeases: { precreated: (() => void) | null; durable: (() => void) | null } = { precreated: null, durable: null }; + const creationLeases: { precreated: (() => void) | null; durable: (() => void) | null; + } = { precreated: null, durable: null }; const accountIdAtCreate = auth.user?.id?.trim() ?? ''; const authOwnerAtCreate = getMobileAuthOwner(); const { isCurrentOwner, worktreeAccountId } = captureNewSessionOwner(authOwnerAtCreate, accountIdAtCreate); @@ -3986,7 +4000,7 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool setCreatePhase('uploading'); const { failedCount, abandoned } = await waitForPendingUploads(); // 离开确认弹窗未决时,等用户选择:选「放弃发送」就不发,选「继续等待」照常创建。 - if (abandoned || await waitForLeaveDecision()) return; + if (abandoned || (await waitForLeaveDecision())) return; if (!isCurrentOwner()) return; if (!ensureDeviceAlive()) return; if (failedCount > 0) return; @@ -4019,33 +4033,29 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool setError(t('session.new.worktreeRecoveryStateFailed')); return; } - const obligationOwnedByLiveTask = (sessionId: string) => ( + const obligationOwnedByLiveTask = (sessionId: string) => hasLiveNewSessionCreation(sessionId) - || isPrecreatedWorktreeRegistrationInFlight(sessionId) - ); + || isPrecreatedWorktreeRegistrationInFlight(sessionId); // 本设备上一次未完成的 obligation 先恢复。其它设备记录、仍被正常创建 // task 认领的记录不参与本次阻塞;只有无 owner 且无法回收/确认的旧目录 // 会阻止继续创建第二份。 const recovery = await recoverPendingPrecreatedWorktrees(worktreeAccountId, { openLink, - discardPrecreated: async (_deviceId, input) => ( - maker.worktree.discardPrecreated(input) - ), + discardPrecreated: async (_deviceId, input) => + maker.worktree.discardPrecreated(input), cancelPrecreated: maker.worktree.cancelPrecreated ? async (_deviceId, input) => maker.worktree.cancelPrecreated!(input) : undefined, onDiscarded: completePrecreatedWorktreeRecovery, - isSessionClaimed: async (_deviceId, pendingSessionId) => ( - isExactRemoteSessionClaimed( + isSessionClaimed: async (_deviceId, pendingSessionId) => + isExactRemoteSessionClaimed( pendingSessionId, (id) => maker.getSession(id), - ) - ), - shouldDefer: (record) => ( - record.deviceId !== selectedDeviceId + ), + shouldDefer: (record) => + record.deviceId !== selectedDeviceId || (!creatingWorktree && record.sessionId !== outboxRecoveryRef.current?.item.sessionId) - || obligationOwnedByLiveTask(record.sessionId) - ), + || obligationOwnedByLiveTask(record.sessionId), isCurrent: isCurrentOwner, }); if (!isCurrentOwner()) return; @@ -4178,8 +4188,9 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool ...(collabDraft ? { orcaEnable: buildOrcaEnableOptions( narrowOrcaWorkerProvider(collabDraft, collabDraft.executionDeviceId - ? (collabDraft.executionDeviceId === collabWorkerDeviceId && collabDeviceProviders.ready ? collabDeviceProviders.providers : null) - : (deviceProviders.ready ? deviceProviders.providers : null)), + ? collabDraft.executionDeviceId === collabWorkerDeviceId && collabDeviceProviders.ready ? collabDeviceProviders.providers : null + : deviceProviders.ready ? deviceProviders.providers : null, + ), buildDraftWorkerInitialTask(collabDraft.initialTask, effectiveDraft.firstMessage), ), collabDraft, @@ -4321,7 +4332,7 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool setModelSheetOpen(true); return; } - const worktreeIntent = prepareWorktreeCreateIntent(key => setGoalError(t(key))); + const worktreeIntent = prepareWorktreeCreateIntent((key) => setGoalError(t(key))); if (!worktreeIntent) return; // ㉙ 设备守卫入口(独立 review P1-1 + busy 泄漏):快照取自闭包 selectedDeviceId, // 入口与 ref 核对必须发生在**加锁之前**——加锁后 return 会绕过 finally,busy @@ -4334,7 +4345,8 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool setGoalError(null); let handedOff = false; let releasePrecreatedRegistration: (() => void) | null = null; - let precreatedWorktree: { + let precreatedWorktree: + | { sessionId: string; path: string; recoveryKey: string; @@ -4394,7 +4406,8 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool evictDeviceProviders(selectedDeviceId); } // Agent 在另一台电脑运行时登录在那台:不按被控电脑的登录拦(agentAuthVerdict 此时恒为 ready)。 - const freshAuth: Promise<{ unauthenticated: boolean; fresh: DeviceProvidersPayload | null }> = + const freshAuth: Promise<{ unauthenticated: boolean; fresh: DeviceProvidersPayload | null; + }> = agentAuthVerdict === 'unauthenticated' || remoteAgentPick ? Promise.resolve({ unauthenticated: false, fresh: null }) : confirmAgentUnauthenticated(draft.agentKind, selectedDeviceId); @@ -4424,29 +4437,25 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool setGoalError(t('session.new.worktreeRecoveryStateFailed')); return; } - const sessionIsClaimed = (pendingSessionId: string) => ( - hasLiveNewSessionCreation(pendingSessionId) - || isPrecreatedWorktreeRegistrationInFlight(pendingSessionId) - ); + const sessionIsClaimed = (pendingSessionId: string) => + hasLiveNewSessionCreation(pendingSessionId) + || isPrecreatedWorktreeRegistrationInFlight(pendingSessionId); const recovery = await recoverPendingPrecreatedWorktrees(worktreeAccountId, { openLink, - discardPrecreated: async (_deviceId, recoveryInput) => ( - maker.worktree.discardPrecreated(recoveryInput) - ), + discardPrecreated: async (_deviceId, recoveryInput) => + maker.worktree.discardPrecreated(recoveryInput), cancelPrecreated: maker.worktree.cancelPrecreated ? async (_deviceId, input) => maker.worktree.cancelPrecreated!(input) : undefined, onDiscarded: completePrecreatedWorktreeRecovery, - isSessionClaimed: async (_deviceId, pendingSessionId) => ( - isExactRemoteSessionClaimed( + isSessionClaimed: async (_deviceId, pendingSessionId) => + isExactRemoteSessionClaimed( pendingSessionId, (id) => maker.getSession(id), - ) - ), - shouldDefer: (record) => ( - record.deviceId !== selectedDeviceId - || sessionIsClaimed(record.sessionId) - ), + ), + shouldDefer: (record) => + record.deviceId !== selectedDeviceId + || sessionIsClaimed(record.sessionId), isCurrent: isCurrentOwner, }); if (!isCurrentOwner()) return; @@ -4844,8 +4853,9 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool try { await enableOrcaTeam(maker, result.sessionId, buildOrcaEnableOptions( narrowOrcaWorkerProvider(collabDraft, collabDraft.executionDeviceId - ? (collabDraft.executionDeviceId === collabWorkerDeviceId && collabDeviceProviders.ready ? collabDeviceProviders.providers : null) - : (deviceProviders.ready ? deviceProviders.providers : null)), + ? collabDraft.executionDeviceId === collabWorkerDeviceId && collabDeviceProviders.ready ? collabDeviceProviders.providers : null + : deviceProviders.ready ? deviceProviders.providers : null, + ), buildDraftWorkerInitialTask(collabDraft.initialTask, input.objective), )); collabEnabled = true; @@ -5073,8 +5083,8 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool return ( + ? composerDock.enabled ? ['top'] : ['top', 'bottom'] + : composerDock.enabled ? ['left', 'right'] : simpleScreenSafeAreaEdges()} style={styles.safeArea} testID="newSession.screen"> {Platform.OS === 'ios' && paneLayout.persistent ? ( ) : Platform.OS === 'ios' ? ( @@ -5096,20 +5106,28 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool style={styles.keyboard} > - {Platform.OS !== 'ios' || paneHeader || buildLabel || creating ? - {paneHeader ? + {paneHeader ? ( + : Platform.OS !== 'ios' ? + ) : Platform.OS !== 'ios' ? ( + : null} + /> + ) : null} {buildLabel ? ( {buildLabel} ) : null} - {creating ? : null} - : null} + {creating ? ( + + ) : null} + + ) : null} {browsePath || t('session.new.readingRemoteDir')} - {browseLoading ? : null} + {browseLoading ? ( + + ) : null} {recentWorkspaces.length > 0 ? ( changeNewSessionVoiceText(voiceSelectionInput, text)} + onChangeText={(text) => changeNewSessionVoiceText(voiceSelectionInput, text)} - onSelectionChange={event => changeNewSessionVoiceSelection(voiceSelectionInput, event.nativeEvent.selection)} + onSelectionChange={(event) => changeNewSessionVoiceSelection(voiceSelectionInput, event.nativeEvent.selection)} onContentSizeChange={handleFirstMessageInputContentSizeChange} onFocus={() => setFirstMessageInputFocused(true)} onPasteImages={(uris) => void addPastedImageAttachments(uris)} @@ -5778,7 +5798,9 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool setContextSheetOpen(false); }} testID="newSession.contextSheetPlanRow" - trailing={planModeOn ? : null} + trailing={planModeOn ? ( + + ) : null} trailingSize={iconSize.md} /> ) : null} @@ -5805,7 +5827,10 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool - ) : 'chevron'} + ) : ( + "chevron" + ) + } /> ) : null} @@ -5866,7 +5891,8 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool - {nativeSelectionSheet ? { setDevicePickerOpen(false); setWorkspacePickerOpen(false); setBrowseOpen(false); }} onBack={() => { setBrowseOpen(false); setWorkspacePickerOpen(true); }} - onDevice={(id) => { const option = deviceOptions.find(device => device.deviceId === id); if (option && !deviceSelectorDisabled) selectDevice(option); }} + onDevice={(id) => { const option = deviceOptions.find( + (device) => device.deviceId === id); if (option && !deviceSelectorDisabled) selectDevice(option); }} onDialogue={selectDialogueWorkspace} onProject={selectRecentProject} onBrowse={openProjectBrowse} onEnter={(path) => void loadBrowsePath(path)} onChoose={chooseWorkingDir} onShowHidden={setShowHiddenDirectories} - /> : null} + /> + ) : null} option.kind), - loadCapabilities: async agent => { + agents: availableNewSessionAgentOptions(availableAgentKinds).map( + (option) => option.kind), + loadCapabilities: async (agent) => { const result = normalizeMobileAgentCapabilities(await maker.getCapabilities(agent)); if (!result) throw new Error('Capabilities unavailable'); return result; @@ -6009,7 +6038,7 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool } : undefined, scope: JSON.stringify([auth.user?.id, collabWorkerDeviceId, 'orca-worker']), agents: collabForm.pickerAgents, - loadCapabilities: async agent => { + loadCapabilities: async (agent) => { const result = normalizeMobileAgentCapabilities(await collabForm.maker.getCapabilities(agent)); if (!result) throw new Error('Capabilities unavailable'); return result; @@ -6067,16 +6096,15 @@ function NewRemoteSessionContent({ creatingRef }: { creatingRef: { current: bool testID="newSession.permissionSheet" /> {pendingImagePreview.host} - {composerPreviewUrl && composerGalleryImages.length > 0 ? ( - // composer 托盘图片的全屏查看(沿用聊天消息同款 ImageLightbox;本地图无需远端取件)。 - // annotation:托盘图可圈点标注 / 再编辑,保存后烧录替换附件重新上传。 - setComposerPreviewAttachmentId(null)} /> - ) : null} {composerAnnotations.host} ); diff --git a/apps/mobile/scripts/sync-document-preview-assets.mjs b/apps/mobile/scripts/sync-document-preview-assets.mjs new file mode 100644 index 00000000000..af0358591a5 --- /dev/null +++ b/apps/mobile/scripts/sync-document-preview-assets.mjs @@ -0,0 +1,80 @@ +// Reproducible, offline WebView engines from the desktop's existing lockfile. +// No native dependency or runtime fingerprint change is needed. +import { readFile, readdir, writeFile } from "node:fs/promises"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { build } from "esbuild"; + +const root = resolve(dirname(fileURLToPath(import.meta.url)), "../../.."); +const pdfRoot = join(root, "node_modules/pdfjs-dist"); +const modelRoot = join(root, "node_modules/@google/model-viewer"); +const output = join( + root, + "apps/mobile/src/session/documentPreviewAssets.generated.ts", +); +const pdfVersion = JSON.parse( + await readFile(join(pdfRoot, "package.json"), "utf8"), +).version; +const modelVersion = JSON.parse( + await readFile(join(modelRoot, "package.json"), "utf8"), +).version; +const pdfBundle = await build({ + stdin: { + contents: `import * as pdf from 'pdfjs-dist/legacy/build/pdf.mjs'; import * as worker from 'pdfjs-dist/legacy/build/pdf.worker.mjs'; globalThis.pdfjsLib = pdf; globalThis.pdfjsWorker = worker;`, + resolveDir: root, + }, + bundle: true, + write: false, + format: "iife", + platform: "browser", + minify: true, + external: ["node:*"], + target: ["safari16", "chrome105"], +}); +const binaryAssets = {}; +for (const [kind, directory, extensions] of [ + ["cMapUrl", "cmaps", [".bcmap"]], + ["standardFontDataUrl", "standard_fonts", [".pfb", ".ttf"]], +]) { + binaryAssets[kind] = {}; + for (const name of await readdir(join(pdfRoot, directory))) { + if (!extensions.some((ext) => name.endsWith(ext))) continue; + binaryAssets[kind][name] = ( + await readFile(join(pdfRoot, directory, name)) + ).toString("base64"); + } +} +const modelBundle = await build({ + entryPoints: [join(modelRoot, "dist/model-viewer.min.js")], + bundle: true, + write: false, + format: "iife", + platform: "browser", + minify: true, + target: ["safari16", "chrome105"], +}); +const modelJs = modelBundle.outputFiles[0].text; +const drawioJs = await readFile( + join(root, "apps/desktop/src/renderer/vendor/drawio/viewer-static.min.js"), + "utf8", +); +const pdfCss = await readFile(join(pdfRoot, "web/pdf_viewer.css"), "utf8"); +await writeFile( + output, + [ + "// Generated by apps/mobile/scripts/sync-document-preview-assets.mjs; do not hand edit.", + "// PDF.js (Apache-2.0) and model-viewer (Apache-2.0) retain their bundled license notices.", + `export const MOBILE_PDF_VERSION = ${JSON.stringify(pdfVersion)};`, + `export const MOBILE_PDF_JS = ${JSON.stringify(pdfBundle.outputFiles[0].text)};`, + `export const MOBILE_PDF_CSS = ${JSON.stringify(pdfCss)};`, + `export const MOBILE_PDF_BINARY_ASSETS = ${JSON.stringify(binaryAssets)};`, + `export const MOBILE_MODEL_VERSION = ${JSON.stringify(modelVersion)};`, + `export const MOBILE_MODEL_JS = ${JSON.stringify(modelJs)};`, + "// draw.io viewer: identical to the desktop vendor distribution, including its notices.", + `export const MOBILE_DRAWIO_JS = ${JSON.stringify(drawioJs)};`, + "", + ].join("\n"), +); +console.log( + `Generated offline PDF ${pdfVersion} and model-viewer ${modelVersion} engines.`, +); diff --git a/apps/mobile/src/__tests__/BotGroupChatScreen.test.tsx b/apps/mobile/src/__tests__/BotGroupChatScreen.test.tsx index fb7cf677ebd..36cc958f026 100644 --- a/apps/mobile/src/__tests__/BotGroupChatScreen.test.tsx +++ b/apps/mobile/src/__tests__/BotGroupChatScreen.test.tsx @@ -240,7 +240,13 @@ vi.mock('@/session/ComposerAttachmentTray', () => ({ { 'data-testid': testID, onClick: onPress }, String(attachments.length + pendingUploads.length)), })); vi.mock('@/session/ImageLightbox', () => ({ - ImageLightbox: ({ images, initialUrl }: any) => el('div', { 'data-testid': 'lightbox', 'data-url': initialUrl, 'data-count': String(images.length) }), + ImageLightbox: ({ images, initialUrl, resolveOpaqueSource, onResolveRemoteMedia }: any) => { + const [url, setUrl] = useState(resolveOpaqueSource ? '' : initialUrl); + useEffect(() => { + if (resolveOpaqueSource) void onResolveRemoteMedia({ url: initialUrl }, {}).then((value: any) => setUrl(value.url)); + }, []); + return el('div', { 'data-testid': 'lightbox', 'data-url': url, 'data-count': String(images.length) }); + }, })); vi.mock('@/session/MessageRenderer', () => ({ AttachmentStrip: ({ attachments, onOpen, onOpenFile, align }: any) => el('div', { 'data-testid': 'attachmentStrip', 'data-align': align }, @@ -253,7 +259,7 @@ vi.mock('@/session/MessageRenderer', () => ({ : { kind: 'file', title: item.name, body: '' }), }, item.name))), })); -vi.mock('@/session/useBotGroupRemoteMedia', () => ({ useBotGroupRemoteMedia: () => async () => { throw new Error('offline'); } })); +vi.mock('@/session/useBotGroupRemoteMedia', () => ({ useBotGroupRemoteMedia: () => async (media: any) => ({ url: media.url, previewable: true, mimeType: 'image/png' }) })); vi.mock('@/session/useHostTeammates', () => ({ useHostTeammates: (_host: unknown, _enabled: boolean, query = '') => { h.teammateQuery = query; return h.teammates; } })); vi.mock('@/session/useBotGroupChat', () => ({ useBotGroupChat: (...args: unknown[]) => { h.hookCalls(...args); return h.chat; } })); vi.mock('@/session/useBotGroupSettingsManagement', () => ({ useBotGroupSettingsManagement: () => h.management ?? ({ @@ -2120,3 +2126,13 @@ describe('mobile group threads', () => { await act(async () => { h.hardwareBack?.(); }); expect(h.leave).toHaveBeenCalledTimes(2); }); }); + +vi.mock('@/session/SheetModal', () => ({ SheetModal: ({ visible, children }: any) => visible ? children : null })); + +vi.mock('@/session/PdfDocumentPreview', () => ({ PdfDocumentPreview: () => null })); + +vi.mock('@/session/ModelDocumentPreview', () => ({ ModelDocumentPreview: () => null })); + +vi.mock('@/session/BotGroupTextAttachmentPreview', () => ({ BotGroupTextAttachmentPreview: () => null, botGroupTextAttachmentPreviewKind: () => null })); +vi.mock('@/session/NativeVideoPlayer', () => ({ NativeVideoPlayer: () => null })); +vi.mock('@/session/mediaPlayerWebView', () => ({ RemoteMediaPlayerWebView: () => null })); diff --git a/apps/mobile/src/__tests__/BotGroupMessageAttachments.test.tsx b/apps/mobile/src/__tests__/BotGroupMessageAttachments.test.tsx index 415edac9f14..067732a0b11 100644 --- a/apps/mobile/src/__tests__/BotGroupMessageAttachments.test.tsx +++ b/apps/mobile/src/__tests__/BotGroupMessageAttachments.test.tsx @@ -83,8 +83,14 @@ vi.mock('@/session/expandedBlockMemory', () => ({ useFoldableExpandedState: () = vi.mock('@/platform/chrome', () => ({ NativePullDownMenu: () => null, showActionMenu: vi.fn(), usesNativePullDownMenu: () => false, usesSystemActionMenu: () => false })); vi.mock('@/session/MobileComposerInputRow', () => ({ MobileComposerInputRow: () => null, MOBILE_COMPOSER_VOICE_ANCHOR_RIGHT: 0, MOBILE_COMPOSER_CONTROL_SIZE: 44 })); vi.mock('@/session/ImageLightbox', async () => { - const { createElement } = await import('react'); - return { ImageLightbox: ({ initialUrl }: { initialUrl: string }) => createElement('div', { 'data-testid': 'lightbox', 'data-url': initialUrl }) }; + const { createElement, useEffect, useState } = await import('react'); + return { ImageLightbox: ({ initialUrl, resolveOpaqueSource, onResolveRemoteMedia }: any) => { + const [url, setUrl] = useState(resolveOpaqueSource ? '' : initialUrl); + useEffect(() => { + if (resolveOpaqueSource) void onResolveRemoteMedia({ url: initialUrl }, {}).then((value: any) => setUrl(value.url)); + }, []); + return createElement('div', { 'data-testid': 'lightbox', 'data-url': url }); + } }; }); vi.mock('@/session/McpAppCard', () => ({ McpAppCard: () => null })); vi.mock('@/session/mermaidWebView', () => ({ MermaidDiagram: () => null })); @@ -93,13 +99,16 @@ vi.mock('@/session/mediaPlayerWebView', () => ({ RemoteMediaPlayerWebView: () => vi.mock('@/session/BotGroupTextAttachmentPreview', async () => { const React = await import('react'); return { - ...await vi.importActual('@/session/BotGroupTextAttachmentPreview'), - BotGroupTextAttachmentPreview: ({ attachmentId, fileName, kind }: { attachmentId: string; fileName: string; kind: string }) => - React.createElement('div', { - 'data-testid': 'groupChat.server.textAttachmentPreview', - 'data-attachment-id': attachmentId, - 'data-kind': kind, - }, fileName), + botGroupTextAttachmentPreviewKind: (name: string) => /\.html?$/.test(name) ? 'html' : /\.md$/.test(name) ? 'markdown' : /\.json$/.test(name) ? 'json' : /\.txt$/.test(name) ? 'text' : null, + BotGroupTextAttachmentPreview: ({ fileName, kind, resolveDeviceMedia }: any) => { + const [url, setUrl] = React.useState(''); + React.useEffect(() => { + void resolveDeviceMedia({}, {}).then((value: any) => setUrl(value.url)); + }, []); + return React.createElement('div', { + 'data-testid': 'groupChat.server.textAttachmentPreview', 'data-url': url, 'data-kind': kind, + }, fileName); + }, }; }); vi.mock('@/session/MarkdownBlockContent', () => ({ MarkdownBlockContent: () => null })); @@ -123,8 +132,8 @@ afterEach(() => { act(() => root?.unmount()); root = undefined; host?.remove(); const imageUrl = 'xdt-image://cache/group-picture.png'; const thumbnailUrl = 'https://example.invalid/group-thumbnail.png'; -const resolveMedia = vi.fn(async () => ({ - url: thumbnailUrl, previewable: true, mimeType: 'image/png', +const resolveMedia = vi.fn(async (_media, options) => ({ + url: options?.front ? imageUrl : thumbnailUrl, previewable: true, mimeType: 'image/png', ossKey: 'thumbnail', size: 10, expiresAt: '', })); @@ -209,7 +218,7 @@ describe('group image attachments outside LegendList (#5278)', () => { expect(resolveMedia).toHaveBeenCalledWith( expect.objectContaining({ url: imageUrl, thumbnail: true }), expect.anything(), ); - act(() => (container.querySelector('[data-testid="message.mediaPreviewButton"]') as HTMLElement).click()); + await act(async () => (container.querySelector('[data-testid="message.mediaPreviewButton"]') as HTMLElement).click()); expect(container.querySelector('[data-testid="lightbox"]')?.getAttribute('data-url')).toBe(imageUrl); }); @@ -244,7 +253,7 @@ describe('group image attachments outside LegendList (#5278)', () => { expect(resolveMedia).toHaveBeenCalledTimes(2); expect(container.querySelector('img')).toBeNull(); expect(container.querySelector('[data-testid="message.mediaThumbFallback"]')).not.toBeNull(); - act(() => (container.querySelector('[data-testid="message.mediaPreviewButton"]') as HTMLElement).click()); + await act(async () => (container.querySelector('[data-testid="message.mediaPreviewButton"]') as HTMLElement).click()); expect(container.querySelector('[data-testid="lightbox"]')?.getAttribute('data-url')).toBe(imageUrl); }); @@ -289,13 +298,12 @@ describe('group attachments the computer has not fetched yet', () => { await act(async () => chips[1]!.click()); await act(async () => chips[0]!.click()); expect(resolveServerMedia.mock.calls.map(([id]) => id)).toEqual(['second', 'first']); - expect(vi.mocked(openBrowserAsync).mock.calls.map(([url]) => url)).toEqual([ - 'https://example.invalid/signed/second', 'https://example.invalid/signed/first', - ]); + expect(host.querySelector('[data-testid="groupChat.server.textAttachmentPreview"]')?.getAttribute('data-url')).toBe('https://example.invalid/signed/first'); + expect(openBrowserAsync).not.toHaveBeenCalled(); await act(async () => host!.querySelector('[data-testid="message.mediaPreviewButton"]')!.click()); - expect(resolveServerMedia).toHaveBeenLastCalledWith('photo'); + expect(resolveServerMedia).toHaveBeenLastCalledWith('photo', expect.any(AbortSignal)); expect(host.querySelector('[data-testid="lightbox"]')?.getAttribute('data-url')).toBe('https://example.invalid/signed/photo'); - expect(openBrowserAsync).toHaveBeenCalledTimes(2); + expect(openBrowserAsync).not.toHaveBeenCalled(); }); it('opens HTML, Markdown and JSON server files in the in-app preview instead of the browser', async () => { @@ -321,7 +329,8 @@ describe('group attachments the computer has not fetched yet', () => { await act(async () => chip.click()); const preview = host.querySelector('[data-testid="groupChat.server.textAttachmentPreview"]'); expect(preview?.textContent).toBe(name); - expect(preview?.getAttribute('data-attachment-id')).toBe(id); + expect(preview?.getAttribute('data-url')).toBe('https://example.invalid/signed/' + id); + expect(resolveServerMedia).toHaveBeenLastCalledWith(id, expect.any(AbortSignal)); expect(preview?.getAttribute('data-kind')).toBe(kind); } expect(openBrowserAsync).not.toHaveBeenCalled(); @@ -355,3 +364,9 @@ describe('group attachments the computer has not fetched yet', () => { expect(resolveMedia).not.toHaveBeenCalled(); }); }); + +vi.mock('expo-file-system', () => ({ File: class {} })); +vi.mock('expo-sharing', () => ({ shareAsync: vi.fn() })); +vi.mock('@/session/SheetModal', () => ({ SheetModal: () => null })); +vi.mock('@/session/PdfDocumentPreview', () => ({ PdfDocumentPreview: () => null })); +vi.mock('@/session/ModelDocumentPreview', () => ({ ModelDocumentPreview: () => null })); diff --git a/apps/mobile/src/__tests__/BotGroupTextAttachmentPreview.test.tsx b/apps/mobile/src/__tests__/BotGroupTextAttachmentPreview.test.tsx index 32c776c3147..020a42b050e 100644 --- a/apps/mobile/src/__tests__/BotGroupTextAttachmentPreview.test.tsx +++ b/apps/mobile/src/__tests__/BotGroupTextAttachmentPreview.test.tsx @@ -4,6 +4,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { createRoot, type Root } from 'react-dom/client'; import type { BotGroupAttachment } from '@cindy/maker-shared/botGroupChat'; import { openBrowserAsync } from 'expo-web-browser'; +import { writeClipboardText } from '@/session/messageActions'; import { withDownloadedRemoteMediaFile } from '@/session/remoteMediaDiskCacheExpo'; import { BotGroupTextAttachmentPreview, @@ -11,35 +12,28 @@ import { } from '@/session/BotGroupTextAttachmentPreview'; const downloadState = vi.hoisted(() => ({ text: '', tooLargeCount: 0, failedCount: 0 })); +const nativeList = vi.hoisted(() => ({ scrollToIndex: vi.fn() })); vi.mock('@/session/remoteMediaDiskCacheExpo', () => ({ withDownloadedRemoteMediaFile: vi.fn() })); vi.mock('expo-web-browser', () => ({ openBrowserAsync: vi.fn(async () => ({})) })); -vi.mock('lucide-react-native', () => ({ - ExternalLink: () => null, - X: () => null, -})); +vi.mock('lucide-react-native', () => Object.fromEntries(['ExternalLink','X','Copy','Search','WrapText','ListOrdered','ChevronLeft','ChevronRight','Ellipsis'].map(key => [key, () => null]))); vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => key }) })); -vi.mock('@/theme', () => ({ - fontWeight: { semibold: '600' }, - iconSize: { action: 20, md: 16 }, - lineHeight: { bodySmall: 20 }, - monoFont: 'monospace', - radius: { pill: 9999 }, - spacing: { xs: 4, sm: 8, md: 12, lg: 16, xl: 24 }, - textStyles: { body: {}, footnote: {}, bodySmall: {}, caption: {} }, - typeScale: { bodySmall: 15 }, - useTheme: () => ({ colors: { - border: 'border', - surface: 'surface', - surfaceElevated: 'surface-elevated', - textPrimary: 'primary', - textSecondary: 'secondary', - textTertiary: 'tertiary', - } }), -})); +vi.mock('@/theme', async () => { + const tokens = await import('@/theme/tokens'); + return { ...tokens, monoFont: 'monospace', useTheme: () => ({ colors: tokens.lightColors }), useThemedStyles: (make: any) => make(tokens.lightColors) }; +}); +vi.mock('@legendapp/list/react-native', async () => ({ LegendList: (await import('react-native')).FlatList })); +vi.mock('@/session/messageActions', () => ({ writeClipboardText: vi.fn().mockResolvedValue(undefined) })); +vi.mock('expo-file-system', () => ({ File: class {} })); +vi.mock('expo-sharing', () => ({ shareAsync: vi.fn() })); +vi.mock('@/session/DiagramDocumentPreview', () => ({ DiagramDocumentPreview: () => null })); +vi.mock('@/session/SheetModal', () => ({ SheetModal: ({ visible, children }: any) => visible ? children : null })); vi.mock('@/components/AppText', async () => { const React = await import('react'); - return { Text: ({ children, testID }: any) => React.createElement('span', { 'data-testid': testID }, children) }; + return { + Text: ({ children, testID }: any) => React.createElement('span', { 'data-testid': testID }, children), + TextInput: (await import('react-native')).TextInput, + }; }); vi.mock('@/session/HtmlFileReader', async () => { const React = await import('react'); @@ -62,23 +56,33 @@ vi.mock('@/session/MarkdownFileReader', async () => { vi.mock('react-native-safe-area-context', async () => { const React = await import('react'); return { + useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }), SafeAreaProvider: ({ children }: any) => React.createElement('div', null, children), SafeAreaView: ({ children }: any) => React.createElement('div', null, children), }; }); vi.mock('react-native', async () => { const React = await import('react'); - const render = (tag: string) => ({ children, testID, accessibilityLabel, onPress }: any) => React.createElement(tag, { + const render = (tag: string) => ({ children, testID, accessibilityLabel, onPress, onChangeText, value }: any) => React.createElement(tag, { 'data-testid': testID, 'aria-label': accessibilityLabel, + value, + onChange: onChangeText ? (event: any) => onChangeText(event.target.value) : undefined, onClick: onPress, }, children); return { + Alert: { alert: vi.fn() }, + ScrollView: render('div'), + TextInput: render('input'), ActivityIndicator: render('span'), - FlatList: ({ data, renderItem, testID }: any) => React.createElement('div', { + FlatList: React.forwardRef((props: any, ref) => { + React.useImperativeHandle(ref, () => ({ scrollToIndex: nativeList.scrollToIndex })); + const { data, renderItem, testID } = props; + return React.createElement('div', { 'data-testid': testID, 'data-row-count': String(data.length), - }, ...data.slice(0, 5).map((item: string, index: number) => React.createElement(React.Fragment, { key: String(index) }, renderItem({ item, index })))), + }, ...data.slice(0, 5).map((item: string, index: number) => React.createElement(React.Fragment, { key: String(index) }, renderItem({ item, index })))); + }), Modal: ({ children, visible }: any) => visible ? React.createElement('div', { 'data-testid': 'modal' }, children) : null, Pressable: render('button'), StyleSheet: { create: (styles: unknown) => styles, hairlineWidth: 1 }, @@ -157,8 +161,8 @@ describe('bot-group text attachment preview kinds', () => { ['notes.md', 'markdown'], ['notes.markdown', 'markdown'], ['summary.json', 'json'], - ['page.xhtml', null], - ['source.ts', null], + ['page.xhtml', 'text'], + ['source.ts', 'text'], ['archive.zip', null], ] as const)('classifies %s as %s', (name, expected) => { expect(botGroupTextAttachmentPreviewKind(name)).toBe(expected); @@ -215,6 +219,8 @@ describe('BotGroupTextAttachmentPreview', () => { const { container } = await renderPreview('json', 'compact.json', '{"value":"' + 'x'.repeat(256_000) + '"}'); const list = container.querySelector('[data-testid="groupChat.server.attachmentJsonPreview"]'); expect(list?.textContent?.length).toBeLessThanOrEqual(4_097); + await act(async () => (container.querySelector('[aria-label="message.renderer.copyDetailContent"]') as HTMLButtonElement).click()); + expect(writeClipboardText).toHaveBeenLastCalledWith('{"value":"' + 'x'.repeat(256_000) + '"}'); expect(container.textContent).toContain('groupChat.server.attachmentPreviewTruncated'); }); @@ -226,6 +232,22 @@ describe('BotGroupTextAttachmentPreview', () => { expect(container.textContent).toContain('groupChat.server.attachmentPreviewTruncated'); }); + // The 10,001-line device fixture found the match but RN FlatList clamped + // jumps to its measured tail. Exercise the source reader's search-to-list + // boundary; actual LegendList positioning is verified on iOS and Android. + it('finds the distant visible line and asks the virtual list to position it', async () => { + const body = Array.from({ length: 10_001 }, (_, index) => String(index)).join('\n'); + const { container } = await renderPreview('json', 'large.json', body); + await act(async () => (container.querySelector('[aria-label="message.attachmentPreview.find"]') as HTMLButtonElement).click()); + const field = container.querySelector('input')!; + await act(async () => { + Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')!.set!.call(field, '9999'); + field.dispatchEvent(new Event('input', { bubbles: true })); + }); + await act(async () => (container.querySelector('[aria-label="message.attachmentPreview.next"]') as HTMLButtonElement).click()); + expect(nativeList.scrollToIndex).toHaveBeenLastCalledWith({ index: 9999, viewPosition: 0.3, animated: false }); + }); + it('shows an explicit empty state for an empty text attachment', async () => { const { container } = await renderPreview('json', 'empty.json', ''); expect(container.querySelector('[data-testid="groupChat.server.attachmentPreview.empty"]')).not.toBeNull(); diff --git a/apps/mobile/src/__tests__/CompanionProfileArtifacts.test.tsx b/apps/mobile/src/__tests__/CompanionProfileArtifacts.test.tsx index 22ff14a9e53..ee387b3e1b9 100644 --- a/apps/mobile/src/__tests__/CompanionProfileArtifacts.test.tsx +++ b/apps/mobile/src/__tests__/CompanionProfileArtifacts.test.tsx @@ -5,6 +5,7 @@ import { afterEach, expect, it, vi } from 'vitest'; import { CompanionProfileArtifacts } from '@/session/CompanionProfileArtifacts'; const state = vi.hoisted(() => ({ value: null as unknown, error: false, refresh: vi.fn() })); +vi.mock('@/utils/useGuardedPush', () => ({ useGuardedPush: () => vi.fn() })); vi.mock('@/session/useRemoteCompanionQuery', () => ({ useRemoteCompanionQuery: () => state })); vi.mock('react-native', async () => { const { createElement } = await import('react'); diff --git a/apps/mobile/src/__tests__/blurBackdropScrim.test.ts b/apps/mobile/src/__tests__/blurBackdropScrim.test.ts index 1d98c54266a..ef0b959dbf9 100644 --- a/apps/mobile/src/__tests__/blurBackdropScrim.test.ts +++ b/apps/mobile/src/__tests__/blurBackdropScrim.test.ts @@ -68,9 +68,9 @@ describe('BlurBackdrop scrim 双模式恒深 (用户定稿 2026-07-21)', () => { } }); - it('ImageLightbox 保持纯黑背板(媒体查看器惯例,不进遮罩口径)', () => { + it('ImageLightbox 使用主题表面背板,不叠加弹层遮罩', () => { const src = readTextLf(resolve(process.cwd(), 'src/session/ImageLightbox.tsx'), 'utf8'); - expect(src).toContain("backgroundColor: '#000000'"); + expect(src).toContain("backgroundColor: colors.surface"); expect(src).not.toContain('colors.overlay'); }); }); diff --git a/apps/mobile/src/__tests__/botGroupAttachmentLightbox.test.tsx b/apps/mobile/src/__tests__/botGroupAttachmentLightbox.test.tsx index ed8f712a5f1..f8c2db23d49 100644 --- a/apps/mobile/src/__tests__/botGroupAttachmentLightbox.test.tsx +++ b/apps/mobile/src/__tests__/botGroupAttachmentLightbox.test.tsx @@ -23,9 +23,18 @@ const runtime = vi.hoisted(() => ({ imageEvents: new Map void; onError?: () => void }>(), })); const openBrowserAsync = vi.hoisted(() => vi.fn(async (_url: string) => ({}))); +vi.mock("expo-sharing", () => ({ shareAsync: vi.fn() })); +vi.mock("expo-file-system", () => ({ + File: class { + exists = false; + }, +})); +vi.mock("@/session/SheetModal", () => ({ + SheetModal: ({ visible, children }: any) => (visible ? children : null), +})); vi.mock('expo-web-browser', () => ({ openBrowserAsync })); vi.mock('react-i18next', async (importOriginal) => ({ - ...await importOriginal(), + ...(await importOriginal()), useTranslation: () => ({ t: (key: string) => key }), })); vi.mock('react-native', async () => { @@ -56,8 +65,19 @@ vi.mock('lucide-react-native', () => Object.fromEntries([ 'ArrowLeftRight', 'ArrowUp', 'Bot', 'Check', 'ChevronDown', 'ChevronRight', 'ChevronUp', 'Circle', 'CircleAlert', 'CircleCheck', 'CircleDashed', 'CircleStop', 'Copy', 'Ellipsis', 'ExternalLink', 'File', 'Ghost', 'Layers', 'ListTodo', 'LoaderCircle', 'MessageSquarePlus', 'Pen', 'PencilLine', 'RefreshCw', 'Send', 'Share', 'Sparkles', 'Split', 'Timer', 'Trash2', 'TriangleAlert', - 'Undo2', 'X', -].map(name => [name, () => null]))); + 'Undo2', + "Redo2", + "X", + "Minus", + "Plus", + "Scan", + "Maximize", + "ChevronLeft", + "Search", + "WrapText", + "ListOrdered", + "Code", +].map((name) => [name, () => null]))); vi.mock('react-native-svg', () => ({ default: () => null, Circle: () => null, Path: () => null })); vi.mock('react-native-uitextview', () => ({ UITextView: () => null })); vi.mock('expo-image', async () => { @@ -75,7 +95,9 @@ vi.mock('@/device-link/DeviceLinkContext', () => ({ useDeviceLink: () => ({ status: 'offline', connectionEpoch: 0, getPresenceAvailability: () => false, invoke: vi.fn(), openLink: vi.fn() }), subscribeRemoteBotChanges: () => () => {}, })); -vi.mock('react-native-safe-area-context', () => ({ useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }) })); +vi.mock('react-native-safe-area-context', () => ({ + SafeAreaProvider: ({ children }: any) => children, + useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }) })); vi.mock('@/theme', async () => { const tokens = await import('@/theme/tokens'); const colors = new Proxy({}, { get: () => '#777777' }); @@ -99,10 +121,10 @@ vi.mock('@/session/AuthorizationMessageCard', () => ({ AuthorizationMessageCard: vi.mock('@/session/CompanionMessageActions', () => ({ CompanionMessageActions: () => null })); vi.mock('@/session/CompanionMessageCard', () => ({ CompanionMessageCard: () => null })); vi.mock('@/session/PendingSendBubble', () => ({ PendingSendBubble: () => null })); -vi.mock('@/session/messageActions', async (original) => ({ ...await original(), copyMessageText: vi.fn(), writeClipboardText: vi.fn() })); +vi.mock('@/session/messageActions', async (original) => ({ ...(await original()), copyMessageText: vi.fn(), writeClipboardText: vi.fn() })); vi.mock('@/platform/gestureHandler', async () => { const { View } = await import('react-native'); - const make = (): any => new Proxy({}, { get: (_target, key) => key === 'then' ? undefined : () => make() }); + const make = (): any => new Proxy({}, { get: (_target, key) => (key === 'then' ? undefined : () => make()) }); return { GestureHandlerRootView: View, GestureDetector: ({ children }: { children: unknown }) => children, @@ -174,9 +196,9 @@ describe('Chat Server group image attachments (#5875)', () => { const resolveServerMedia = vi.fn(async (id: string) => ({ ...serverAttachment, id, category: 'image' as const, url: `${signedImageUrl}#${id}` })); const container = await render({}, visibility); await act(async () => root!.render(createElement(MessageListVisibilityContext.Provider, { value: visibility }, - ...['history', 'latest'].map(messageId => createElement(BotGroupMessageAttachments, { + ...['history', 'latest'].map((messageId) => createElement(BotGroupMessageAttachments, { key: messageId, messageId, align: 'right', resolveServerMedia, onResolveRemoteMedia: vi.fn(), - attachments: (messageId === 'history' ? ['old-1', 'old-2'] : ['latest']).map(id => ({ ...serverAttachment, id })), + attachments: (messageId === 'history' ? ['old-1', 'old-2'] : ['latest']).map((id) => ({ ...serverAttachment, id })), })), ))); expect(resolveServerMedia).not.toHaveBeenCalled(); @@ -202,7 +224,9 @@ describe('Chat Server group image attachments (#5875)', () => { expect(resolveServerMedia).toHaveBeenCalledTimes(1); await click(container.querySelector('[data-testid="message.mediaPreviewButton"]')); - expect(resolveServerMedia).toHaveBeenCalledWith('media-1'); + expect(resolveServerMedia).toHaveBeenCalledWith('media-1', + expect.any(AbortSignal), + ); const viewer = container.querySelector('[data-testid="message.imageLightbox"]'); expect(viewer?.querySelector('img')?.getAttribute('src')).toBe(signedImageUrl); expect(container.textContent).not.toContain(loadFailedKey); @@ -259,7 +283,7 @@ describe('Chat Server group image attachments (#5875)', () => { let finishRefresh!: (value: BotGroupAttachment) => void; const resolveServerMedia = vi.fn<() => Promise>() .mockResolvedValueOnce({ ...serverAttachment, category: 'image', url: signedImageUrl }) - .mockImplementationOnce(() => new Promise(resolve => { finishRefresh = resolve; })); + .mockImplementationOnce(() => new Promise((resolve) => { finishRefresh = resolve; })); const container = await render({ resolveServerMedia }, visibility); await act(async () => visibility.update([])); await act(async () => runtime.imageEvents.get(signedImageUrl)!.onError?.()); @@ -273,7 +297,7 @@ describe('Chat Server group image attachments (#5875)', () => { // PR #7145 r4371557412: the first automatic request has no cached preview. // A successful tap must populate the group list even if that request fails or hangs. - it.each(['late-success', 'late-failure', 'already-failed'])('keeps the tapped preview before initial authorization completes (%s)', async outcome => { + it.each(['late-success', 'late-failure', 'already-failed'])('keeps the tapped preview before initial authorization completes (%s)', async (outcome) => { let finishInitial!: (value: BotGroupAttachment) => void; let failInitial!: (error: Error) => void; const tappedUrl = `${signedImageUrl}&tap=initial`; @@ -303,13 +327,13 @@ describe('Chat Server group image attachments (#5875)', () => { const refreshedUrl = `${signedImageUrl}&retry=initial`; let targetCalls = 0; const resolveServerMedia = vi.fn((id: string): Promise => { - if (id !== serverAttachment.id) return new Promise(resolve => finishNeighbours.push(resolve)); + if (id !== serverAttachment.id) return new Promise((resolve) => finishNeighbours.push(resolve)); targetCalls++; if (targetCalls === 1) return new Promise((_resolve, reject) => { failInitial = reject; }); return Promise.resolve({ ...serverAttachment, category: 'image', url: targetCalls === 2 ? tappedUrl : refreshedUrl }); }); const container = await render({ resolveServerMedia, - attachments: [serverAttachment, ...['neighbour-1', 'neighbour-2'].map(id => ({ ...serverAttachment, id }))] }); + attachments: [serverAttachment, ...['neighbour-1', 'neighbour-2'].map((id) => ({ ...serverAttachment, id }))] }); try { await click(container.querySelector('[data-testid="message.filePreviewButton"]')); await click(container.querySelector('[data-testid="message.imageLightboxCloseButton"]')); @@ -329,7 +353,7 @@ describe('Chat Server group image attachments (#5875)', () => { const tappedUrl = `${signedImageUrl}&tap=1`; const resolveServerMedia = vi.fn<() => Promise>() .mockResolvedValueOnce({ ...serverAttachment, category: 'image', url: signedImageUrl }) - .mockImplementationOnce(() => new Promise(resolve => { finishRefresh = resolve; })) + .mockImplementationOnce(() => new Promise((resolve) => { finishRefresh = resolve; })) .mockResolvedValueOnce({ ...serverAttachment, category: 'image', url: tappedUrl }); const container = await render({ resolveServerMedia }); await act(async () => runtime.imageEvents.get(signedImageUrl)!.onError?.()); @@ -361,7 +385,7 @@ describe('Chat Server group image attachments (#5875)', () => { const resolveServerMedia = vi.fn(async (id: string, signal?: AbortSignal) => { if (id === 'neighbour') { neighbourSignal = signal; - return new Promise(resolve => { finishNeighbour = resolve; }); + return new Promise((resolve) => { finishNeighbour = resolve; }); } return { ...serverAttachment, category: 'image' as const, url: signedImageUrl }; }); @@ -391,14 +415,18 @@ describe('Chat Server group image attachments (#5875)', () => { expect(resolveServerMedia).toHaveBeenCalledTimes(2); }); - it('opens a non-image server file through its short-lived download address, not the viewer', async () => { + it("opens an authorized PDF in the common viewer without sending its URL to another browser", async () => { const fileUrl = 'https://chat.example.invalid/media/report.pdf?signature=placeholder'; const resolveServerMedia = vi.fn(async (id: string): Promise => ({ id, name: 'report.pdf', category: 'file', mimeType: 'application/pdf', size: 10, url: fileUrl, path: null, })); const container = await render({ resolveServerMedia, attachments: [{ ...serverAttachment, name: 'report.pdf' }] }); await click(container.querySelector('[data-testid^="message.filePreviewButton"]')); - expect(openBrowserAsync).toHaveBeenCalledWith(fileUrl); + expect(openBrowserAsync).not.toHaveBeenCalled(); + expect( + container.querySelector('[data-testid="preview.header"]'), + ).not.toBeNull(); + expect(container.textContent).toContain("report.pdf"); expect(container.querySelector('[data-testid="message.imageLightbox"]')).toBeNull(); }); @@ -417,23 +445,23 @@ describe('Chat Server group image attachments (#5875)', () => { const visibility = new MessageListVisibility(); visibility.update([{ key: 'message-1', isViewable: true }]); const pending: Array<(value: BotGroupAttachment) => void> = []; - const resolveServerMedia = vi.fn(() => new Promise(resolve => pending.push(resolve))); + const resolveServerMedia = vi.fn(() => new Promise((resolve) => pending.push(resolve))); const attachments = Array.from({ length: 4 }, (_, index) => ({ ...serverAttachment, id: `media-${index}` })); const container = await render({ resolveServerMedia, attachments }, visibility); expect(resolveServerMedia).toHaveBeenCalledTimes(3); await act(async () => visibility.update([])); - await act(async () => pending.forEach(resolve => resolve({ ...serverAttachment, category: 'image', url: signedImageUrl }))); + await act(async () => pending.forEach((resolve) => resolve({ ...serverAttachment, category: 'image', url: signedImageUrl }))); expect(resolveServerMedia).toHaveBeenCalledTimes(3); expect(container.querySelector('img')).toBeNull(); }); // PR #7145 r4371434693: three stalled requests must not block another page // until their 20s timeout. Unit through the real strip; old tests settled them first. - it.each(['message', 'account'])('aborts old preview jobs and frees slots before stale requests settle (%s)', async change => { + it.each(['message', 'account'])('aborts old preview jobs and frees slots before stale requests settle (%s)', async (change) => { const signals: Array = []; const finishes: Array<(value: BotGroupAttachment) => void> = []; const oldResolver = vi.fn((_id: string, signal?: AbortSignal) => { - signals.push(signal); return new Promise(resolve => finishes.push(resolve)); + signals.push(signal); return new Promise((resolve) => finishes.push(resolve)); }); const container = await render({ resolveServerMedia: oldResolver, attachments: Array.from({ length: 4 }, (_, n) => ({ ...serverAttachment, id: `old-${n}` })) }); @@ -446,11 +474,11 @@ describe('Chat Server group image attachments (#5875)', () => { attachments: [serverAttachment], onResolveRemoteMedia: vi.fn(), resolveServerMedia: nextResolver, }))); try { - expect(signals.every(signal => signal?.aborted)).toBe(true); + expect(signals.every((signal) => signal?.aborted)).toBe(true); expect(nextResolver).toHaveBeenCalledTimes(1); expect(container.querySelector('img')?.getAttribute('src')).toBe(nextUrl); } finally { - await act(async () => finishes.forEach(finish => finish({ ...serverAttachment, category: 'image', url: signedImageUrl }))); + await act(async () => finishes.forEach((finish) => finish({ ...serverAttachment, category: 'image', url: signedImageUrl }))); } expect(oldResolver).toHaveBeenCalledTimes(3); expect(container.querySelector('img')?.getAttribute('src')).toBe(nextUrl); @@ -458,8 +486,8 @@ describe('Chat Server group image attachments (#5875)', () => { it('bounds preview authorization and ignores late results when the message changes', async () => { const pending: Array<(value: BotGroupAttachment) => void> = []; - const resolveServerMedia = vi.fn((id: string) => new Promise(resolve => { - pending.push(value => resolve({ ...value, id })); + const resolveServerMedia = vi.fn((id: string) => new Promise((resolve) => { + pending.push((value) => resolve({ ...value, id })); })); const attachments = Array.from({ length: 8 }, (_, index) => ({ ...serverAttachment, id: `media-${index}` })); const container = await render({ resolveServerMedia, attachments }); @@ -467,7 +495,7 @@ describe('Chat Server group image attachments (#5875)', () => { await act(async () => root!.render(createElement(BotGroupMessageAttachments, { messageId: 'replacement', align: 'right', attachments: [], onResolveRemoteMedia: vi.fn(), resolveServerMedia, }))); - await act(async () => pending.forEach(resolve => resolve({ ...serverAttachment, category: 'image', url: signedImageUrl }))); + await act(async () => pending.forEach((resolve) => resolve({ ...serverAttachment, category: 'image', url: signedImageUrl }))); expect(resolveServerMedia).toHaveBeenCalledTimes(3); expect(container.querySelector('img')).toBeNull(); }); diff --git a/apps/mobile/src/__tests__/chatFileChipMenuModel.test.ts b/apps/mobile/src/__tests__/chatFileChipMenuModel.test.ts index 01b1b98af39..877277665da 100644 --- a/apps/mobile/src/__tests__/chatFileChipMenuModel.test.ts +++ b/apps/mobile/src/__tests__/chatFileChipMenuModel.test.ts @@ -47,9 +47,10 @@ describe('chatFileChipMenuModel', () => { ]); }); - it('Android 的 PDF 不给「快速预览」(WebView 不渲染 PDF),仍可分享', () => { + it('Android 的 PDF 提供应用内预览和分享', () => { const pdf: ChatFilePathTarget = { kind: 'file', relPath: 'documents/报告.PDF', absPath: '/w/documents/报告.PDF' }; expect(chatFileChipMenuRows(pdf, 'android').map((r) => r.key)).toEqual([ + 'open', 'revealInBrowser', 'sendToSession', 'copyPath', diff --git a/apps/mobile/src/__tests__/fileChipNameWidth.test.tsx b/apps/mobile/src/__tests__/fileChipNameWidth.test.tsx index daf9b35810e..d9051132ba8 100644 --- a/apps/mobile/src/__tests__/fileChipNameWidth.test.tsx +++ b/apps/mobile/src/__tests__/fileChipNameWidth.test.tsx @@ -171,3 +171,5 @@ describe('file chip names keep their width when the chip is content-sized (#5875 } }); }); + +vi.mock('@/session/RemoteAttachmentPreview', () => ({ RemoteAttachmentPreview: () => null })); diff --git a/apps/mobile/src/__tests__/filePreviewAndroidPdf.test.tsx b/apps/mobile/src/__tests__/filePreviewAndroidPdf.test.tsx index 63b80edfae2..8cc587ca676 100644 --- a/apps/mobile/src/__tests__/filePreviewAndroidPdf.test.tsx +++ b/apps/mobile/src/__tests__/filePreviewAndroidPdf.test.tsx @@ -1,13 +1,12 @@ // @vitest-environment jsdom -import { act, type ReactNode } from 'react'; +import { createElement, act, type ReactNode } from 'react'; import { createRoot, type Root } from 'react-dom/client'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import Screen from '../../app/files/preview/[sessionId]'; import { storeCachedListing } from '@/session/fileBrowserCache'; -// Integration: Android WebView cannot render PDFs. The real preview screen must -// show the export/share page instead, fetch nothing until the user taps it, and -// then hand the original file name and PDF mime type to the system share sheet. +// Integration: Android authorizes the PDF and mounts the shared reader; +// saving still shares the original filename and MIME type. const state = vi.hoisted(() => ({ scope: '', listDir: vi.fn(), @@ -19,15 +18,19 @@ const state = vi.hoisted(() => ({ })); vi.mock('react-native', async () => { const { createElement } = await import('react'); - const View = ({ children, testID }: { children?: ReactNode; testID?: string }) => + const View = ({ children, testID }: { children?: ReactNode; testID?: string; + }) => createElement('div', { 'data-testid': testID }, children); - const Pressable = ({ children, onPress, testID }: { children?: ReactNode; onPress?: () => void; testID?: string }) => + const Pressable = ({ children, onPress, testID }: { children?: ReactNode; onPress?: () => void; testID?: string; + }) => createElement('button', { 'data-testid': testID, onClick: onPress }, children); return { - View, Text: View, Pressable, ActivityIndicator: () => null, Alert: {}, Platform: { OS: 'android' }, + View, Text: View, + ScrollView: View, Pressable, ActivityIndicator: () => null, Alert: {}, Platform: { OS: 'android' }, StyleSheet: { create: (styles: unknown) => styles, hairlineWidth: 1 }, useWindowDimensions: () => ({ width: 390, height: 844 }), - FlatList: (props: { data: unknown[]; renderItem: (row: { item: unknown; index: number }) => ReactNode }) => { + FlatList: (props: { data: unknown[]; renderItem: (row: { item: unknown; index: number }) => ReactNode; + }) => { state.pager = props; return createElement('div', null, props.data.map((item, index) => createElement('div', { key: index }, props.renderItem({ item, index })))); }, @@ -63,11 +66,29 @@ vi.mock('@/theme', async () => { return { ...tokens, monoFont: 'monospace', useTheme: () => ({ colors: tokens.darkColors }), useThemedStyles: (make: any) => make(tokens.darkColors) }; }); vi.mock('@react-native-async-storage/async-storage', () => ({ default: { getItem: async () => null, setItem: async () => {}, multiRemove: async () => {} } })); -vi.mock('lucide-react-native', () => Object.fromEntries(['ArrowDownToLine', 'Copy', 'Database', 'File', 'Info', 'MessageSquarePlus', 'Share'].map(key => [key, () => null]))); +vi.mock('lucide-react-native', () => Object.fromEntries(['ArrowDownToLine', 'Copy', 'Database', 'File', 'Info', 'MessageSquarePlus', 'Share'].map((key) => [key, () => null]))); vi.mock('expo-clipboard', () => ({})); vi.mock('expo-image', () => ({ Image: () => null })); vi.mock('expo-sharing', () => ({ shareAsync: state.shareAsync })); -vi.mock('react-native-webview', () => ({ WebView: () => { throw new Error('Android PDF page must not mount a WebView'); } })); +vi.mock('react-native-webview', () => ({ WebView: () => null })); +vi.mock("expo-file-system", () => ({ + File: class { + exists = false; + }, +})); +vi.mock("@/session/ModelDocumentPreview", () => ({ + ModelDocumentPreview: () => null, +})); +vi.mock("@/session/DiagramDocumentPreview", () => ({ + DiagramDocumentPreview: () => null, +})); +vi.mock("@/session/TextDocumentPreview", () => ({ + TextDocumentPreview: () => null, +})); +vi.mock("@/session/PdfDocumentPreview", () => ({ + PdfDocumentPreview: ({ uri }: { uri: string }) => + createElement("div", { "data-testid": "pdf.reader", "data-uri": uri }), +})); vi.mock('@/debug/mobileDebugLog', () => ({ mobileDebugLog: () => {} })); vi.mock('@/session/remoteAbsFileFetch', () => ({})); vi.mock('@/session/pdfPreviewSource', () => ({ @@ -77,7 +98,13 @@ vi.mock('@/session/fileBrowserExport', () => ({ exportRemoteFileToUrl: state.exp vi.mock('@/session/FileTransferBusyNotice', () => ({ FileTransferBusyNotice: () => null })); vi.mock('@/session/HtmlFileReader', () => ({})); vi.mock('@/session/HtmlPreviewLoading', () => ({})); -vi.mock('@/session/HtmlBrowserChrome', () => ({ HtmlBrowserChrome: () => null })); +vi.mock('@/session/HtmlBrowserChrome', () => ({ HtmlBrowserChrome: ({ onShare }: { onShare: () => void }) => + createElement("button", { + "data-testid": "preview.share", + onClick: onShare, + }), +})); +vi.mock("@/session/PreviewChrome", () => ({ PreviewFooter: () => null })); vi.mock('@/session/HtmlWebsiteBrowser', () => ({})); vi.mock('@/session/mobileHtmlPreview', () => ({})); vi.mock('@/session/useHtmlSnapshot', () => ({})); @@ -108,21 +135,24 @@ beforeEach(() => { afterEach(() => { act(() => root.unmount()); vi.unstubAllGlobals(); }); describe('Android PDF preview', () => { - it('shows the export/share page without fetching, then shares the original PDF on tap', async () => { + it("previews the authorized PDF on Android and shares the original on tap", async () => { await act(async () => { root.render(); }); - expect(host.querySelector('[data-testid="filePreview.unsupported"]')).not.toBeNull(); - expect(host.querySelector('[data-testid="filePreview.pdfLoading"]')).toBeNull(); - expect(host.textContent).toContain("PDFs can't be previewed inside Cindy on Android yet."); - expect(state.exportFile).not.toHaveBeenCalled(); - // No WebView owns horizontal drags on Android, so the pager keeps swiping to neighbours. - expect(state.pager.scrollEnabled).toBe(true); + expect(host.querySelector('[data-testid="filePreview.unsupported"]')).toBeNull(); + expect(host.querySelector('[data-testid="pdf.reader"]') + ?.getAttribute("data-uri"), + ).toBe("https://oss.example.invalid/report.pdf"); + expect(state.exportFile).toHaveBeenCalledOnce(); + expect(state.shareAsync).not.toHaveBeenCalled(); + expect(state.pager.scrollEnabled).toBe(false); - const share = host.querySelector('[data-testid="filePreview.unsupportedDownload"]'); + const share = host.querySelector( + '[data-testid="preview.share"]', + ); expect(share).not.toBeNull(); await act(async () => { share!.click(); }); - expect(state.exportFile).toHaveBeenCalledOnce(); + expect(state.exportFile).toHaveBeenCalledTimes(2); expect(state.exportFile.mock.calls[0]?.slice(1)).toEqual(['/repo', 'docs/report.pdf', 7]); expect(state.shareTemp).toHaveBeenCalledWith( 'https://oss.example.invalid/report.pdf', 'application/pdf', 'report.pdf', expect.anything(), diff --git a/apps/mobile/src/__tests__/filePreviewCacheRendering.test.tsx b/apps/mobile/src/__tests__/filePreviewCacheRendering.test.tsx index 5899df98350..3c3d8b7b481 100644 --- a/apps/mobile/src/__tests__/filePreviewCacheRendering.test.tsx +++ b/apps/mobile/src/__tests__/filePreviewCacheRendering.test.tsx @@ -12,6 +12,7 @@ const state = vi.hoisted(() => ({ listDir: vi.fn(), scroll: vi.fn(), pager: null as any, + footer: null as any, params: { sessionId: 'task', deviceId: 'device', relPath: 'README.md' }, })); vi.mock('react-native', async () => { @@ -119,8 +120,12 @@ function expectVisiblePage(relPath: string, meta: string) { }) as PageProps[]; const visiblePages = pages.filter(page => page.visible); expect(visiblePages).toHaveLength(1); + const [position, count] = meta.split(' · ')[0]!.split(' / ').map(Number); + expect(state.footer.collection.index).toBe(position! - 1); + expect(state.footer.collection.names).toHaveLength(count!); + expect(state.footer.collection.names[position! - 1]).toBe(relPath); expect(visiblePages[0]).toMatchObject({ - active: true, item: { name: relPath, relPath }, htmlBrowserActions: { meta }, + active: true, item: { name: relPath, relPath }, htmlBrowserActions: { meta: meta.split(' · ')[1] ?? '' }, }); } describe('cached file preview presentation', () => { @@ -168,3 +173,10 @@ describe('cached file preview presentation', () => { expect(state.scroll).toHaveBeenCalledExactlyOnceWith({ animated: false, offset: 2 * 390 }); }); }); + +vi.mock('expo-file-system', () => ({ File: class {} })); +vi.mock('@/session/TextDocumentPreview', () => ({ TextDocumentPreview: () => null })); +vi.mock('@/session/PdfDocumentPreview', () => ({ PdfDocumentPreview: () => null })); +vi.mock('@/session/ModelDocumentPreview', () => ({ ModelDocumentPreview: () => null })); +vi.mock('@/session/DiagramDocumentPreview', () => ({ DiagramDocumentPreview: () => null })); +vi.mock('@/session/PreviewChrome', async () => ({ PreviewFooter: (props: any) => { state.footer = props; return null; }, PreviewCollectionContext: (await import('react')).createContext(undefined) })); diff --git a/apps/mobile/src/__tests__/filePreviewPagerWiring.test.ts b/apps/mobile/src/__tests__/filePreviewPagerWiring.test.ts index e25a27183ff..426ebe16901 100644 --- a/apps/mobile/src/__tests__/filePreviewPagerWiring.test.ts +++ b/apps/mobile/src/__tests__/filePreviewPagerWiring.test.ts @@ -42,7 +42,7 @@ describe('remote file preview pager wiring', () => { it('exposes source readiness only on the visible loaded text page', () => { const textPage = source.slice(source.indexOf('function TextPreviewPage('), source.indexOf('function ImagePreviewPage(')); - const sourceList = textPage.match(/ { it('HTML 渲染态同样让出外层横滑,让内层 WebView 能横向平移', () => { // 固定宽度布局 / 放大后需要横向平移,pager 抢走手势就永远看不到超出视口的内容。 - expect(source).toContain("scrollEnabled={!(current.previewKind === 'pdf' && PDF_PREVIEWS_INLINE) && htmlPanPageKey !== current.key && markdownPagerPageKey !== current.key}"); + expect(source).toMatch(/scrollEnabled=\{[^}]*!\(current.previewKind === 'pdf' && PDF_PREVIEWS_INLINE\) && htmlPanPageKey !== current.key && markdownPagerPageKey !== current.key\}/); const markdownReader = readSource('src/session/MarkdownFileReader.tsx'); expect(markdownReader).toContain('injectedJavaScript={onPageSwipe ? MARKDOWN_FILE_PAGER_SCRIPT : undefined}'); expect(markdownReader).toContain('onMessage={onPageSwipe ? handleMessage : undefined}'); @@ -79,7 +79,13 @@ describe('remote file preview pager wiring', () => { expect(source).not.toContain('keyExtractor={(item) => `${item.key}:${pageIndex}'); expect(source).not.toContain(' (url ? { uri: url } : null), [url]);'); - expect(source).toContain(''); + const reader = readSource('src/session/PdfDocumentPreview.tsx'); + expect(reader).toContain('key={`${uri}:${retry}`}'); + expect(reader).toContain('source={{ html }}'); + const document = reader.slice(reader.indexOf('const html = useMemo('), reader.indexOf('const command =')); + expect(document).toContain('[base64, colors.surface, colors.textPrimary]'); + expect(document).not.toMatch(/\b(?:status|zoom|page)\b/); }); it('retries a failed PDF after device recovery without remounting a loaded WebView', () => { diff --git a/apps/mobile/src/__tests__/imageLightboxInteraction.test.tsx b/apps/mobile/src/__tests__/imageLightboxInteraction.test.tsx index b1f09d7c9ea..26347b75181 100644 --- a/apps/mobile/src/__tests__/imageLightboxInteraction.test.tsx +++ b/apps/mobile/src/__tests__/imageLightboxInteraction.test.tsx @@ -30,7 +30,8 @@ type GestureNode = { type Animation = { target: number; done?: (finished: boolean) => void; - decay?: { velocity: number; clamp: [number, number]; rubberBandEffect?: boolean }; + decay?: { velocity: number; clamp: [number, number]; rubberBandEffect?: boolean; + }; }; type Value = { value: number; animation?: Animation }; const runtime = vi.hoisted(() => ({ @@ -43,6 +44,11 @@ const runtime = vi.hoisted(() => ({ alerts: [] as Array<[string, string, Array<{ text: string; style?: string; onPress?: () => void }>]>, scrollToOffset: vi.fn(), renderPageIndex: 0, + collectionSize: 2, +})); + +vi.mock("@/session/SheetModal", () => ({ + SheetModal: ({ visible, children }: any) => (visible ? children : null), })); vi.mock("expo-router", () => ({ @@ -52,7 +58,7 @@ vi.mock('@/session/VideoDownloadButton', () => ({ VideoDownloadButton: (props: a runtime.nodes.set('message.videoDownload', props); return null; } })); vi.mock("react-i18next", async (importOriginal) => ({ - ...await importOriginal(), + ...(await importOriginal()), useTranslation: () => ({ t: (key: string) => key }), })); vi.mock("react-native-safe-area-context", () => ({ @@ -65,6 +71,13 @@ vi.mock("lucide-react-native", () => ({ Undo2: () => null, X: () => null, CircleAlert: () => null, + ChevronLeft: () => null, + ChevronRight: () => null, + Ellipsis: () => null, + Minus: () => null, + Plus: () => null, + Maximize: () => null, + Redo2: () => null, })); vi.mock("react-native-svg", () => ({ default: () => null, Path: () => null })); vi.mock("@/theme", async () => { @@ -80,6 +93,7 @@ vi.mock("react-native", async () => { }; return { View: view("View"), + ScrollView: view("ScrollView"), Pressable: view("Pressable"), Modal: view("Modal"), Text: view("Text"), @@ -93,7 +107,16 @@ vi.mock("react-native", async () => { absoluteFill: {}, hairlineWidth: 1, }, - useWindowDimensions: () => runtime.dimensions, + // Gesture scenarios use a fixed canvas; add the shared chrome outside it. + useWindowDimensions: () => ({ + ...runtime.dimensions, + height: + runtime.dimensions.height + + runtime.insets.top + + runtime.insets.bottom + + 64 + + (runtime.collectionSize > 1 ? 112 : 56), + }), FlatList: (props: any) => { runtime.nodes.set("FlatList", props); useImperativeHandle( @@ -239,7 +262,7 @@ beforeEach(() => { runtime.alerts = []; runtime.scrollToOffset.mockClear(); runtime.renderPageIndex = 0; - root = createRoot(container = document.createElement("div")); + root = createRoot((container = document.createElement("div"))); }); afterEach(() => act(() => root.unmount())); @@ -264,7 +287,10 @@ function mount(overrides: Partial = {}) { onClose: vi.fn(), ...overrides, }; - const render = () => act(() => root.render()); + const render = () => { + runtime.collectionSize = props.images.length; + act(() => root.render()); + }; render(); return { props, render }; } @@ -391,7 +417,7 @@ describe('pending image gallery updates', () => { render(); // Production's synchronous dispatch observer captures the slot identity before queue materialization. rememberSentMessageImagePreviews(previews, 's', item.clientId, outboxItemAttachments(item), - () => phase === 'remote-only' ? null : 'file:///durable.jpg', (attachment) => { + () => (phase === 'remote-only' ? null : 'file:///durable.jpg'), (attachment) => { const slot = item.attachmentSlots.findIndex((entry) => entry?.id === attachment.id); return `${item.clientId}-slot-${slot}`; }); @@ -500,7 +526,7 @@ describe('pending image gallery updates', () => { }))); props.images = lightboxImagesForPayload(updated, clicked, 'pending', props.images); render(); - expect(runtime.nodes.get('message.imageLightboxPageLabel').children).toBe('2 / 2'); + expect(runtime.nodes.get("preview.collectionCount").children).toBe('2 / 2'); await act(async () => runtime.nodes.get('message.imageLightboxShareButton').onPress()); expect(onShareImage.mock.lastCall?.[0].url).toBe(updated[1].url); // Paging away from the originally clicked page must also survive a row handoff. @@ -508,7 +534,7 @@ describe('pending image gallery updates', () => { props.images = lightboxImagesForPayload([], clicked, 'pending', props.images, false, true); render(); expect(runtime.nodes.get('FlatList').data).toHaveLength(2); - expect(runtime.nodes.get('message.imageLightboxPageLabel').children).toBe('1 / 2'); + expect(runtime.nodes.get("preview.collectionCount").children).toBe('1 / 2'); await act(async () => runtime.nodes.get('message.imageLightboxShareButton').onPress()); expect(onShareImage.mock.lastCall?.[0].url).toBe(updated[0].url); }); @@ -541,7 +567,7 @@ describe('pending image gallery updates', () => { for (const file of files) item = outboxItemWithUpload(item, file.id, file); const previews: SentMessageImagePreviews = new Map(); rememberSentMessageImagePreviews(previews, 's', item.clientId, outboxItemAttachments(item), - (file) => file.id === files[1].id ? 'file:///durable-1.jpg' : null, + (file) => (file.id === files[1].id ? 'file:///durable-1.jpg' : null), (file) => `${item.clientId}-slot-${item.attachmentSlots.findIndex((entry) => entry?.id === file.id)}`); const getPreview: GetSentMessageImagePreview = (...args) => getSentMessageImagePreview(previews, 's', ...args); if (proof === 'materialized queue') { @@ -692,7 +718,7 @@ describe('pending image gallery updates', () => { path: 'https://example.invalid/target.jpg' }; const previews: SentMessageImagePreviews = new Map(); if (local) rememberSentMessageImagePreviews(previews, 's', 'pending', [first, target], - (file) => file.id === target.id ? 'file:///target.jpg' : null, + (file) => (file.id === target.id ? 'file:///target.jpg' : null), (file) => `pending-slot-${file.id === target.id ? 1 : 0}`); const getPreview: GetSentMessageImagePreview = (...args) => getSentMessageImagePreview(previews, 's', ...args); const queued = (files: RemoteSerializedAttachment[]) => collectMobileMessageGalleryImages(buildPendingSendItems({ @@ -731,7 +757,7 @@ describe('pending image gallery updates', () => { const { props, render } = mount({ images: initial, initialUrl: initial[0].url }); props.images = collect(thumbs); render(); - expect(runtime.nodes.get('message.imageLightboxPageLabel').children).toBe('2 / 2'); + expect(runtime.nodes.get("preview.collectionCount").children).toBe('2 / 2'); expect(runtime.scrollToOffset).toHaveBeenLastCalledWith({ animated: false, offset: 400 }); }); @@ -758,7 +784,7 @@ describe('pending image gallery updates', () => { render(); const index = props.images.findIndex((image) => image.key === initial[1].key); expect(index).toBe(attachments.indexOf(files[1])); - expect(runtime.nodes.get('message.imageLightboxPageLabel').children).toBe(`${index + 1} / 2`); + expect(runtime.nodes.get("preview.collectionCount").children).toBe(`${index + 1} / 2`); await act(async () => runtime.nodes.get('message.imageLightboxShareButton').onPress()); expect(onShareImage.mock.lastCall?.[0].url).toBe(files[1].path); } @@ -771,9 +797,9 @@ describe('pending image gallery updates', () => { const { props, render } = mount({ images: initial, initialUrl: initial[0].url, onShareImage }); act(() => runtime.nodes.get('FlatList').onMomentumScrollEnd({ nativeEvent: { contentOffset: { x: index * 400 } } })); const replacement = { ...original[index], key: `${original[index].key}:replaced`, uri: 'https://example.invalid/replacement.jpg' }; - props.images = collect(original.map((thumb, slot) => slot === index ? replacement : thumb)); + props.images = collect(original.map((thumb, slot) => (slot === index ? replacement : thumb))); render(); - expect(runtime.nodes.get('message.imageLightboxPageLabel').children).toBe(`${index + 1} / 2`); + expect(runtime.nodes.get("preview.collectionCount").children).toBe(`${index + 1} / 2`); await act(async () => runtime.nodes.get('message.imageLightboxShareButton').onPress()); expect(onShareImage.mock.lastCall?.[0].url).toBe(replacement.uri); }); @@ -784,7 +810,7 @@ describe('pending image gallery updates', () => { const list = runtime.nodes.get('FlatList'); expect(list.initialScrollIndex).toBe(index); expect(list.renderItem({ item: images[index], index }).props.active).toBe(true); - expect(runtime.nodes.get('message.imageLightboxPageLabel').children).toBe(`${index + 1} / 2`); + expect(runtime.nodes.get("preview.collectionCount").children).toBe(`${index + 1} / 2`); }); it('keeps the replacement and its annotation when an earlier image becomes readable', async () => { @@ -832,7 +858,7 @@ describe('pending image gallery updates', () => { render(); const page = runtime.nodes.get('FlatList').renderItem({ item: props.images[1], index: 1 }).props; expect(page.active).toBe(true); - expect(runtime.nodes.get('message.imageLightboxPageLabel').children).toBe('2 / 2'); + expect(runtime.nodes.get("preview.collectionCount").children).toBe('2 / 2'); expect(runtime.scrollToOffset).toHaveBeenLastCalledWith({ animated: false, offset: 400 }); await act(async () => runtime.nodes.get('message.imageLightboxShareButton').onPress()); expect(onShareImage.mock.lastCall?.[0]).toBe(props.images[1].payload.media); @@ -883,7 +909,7 @@ describe('pending image gallery updates', () => { const replacement = { ...thumbs[1], key: 'replacement', uri: 'file:///replacement.jpg' }; props.images = collect([{ ...waiting[1], uri: 'file:///earlier.jpg' }, replacement]); render(); - expect(runtime.nodes.get('message.imageLightboxPageLabel').children).toBe('2 / 2'); + expect(runtime.nodes.get("preview.collectionCount").children).toBe('2 / 2'); await act(async () => runtime.nodes.get('message.imageLightboxShareButton').onPress()); expect(onShareImage.mock.lastCall?.[0]).toBe(props.images[1].payload.media); }); @@ -897,7 +923,7 @@ describe('pending image gallery updates', () => { render(); props.images = collect([replacement, thumbs[1], thumbs[0]]); render(); - expect(runtime.nodes.get('message.imageLightboxPageLabel').children).toBe('1 / 3'); + expect(runtime.nodes.get("preview.collectionCount").children).toBe('1 / 3'); await act(async () => runtime.nodes.get('message.imageLightboxShareButton').onPress()); expect(onShareImage.mock.lastCall?.[0].url).toBe(replacement.uri); }); @@ -910,10 +936,10 @@ describe('pending image gallery updates', () => { const { props, render } = mount({ images: initial, initialUrl: last.uri, onShareImage }); props.images = collect([thumbs[1], last]); render(); - expect(runtime.nodes.get('message.imageLightboxPageLabel').children).toBe('2 / 2'); + expect(runtime.nodes.get("preview.collectionCount").children).toBe('2 / 2'); props.images = collect([thumbs[1], replacement, thumbs[0]]); render(); - expect(runtime.nodes.get('message.imageLightboxPageLabel').children).toBe('2 / 3'); + expect(runtime.nodes.get("preview.collectionCount").children).toBe('2 / 3'); await act(async () => runtime.nodes.get('message.imageLightboxShareButton').onPress()); expect(onShareImage.mock.lastCall?.[0].url).toBe(replacement.uri); }); @@ -975,7 +1001,7 @@ describe('pending image gallery updates', () => { const images = lightboxImagesForPayload(updated, clicked, 'pending'); mount({ images, initialUrl: images[galleryImageIndexForPayload(images, clicked)]?.url ?? clicked.media.url }); expect(runtime.nodes.get('FlatList').initialScrollIndex).toBe(1); - expect(runtime.nodes.get('message.imageLightboxPageLabel').children).toBe('2 / 2'); + expect(runtime.nodes.get("preview.collectionCount").children).toBe('2 / 2'); }); it.each([false, true])('opens the clicked second slot after replacement before mounting (duplicate URL: %s)', async (duplicate) => { @@ -991,7 +1017,7 @@ describe('pending image gallery updates', () => { initialImageKey: initialImage?.key ?? clicked.galleryImageKey, initialUrl: initialImage?.url ?? clicked.media.url, onShareImage }); expect(runtime.nodes.get('FlatList').initialScrollIndex).toBe(1); - expect(runtime.nodes.get('message.imageLightboxPageLabel').children).toBe('2 / 2'); + expect(runtime.nodes.get("preview.collectionCount").children).toBe('2 / 2'); await act(async () => runtime.nodes.get('message.imageLightboxShareButton').onPress()); expect(onShareImage.mock.lastCall?.[0]).toBe(images[1].payload.media); @@ -999,7 +1025,7 @@ describe('pending image gallery updates', () => { props.images = [{ ...images[0], key: 'inserted', payload: { ...images[0].payload, galleryImageKey: 'inserted' } }, ...images]; render(); - expect(runtime.nodes.get('message.imageLightboxPageLabel').children).toBe('3 / 3'); + expect(runtime.nodes.get("preview.collectionCount").children).toBe('3 / 3'); await act(async () => runtime.nodes.get('message.imageLightboxShareButton').onPress()); expect(onShareImage.mock.lastCall?.[0]).toBe(images[1].payload.media); }); @@ -1042,7 +1068,7 @@ describe('pending image gallery updates', () => { expect(page().annotationStrokes).toEqual([]); expect(page().annotationDraftStroke).toBeNull(); expect(page().naturalSize).toBeNull(); - expect(runtime.nodes.get('message.imageLightboxPageLabel').children).toBe('1 / 2'); + expect(runtime.nodes.get("preview.collectionCount").children).toBe('1 / 2'); act(() => runtime.nodes.get('Image').onLoad({ source: { width: 800, height: 400 } })); fire(draw(), 'onStart', { x: 200, y: 400 }); fire(draw(), 'onFinalize', {}, true); @@ -1061,14 +1087,14 @@ describe('pending image gallery updates', () => { const { props, render } = mount({ images: initial, initialUrl: initial[1].url, onShareImage }); props.images = initial.map((image) => ({ ...image, key: `completed:${image.key}` })); render(); - expect(runtime.nodes.get('message.imageLightboxPageLabel').children).toBe('2 / 2'); + expect(runtime.nodes.get("preview.collectionCount").children).toBe('2 / 2'); await act(async () => runtime.nodes.get('message.imageLightboxShareButton').onPress()); expect(onShareImage.mock.lastCall?.[0].url).toBe(initial[1].url); // After rebuilding the keys, moving this page must follow its adopted key. const updated = collect([{ ...thumbs[1], uri: 'https://example.invalid/current.jpg' }])[0]; props.images = [{ ...updated, key: props.images[1].key }, props.images[0]]; render(); - expect(runtime.nodes.get('message.imageLightboxPageLabel').children).toBe('1 / 2'); + expect(runtime.nodes.get("preview.collectionCount").children).toBe('1 / 2'); await act(async () => runtime.nodes.get('message.imageLightboxShareButton').onPress()); expect(onShareImage.mock.lastCall?.[0].url).toBe('https://example.invalid/current.jpg'); }); @@ -1110,7 +1136,9 @@ describe('gallery prefetch boundaries', () => { expect(download.mock.calls.map(([media]) => media.url)).toEqual(['block-1', 'block-2', image.url]); await act(async () => { pending.get(image.url)!(result(image.key)); }); expect(runtime.nodes.get('Image').source.uri).toBe(`file:///${image.key}.png`); - expect(isMounted('message.imageLightboxShareButton')).toBe(false); + expect( + runtime.nodes.get("message.imageLightboxShareButton").disabled, + ).toBe(true); act(() => runtime.nodes.get('Image').onLoad({ source: { width: 1600, height: 900 } })); await act(async () => { press('message.imageLightboxShareButton'); }); expect(onShareImage).toHaveBeenCalledWith(image.payload.media, `file:///${image.key}.png`, 'image/png', 1); @@ -1613,10 +1641,9 @@ describe("image viewer actions", () => { // The native dimensions hook schedules its own render; this mock uses a changed prop. harness.props.onClose = vi.fn(); harness.render(); - const chromeBounds = () => Object.assign({}, ...runtime.nodes.get('message.imageLightboxChrome').style); - const closeBounds = Object.assign({}, ...runtime.nodes.get('message.imageLightboxCloseButton').style); - expect(chromeBounds().left + closeBounds.left).toBe(75); - expect(chromeBounds().right).toBe(0); + const chromeBounds = () => Object.assign({}, ...runtime.nodes.get("preview.header").style); + expect(chromeBounds().paddingLeft).toBeGreaterThanOrEqual(59); + expect(chromeBounds().paddingRight).toBeLessThan(59); expect(transform().x).toBeCloseTo(0); expect(transform().y).toBe(-300); expect(transform().scale).toBe(2.5); @@ -1625,8 +1652,8 @@ describe("image viewer actions", () => { runtime.insets = { top: 0, bottom: 20, left: 0, right: 59 }; harness.props.onClose = vi.fn(); harness.render(); - expect(chromeBounds().left).toBe(0); - expect(chromeBounds().right).toBe(59); + expect(chromeBounds().paddingLeft).toBeLessThan(59); + expect(chromeBounds().paddingRight).toBeGreaterThanOrEqual(59); }); it('includes the visible unfinished stroke and ignores drawing delivered after submit', async () => { @@ -1635,7 +1662,8 @@ describe("image viewer actions", () => { mount({ annotation: { submitLabel: 'Send', onSubmit } }); act(() => runtime.nodes.get('Image').onLoad({ source: { width: 400, height: 800 } })); press('message.imageLightboxAnnotateButton'); - const draw = gestures().find(g => g.kind === 'Pan' && g.options.minDistance === 0)!; + const draw = gestures().find( + (g) => g.kind === 'Pan' && g.options.minDistance === 0)!; fire(draw, 'onStart', { x: 100, y: 200 }); press('message.imageLightboxAnnotationSubmit'); fire(draw, 'onUpdate', { x: 300, y: 600 }); @@ -1653,7 +1681,7 @@ describe("image viewer actions", () => { mount({ annotation: { submitLabel: 'Send', onSubmit } }); act(() => runtime.nodes.get('Image').onLoad({ source: { width: 400, height: 800 } })); press('message.imageLightboxAnnotateButton'); - const draw = () => gestures().find(g => g.kind === 'Pan' && g.options.minDistance === 0)!; + const draw = () => gestures().find((g) => g.kind === 'Pan' && g.options.minDistance === 0)!; fire(draw(), 'onStart', { x: 100, y: 200 }); press('message.imageLightboxAnnotationSubmit'); fire(draw(), 'onFinalize'); @@ -1963,16 +1991,16 @@ describe("chrome toggle, loading and video pages", () => { fire(singleTap(), "onEnd", {}, true); finishAnimations(); expect(props.onClose).not.toHaveBeenCalled(); - expect(chrome().pointerEvents).toBe("none"); - expect(chromeOpacity()).toBe(0); + expect(isMounted("message.imageLightboxCloseButton")).toBe(true); + expect(isMounted("preview.collectionCount")).toBe(false); doubleTapAtCorner(); finishAnimations(); fire(singleTap(), "onEnd", {}, true); finishAnimations(); expect(props.onClose).not.toHaveBeenCalled(); - expect(chrome().pointerEvents).toBe("box-none"); - expect(chromeOpacity()).toBe(1); + expect(isMounted("message.imageLightboxCloseButton")).toBe(true); + expect(isMounted("preview.collectionCount")).toBe(true); }); it("lets the placeholder zoom and shows a loading bar instead of the tools while the original is loading", async () => { @@ -1982,9 +2010,10 @@ describe("chrome toggle, loading and video pages", () => { payload: { kind: "media", media: { kind: "image", url, previewable: false } }, } as ImageLightboxProps["images"][number]; let finish!: (media: unknown) => void; - const resolver = vi.fn((media: { thumbnail?: boolean }) => (media.thumbnail + const resolver = vi.fn((media: { thumbnail?: boolean }) => + media.thumbnail ? Promise.resolve({ url: "file:///thumb.png", previewable: true }) - : new Promise((resolve) => { finish = resolve; }))); + : new Promise((resolve) => { finish = resolve; })); runtime.gesture = null; await act(async () => { mount({ @@ -1997,10 +2026,18 @@ describe("chrome toggle, loading and video pages", () => { const preview = () => runtime.nodes.get("message.imageLightboxPreviewLayer"); expect(preview().source.uri).toBe("file:///thumb.png"); expect(isMounted("message.imageLightboxLoadingBar")).toBe(true); - expect(container.textContent).toContain("message.lightbox.loadingOriginal"); - expect(isMounted("message.imageLightboxShareButton")).toBe(false); - expect(isMounted("message.imageLightboxSendToChatButton")).toBe(false); - expect(isMounted("message.imageLightboxAnnotateButton")).toBe(false); + expect( + runtime.nodes.get("message.imageLightboxLoadingBar").accessibilityLabel, + ).toBe("message.lightbox.loadingOriginal"); + expect(runtime.nodes.get("message.imageLightboxShareButton").disabled).toBe( + true, + ); + expect( + runtime.nodes.get("message.imageLightboxSendToChatButton").disabled, + ).toBe(true); + expect( + runtime.nodes.get("message.imageLightboxAnnotateButton").disabled, + ).toBe(true); act(() => preview().onLoad({ source: { width: 160, height: 90 } })); doubleTapAtCorner(); @@ -2026,9 +2063,10 @@ describe("chrome toggle, loading and video pages", () => { payload: { kind: "media", media: { kind: "image", url, previewable: false } }, } as ImageLightboxProps["images"][number]; // 气泡用的是本地图,从没取过远程缩略图:缓存查询必然落空,原图仍在路上。 - const resolver = vi.fn((media: { thumbnail?: boolean }) => (media.thumbnail + const resolver = vi.fn((media: { thumbnail?: boolean }) => + media.thumbnail ? Promise.reject(new Error("not cached")) - : new Promise(() => undefined))); + : new Promise(() => undefined)); await act(async () => { mount({ images: [image], initialUrl: url, onResolveRemoteMedia: resolver as never }); }); @@ -2048,9 +2086,10 @@ describe("chrome toggle, loading and video pages", () => { } as ImageLightboxProps["images"][number]; const failures = new Map void>(); let finish!: (media: unknown) => void; - const resolver = vi.fn((media: { url: string; thumbnail?: boolean }) => (media.thumbnail + const resolver = vi.fn((media: { url: string; thumbnail?: boolean }) => + media.thumbnail ? Promise.resolve({ url: "file:///thumb.png", previewable: true }) - : new Promise((resolve, reject) => { finish = resolve; failures.set(media.url, reject); }))); + : new Promise((resolve, reject) => { finish = resolve; failures.set(media.url, reject); })); const fail = (error: Error) => failures.get(url)!(error); await act(async () => { const nextUrl = "xdt-image://cache/next.png"; @@ -2175,10 +2214,11 @@ describe("chrome toggle, loading and video pages", () => { }); }); const player = runtime.nodes.get("message.imageLightboxVideoPlayer"); - expect(runtime.nodes.get('message.videoDownload')).toMatchObject({ source: resolved.url, mimeType: 'video/mp4', immersive: true }); - // The download toolbar must not cover the native scrubber, even if actions wrap. - act(() => runtime.nodes.get('message.imageLightboxVideoActions').onLayout({ nativeEvent: { layout: { height: 96 } } })); - expect(runtime.nodes.get('message.imageLightboxVideoPage').style.paddingBottom).toBe(runtime.insets.bottom + 96 + 24); + expect(runtime.nodes.get('message.videoDownload')).toMatchObject({ source: resolved.url, mimeType: 'video/mp4', + }); + // The common footer is outside the media viewport; native playback retains its own controls. + expect(runtime.nodes.get('message.imageLightboxVideoPage').style.height, + ).toBe(runtime.dimensions.height); expect(player.url).toBe(resolved.url); expect(player.visible).toBe(true); expect(runtime.nodes.has("message.imageLightboxShareButton")).toBe(false); diff --git a/apps/mobile/src/__tests__/messageGallery.test.ts b/apps/mobile/src/__tests__/messageGallery.test.ts index 746d3d3c227..ffc806d0a1b 100644 --- a/apps/mobile/src/__tests__/messageGallery.test.ts +++ b/apps/mobile/src/__tests__/messageGallery.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it, vi } from 'vitest'; import { collectMobileMessageGalleryImages, + collectMobileMessageAttachmentPayloads, galleryImageIndexForPayload, lightboxImagesForPayload, } from '@/session/messageGallery'; @@ -709,3 +710,19 @@ describe('message gallery', () => { } }); }); + +// Unified attachment contract: a message-local mixed collection must retain files +// between media, without importing the neighbouring message's gallery. Unit tier; +// the previous image-only collector could not represent this input. +describe('message attachment collection', () => { + it('keeps the clicked message files and media in order', () => { + const row = messageItem('mixed', { attachments: [ + { kind: 'image', name: 'photo.png', uri: 'file:///photo.png', previewable: true }, + { kind: 'file', name: 'report.pdf', path: '/report.pdf', previewable: false }, + { kind: 'file', name: 'clip.mp4', path: '/clip.mp4', previewable: false }, + ] }); + const result = collectMobileMessageAttachmentPayloads(row); + expect(result).toHaveLength(3); + expect(result.map((payload) => payload.kind === 'file' ? payload.title : payload.kind === 'media' ? payload.media.url : payload.kind)).toEqual(['file:///photo.png', 'report.pdf', 'clip.mp4']); + }); +}); diff --git a/apps/mobile/src/__tests__/messageMediaThumbnailWiring.test.ts b/apps/mobile/src/__tests__/messageMediaThumbnailWiring.test.ts index 6b5530c2a8d..61d176bd627 100644 --- a/apps/mobile/src/__tests__/messageMediaThumbnailWiring.test.ts +++ b/apps/mobile/src/__tests__/messageMediaThumbnailWiring.test.ts @@ -171,7 +171,7 @@ describe('mobile message media thumbnail wiring', () => { expect(rendererSource).toContain('lightboxImagesForPayload(galleryImages, payload, payloadSourceGroupKey, prev, sourceGroupPresent, retainPendingSnapshot)'); expect(rendererSource).toContain("const imageLightboxOpen = historyActive && payload?.kind === 'media' && isLightboxMediaKind(payload.media.kind);"); expect(rendererSource).toContain('payload={historyActive ? payload : null}'); - expect(rendererSource).toContain('() => (imageLightboxOpen'); + expect(rendererSource).toMatch(/\(\) =>\s*\(?imageLightboxOpen/); expect(rendererSource).toContain(': null),'); // 旧的内嵌缩放查看器与图库箭头已退役 expect(rendererSource).not.toContain('ZoomablePayloadImage'); diff --git a/apps/mobile/src/__tests__/messagePayload.test.ts b/apps/mobile/src/__tests__/messagePayload.test.ts index 617f8ba5e16..fbfef2ec88d 100644 --- a/apps/mobile/src/__tests__/messagePayload.test.ts +++ b/apps/mobile/src/__tests__/messagePayload.test.ts @@ -1,7 +1,8 @@ -import { beforeAll, describe, expect, it } from 'vitest'; +import { beforeAll, describe, expect, it, vi } from 'vitest'; import { i18n } from '@/i18n'; import { buildAttachmentPayload, + resolveAttachmentPreviewSource, buildDiffPayload, buildMediaPayload, buildMermaidPayload, @@ -257,3 +258,23 @@ describe('messagePayload', () => { expect(isPayloadDirectPreviewableUrl('https://example.com/image.png')).toBe(true); }); }); + +// #8356 r4436061981: direct audio must reach the player without a host fetch. +// Existing payload-format tests never executed the preview resolver (unit tier). +describe('attachment preview source', () => { + it.each(['https://media.example.invalid/clip.mp3', 'data:audio/mpeg;base64,SUQz'])('plays direct audio %s without remote fetching', async (url) => { + const resolve = vi.fn(); + const payload = buildMediaPayload({ kind: 'audio', url, previewable: true, mimeType: 'audio/mpeg' }, 'clip.mp3'); + await expect(resolveAttachmentPreviewSource(payload, resolve, new AbortController().signal)) + .resolves.toEqual({ url, mimeType: 'audio/mpeg' }); + expect(resolve).not.toHaveBeenCalled(); + }); + it('authorizes a host file with the caller cancellation signal', async () => { + const signal = new AbortController().signal; + const resolved = { url: 'file:///cache/clip.mp3', mimeType: 'audio/mpeg' }; + const resolve = vi.fn().mockResolvedValue(resolved); + const payload = buildMediaPayload({ kind: 'audio', url: 'xdt-file://open?path=%2Fclip.mp3', previewable: false }, 'clip.mp3'); + await expect(resolveAttachmentPreviewSource(payload, resolve, signal)).resolves.toBe(resolved); + expect(resolve).toHaveBeenCalledExactlyOnceWith({ kind: 'file', url: payload.media.url, previewable: false }, { front: true, signal }); + }); +}); diff --git a/apps/mobile/src/__tests__/messageSourceLabelsRender.test.tsx b/apps/mobile/src/__tests__/messageSourceLabelsRender.test.tsx index 1f2c00e28b2..3b0cab89c34 100644 --- a/apps/mobile/src/__tests__/messageSourceLabelsRender.test.tsx +++ b/apps/mobile/src/__tests__/messageSourceLabelsRender.test.tsx @@ -816,3 +816,5 @@ describe("table-heavy assistant replies", () => { } finally { h.unmount(); } }); }); + +vi.mock('@/session/RemoteAttachmentPreview', () => ({ RemoteAttachmentPreview: () => null })); diff --git a/apps/mobile/src/__tests__/pdfPreviewSource.test.ts b/apps/mobile/src/__tests__/pdfPreviewSource.test.ts index 992a3106ade..faa08161c58 100644 --- a/apps/mobile/src/__tests__/pdfPreviewSource.test.ts +++ b/apps/mobile/src/__tests__/pdfPreviewSource.test.ts @@ -1,6 +1,10 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; -const { download, remove } = vi.hoisted(() => ({ download: vi.fn(), remove: vi.fn() })); -vi.mock('@/session/remoteMediaDiskCacheExpo', () => ({ retainRemoteFilePreview: download })); +const { download, remove, bounded } = vi.hoisted(() => ({ download: vi.fn(), remove: vi.fn(), + bounded: vi.fn(), +})); +vi.mock('@/session/remoteMediaDiskCacheExpo', () => ({ retainRemoteFilePreview: download, + withDownloadedRemoteMediaFile: bounded, +})); import { preparePdfPreviewSource } from '@/session/pdfPreviewSource'; describe('PDF preview native source', () => { @@ -14,7 +18,7 @@ describe('PDF preview native source', () => { source.release(); expect(remove).toHaveBeenCalledOnce(); }); - it.each(['https://example.com/document.pdf', 'file:///existing/document.pdf'])('delegates source ownership for %s', async uri => { + it.each(['file:///existing/document.pdf'])('delegates source ownership for %s', async (uri) => { download.mockResolvedValue({ uri, release: remove }); const source = await preparePdfPreviewSource(uri); expect(source.uri).toBe(uri); @@ -22,6 +26,42 @@ describe('PDF preview native source', () => { expect(download).toHaveBeenCalledWith(uri, 'application/pdf', 'preview.pdf'); expect(remove).toHaveBeenCalledOnce(); }); + it("downloads remote PDFs within the byte budget and retains the copy before releasing the download", async () => { + const controller = new AbortController(); + download.mockResolvedValue({ + uri: "file:///retained/report.pdf", + release: remove, + }); + bounded.mockImplementation(async (_url, _mime, limit, consume, signal) => { + expect(limit).toBe(30 * 1024 * 1024); + expect(signal).toBe(controller.signal); + return consume({ uri: "file:///download/report.pdf" }); + }); + const source = await preparePdfPreviewSource( + "https://example.com/report.pdf", + controller.signal, + ); + expect(download).toHaveBeenCalledWith( + "file:///download/report.pdf", + "application/pdf", + "preview.pdf", + ); + expect(source.uri).toBe("file:///retained/report.pdf"); + source.release(); + expect(remove).toHaveBeenCalledOnce(); + }); + it("reports an oversized download without opening a partial PDF", async () => { + bounded.mockImplementation( + async (_url, _mime, _limit, _consume, _signal, oversized) => { + oversized(); + return null; + }, + ); + await expect( + preparePdfPreviewSource("https://example.com/huge.pdf"), + ).rejects.toThrow("PDF_TOO_LARGE"); + expect(download).not.toHaveBeenCalled(); + }); it('rejects unsupported sources and failed cache writes before reaching WebView', async () => { await expect(preparePdfPreviewSource('data:text/html;base64,AA==')).rejects.toThrow('INVALID_PDF_URL'); download.mockRejectedValue(new Error('PREVIEW_CACHE_FAILED')); diff --git a/apps/mobile/src/__tests__/pdfSystemOpen.test.ts b/apps/mobile/src/__tests__/pdfSystemOpen.test.ts index dbf14f8dce9..fd858a8ad52 100644 --- a/apps/mobile/src/__tests__/pdfSystemOpen.test.ts +++ b/apps/mobile/src/__tests__/pdfSystemOpen.test.ts @@ -3,14 +3,14 @@ import { describe, expect, it } from 'vitest'; import { lacksInlinePreview, pdfPreviewsInline } from '@/session/pdfSystemOpen'; describe('pdfSystemOpen', () => { - it('只有 Android 不在应用内预览 PDF', () => { + it("iOS 和 Android 都在应用内预览 PDF", () => { expect(pdfPreviewsInline('ios')).toBe(true); - expect(pdfPreviewsInline('android')).toBe(false); + expect(pdfPreviewsInline('android')).toBe(true); }); - it('Android 上仅 PDF 缺少应用内预览(扩展名大小写不敏感,支持完整路径)', () => { - expect(lacksInlinePreview('documents/大型IPO的投资者问答.pdf', 'android')).toBe(true); - expect(lacksInlinePreview('/w/REPORT.PDF', 'android')).toBe(true); + it("已支持平台不会隐藏 PDF 快速预览(完整路径与大写扩展名)", () => { + expect(lacksInlinePreview('documents/大型IPO的投资者问答.pdf', 'android')).toBe(false); + expect(lacksInlinePreview('/w/REPORT.PDF', 'android')).toBe(false); expect(lacksInlinePreview('report.pdf', 'ios')).toBe(false); expect(lacksInlinePreview('notes.md', 'android')).toBe(false); expect(lacksInlinePreview('photo.png', 'android')).toBe(false); diff --git a/apps/mobile/src/__tests__/pdfWebViewHtml.test.ts b/apps/mobile/src/__tests__/pdfWebViewHtml.test.ts new file mode 100644 index 00000000000..5f7eaa4e5d0 --- /dev/null +++ b/apps/mobile/src/__tests__/pdfWebViewHtml.test.ts @@ -0,0 +1,84 @@ +// #8356 r4436061999. Execute the production WebView document in a VM and observe +// page dimensions. Existing route/source tests never run the reader script. +// Unit tier: injected PDF engine, no native WebView/network/canvas startup. +import { createContext, runInContext } from "node:vm"; +import { expect, it, vi } from "vitest"; +import { buildPdfWebViewHtml } from "@/session/pdfWebViewHtml"; + +vi.mock("@/session/documentPreviewAssets.generated", () => ({ + MOBILE_PDF_JS: "", + MOBILE_PDF_CSS: "", + MOBILE_PDF_BINARY_ASSETS: {}, +})); + +it.each([ + [390, 640], + [760, 240], +])( + "fits a portrait PDF within %s × %s and restores fit after zoom", + async (width, height) => { + const elements: Array<{ + style: Record; + dataset: Record; + replaceChildren: () => void; + }> = []; + const listeners = new Map void>(); + const context = createContext({ + innerWidth: width, + innerHeight: height, + atob: () => "", + window: { + document: { + createElement: () => { + const el = { style: {}, dataset: {}, replaceChildren: () => {} }; + elements.push(el); + return el; + }, + }, + }, + document: { getElementById: () => ({ append: () => {} }) }, + addEventListener: (name: string, callback: () => void) => + listeners.set(name, callback), + IntersectionObserver: class { + observe() {} + disconnect() {} + }, + pdfjsLib: { + getDocument: () => ({ + promise: Promise.resolve({ + numPages: 1, + getPage: async () => ({ + getViewport: () => ({ width: 612, height: 792 }), + }), + }), + }), + }, + }); + const html = buildPdfWebViewHtml("", { + surface: "#fff", + textPrimary: "#000", + }); + const script = html.slice( + html.indexOf(""), + ); + runInContext(script, context); + await new Promise((resolve) => setImmediate(resolve)); + const page = elements[0]; + const bounds = () => ({ + width: parseFloat(page.style.width), + height: parseFloat(page.style.height), + }); + const fit = bounds(); + expect(fit.width).toBeLessThanOrEqual(width - 24); + expect(fit.height).toBeLessThanOrEqual(height - 24); + expect(fit.width / fit.height).toBeCloseTo(612 / 792); + await context.window.__cindyPdfCommand({ type: "zoom", value: 2 }); + expect(bounds().height).toBeCloseTo(fit.height * 2); + await context.window.__cindyPdfCommand({ type: "zoom", value: 1 }); + expect(bounds()).toEqual(fit); + context.innerHeight = 200; + listeners.get("resize")!(); + expect(bounds().height).toBeLessThanOrEqual(176); + }, +); diff --git a/apps/mobile/src/__tests__/pluginAttachmentBubble.test.tsx b/apps/mobile/src/__tests__/pluginAttachmentBubble.test.tsx index 7929802ebdd..b092958fdc6 100644 --- a/apps/mobile/src/__tests__/pluginAttachmentBubble.test.tsx +++ b/apps/mobile/src/__tests__/pluginAttachmentBubble.test.tsx @@ -375,3 +375,5 @@ describe('companion native read position', () => { } }); }); + +vi.mock('@/session/RemoteAttachmentPreview', () => ({ RemoteAttachmentPreview: () => null })); diff --git a/apps/mobile/src/__tests__/sessionComposerDesktopFirst.test.ts b/apps/mobile/src/__tests__/sessionComposerDesktopFirst.test.ts index 59893896f3f..3dfb2228bf9 100644 --- a/apps/mobile/src/__tests__/sessionComposerDesktopFirst.test.ts +++ b/apps/mobile/src/__tests__/sessionComposerDesktopFirst.test.ts @@ -332,7 +332,7 @@ describe('mobile session composer desktop-first surface', () => { expect(composerInputSource).toContain(' = { + "sample.webp": { + ossKey: "", + mimeType: "image/webp", + size: 15474, + inlineBase64: + "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", + }, + "sample.pdf": { + ossKey: "", + mimeType: "application/pdf", + size: 962, + inlineBase64: + "JVBERi0xLjQKMSAwIG9iago8PC9UeXBlL0NhdGFsb2cvUGFnZXMgMiAwIFI+PgplbmRvYmoKMiAwIG9iago8PC9UeXBlL1BhZ2VzL0tpZHNbMyAwIFJdL0NvdW50IDE+PgplbmRvYmoKMyAwIG9iago8PC9UeXBlL1BhZ2UvUGFyZW50IDIgMCBSL01lZGlhQm94WzAgMCA1OTUgODQyXS9SZXNvdXJjZXM8PC9Gb250PDwvRjEgNCAwIFI+Pj4+L0NvbnRlbnRzIDUgMCBSPj4KZW5kb2JqCjQgMCBvYmoKPDwvVHlwZS9Gb250L1N1YnR5cGUvVHlwZTEvQmFzZUZvbnQvSGVsdmV0aWNhPj4KZW5kb2JqCjUgMCBvYmoKPDwvTGVuZ3RoIDQ1ND4+CnN0cmVhbQpCVCAvRjEgMjQgVGYgNzIgNzYwIFRkIChBbmRyb2lkIFBERiBBY2NlcHRhbmNlKSBUaiBFVApCVCAvRjEgMjAgVGYgNzIgNzEyIFRkIChWZXJpZmljYXRpb24gY29kZTogTk9WQS1BNUQ3NDgpIFRqIEVUCkJUIC9GMSAxOCBUZiA3MiA2NzAgVGQgKFNoYXBlczogNCBncmVlbiBzcXVhcmVzLCAzIHB1cnBsZSBzcXVhcmVzKSBUaiBFVApCVCAvRjEgMTggVGYgNzIgNjM4IFRkIChUb3RhbCBzaGFwZXM6IDcpIFRqIEVUCjAgMC42NSAwIHJnIDcyIDUxMCA2MCA2MCByZSBmCjAgMC42NSAwIHJnIDE3MiA1MTAgNjAgNjAgcmUgZgowIDAuNjUgMCByZyAyNzIgNTEwIDYwIDYwIHJlIGYKMCAwLjY1IDAgcmcgMzcyIDUxMCA2MCA2MCByZSBmCjAuNiAwIDAuOCByZyA3MiA0MTAgNjAgNjAgcmUgZgowLjYgMCAwLjggcmcgMTcyIDQxMCA2MCA2MCByZSBmCjAuNiAwIDAuOCByZyAyNzIgNDEwIDYwIDYwIHJlIGYKZW5kc3RyZWFtCmVuZG9iagp4cmVmCjAgNgowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwMDkgMDAwMDAgbiAKMDAwMDAwMDA1NCAwMDAwMCBuIAowMDAwMDAwMTA1IDAwMDAwIG4gCjAwMDAwMDAyMTcgMDAwMDAgbiAKMDAwMDAwMDI4MCAwMDAwMCBuIAp0cmFpbGVyCjw8L1NpemUgNi9Sb290IDEgMCBSPj4Kc3RhcnR4cmVmCjc4MgolJUVPRgo=", + }, + "sample.mp4": { + ossKey: "", + mimeType: "video/mp4", + size: 23634, + inlineBase64: + "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AAATAAAAZAAAABMAAACTAAAAEwAAAGcAAAAYAAAAbwAAABoAAACzAAAAFwAAAGUAAAAVAAAAiAAAABsAAAA6AAAAEwAAACMAAAAQAAAALQAAABEAAAAlAAAAEQAAABIAAAAUc3RjbwAAAAAAAAABAAAH7QAAAGJ1ZHRhAAAAWm1ldGEAAAAAAAAAIWhkbHIAAAAAAAAAAG1kaXJhcHBsAAAAAAAAAAAAAAAALWlsc3QAAAAlqXRvbwAAAB1kYXRhAAAAAQAAAABMYXZmNjIuMTIuMTAxAAAACGZyZWUAAFRtbWRhdAAAAq4GBf//qtxF6b3m2Ui3lizYINkj7u94MjY0IC0gY29yZSAxNjUgcjMyMjIgYjM1NjA1YSAtIEguMjY0L01QRUctNCBBVkMgY29kZWMgLSBDb3B5bGVmdCAyMDAzLTIwMjUgLSBodHRwOi8vd3d3LnZpZGVvbGFuLm9yZy94MjY0Lmh0bWwgLSBvcHRpb25zOiBjYWJhYz0xIHJlZj0zIGRlYmxvY2s9MTowOjAgYW5hbHlzZT0weDM6MHgxMTMgbWU9aGV4IHN1Ym1lPTcgcHN5PTEgcHN5X3JkPTEuMDA6MC4wMCBtaXhlZF9yZWY9MSBtZV9yYW5nZT0xNiBjaHJvbWFfbWU9MSB0cmVsbGlzPTEgOHg4ZGN0PTEgY3FtPTAgZGVhZHpvbmU9MjEsMTEgZmFzdF9wc2tpcD0xIGNocm9tYV9xcF9vZmZzZXQ9LTIgdGhyZWFkcz03IGxvb2thaGVhZF90aHJlYWRzPTEgc2xpY2VkX3RocmVhZHM9MCBucj0wIGRlY2ltYXRlPTEgaW50ZXJsYWNlZD0wIGJsdXJheV9jb21wYXQ9MCBjb25zdHJhaW5lZF9pbnRyYT0wIGJmcmFtZXM9MyBiX3B5cmFtaWQ9MiBiX2FkYXB0PTEgYl9iaWFzPTAgZGlyZWN0PTEgd2VpZ2h0Yj0xIG9wZW5fZ29wPTAgd2VpZ2h0cD0yIGtleWludD0yNTAga2V5aW50X21pbj0yNCBzY2VuZWN1dD00MCBpbnRyYV9yZWZyZXNoPTAgcmNfbG9va2FoZWFkPTQwIHJjPWNyZiBtYnRyZWU9MSBjcmY9MzIuMCBxY29tcD0wLjYwIHFwbWluPTAgcXBtYXg9NjkgcXBzdGVwPTQgaXBfcmF0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", + }, + "cube.gltf": { + ossKey: "", + mimeType: "model/gltf+json", + size: 988, + inlineBase64: + "eyJhc3NldCI6IHsidmVyc2lvbiI6ICIyLjAifSwgInNjZW5lIjogMCwgInNjZW5lcyI6IFt7Im5vZGVzIjogWzBdfV0sICJub2RlcyI6IFt7Im1lc2giOiAwfV0sICJtZXNoZXMiOiBbeyJwcmltaXRpdmVzIjogW3siYXR0cmlidXRlcyI6IHsiUE9TSVRJT04iOiAwfSwgImluZGljZXMiOiAxLCAibWF0ZXJpYWwiOiAwfV19XSwgIm1hdGVyaWFscyI6IFt7InBick1ldGFsbGljUm91Z2huZXNzIjogeyJiYXNlQ29sb3JGYWN0b3IiOiBbMC4yLCAwLjUsIDAuOCwgMV0sICJtZXRhbGxpY0ZhY3RvciI6IDAsICJyb3VnaG5lc3NGYWN0b3IiOiAwLjh9LCAiZG91YmxlU2lkZWQiOiB0cnVlfV0sICJidWZmZXJzIjogW3siYnl0ZUxlbmd0aCI6IDE2OCwgInVyaSI6ICJkYXRhOmFwcGxpY2F0aW9uL29jdGV0LXN0cmVhbTtiYXNlNjQsQUFDQXZ3QUFnTDhBQUlDL0FBQ0FQd0FBZ0w4QUFJQy9BQUNBUHdBQWdEOEFBSUMvQUFDQXZ3QUFnRDhBQUlDL0FBQ0F2d0FBZ0w4QUFJQS9BQUNBUHdBQWdMOEFBSUEvQUFDQVB3QUFnRDhBQUlBL0FBQ0F2d0FBZ0Q4QUFJQS9BQUFDQUFFQUFBQURBQUlBQkFBRkFBWUFCQUFHQUFjQUFBQUJBQVVBQUFBRkFBUUFBd0FIQUFZQUF3QUdBQUlBQVFBQ0FBWUFBUUFHQUFVQUFBQUVBQWNBQUFBSEFBTUEifV0sICJidWZmZXJWaWV3cyI6IFt7ImJ1ZmZlciI6IDAsICJieXRlT2Zmc2V0IjogMCwgImJ5dGVMZW5ndGgiOiA5NiwgInRhcmdldCI6IDM0OTYyfSwgeyJidWZmZXIiOiAwLCAiYnl0ZU9mZnNldCI6IDk2LCAiYnl0ZUxlbmd0aCI6IDcyLCAidGFyZ2V0IjogMzQ5NjN9XSwgImFjY2Vzc29ycyI6IFt7ImJ1ZmZlclZpZXciOiAwLCAiY29tcG9uZW50VHlwZSI6IDUxMjYsICJjb3VudCI6IDgsICJ0eXBlIjogIlZFQzMiLCAibWluIjogWy0xLCAtMSwgLTFdLCAibWF4IjogWzEsIDEsIDFdfSwgeyJidWZmZXJWaWV3IjogMSwgImNvbXBvbmVudFR5cGUiOiA1MTIzLCAiY291bnQiOiAzNiwgInR5cGUiOiAiU0NBTEFSIn1dfQ==", + }, + "flow.mmd": { + ossKey: "", + mimeType: "text/plain", + size: 78, + inlineBase64: + "Zmxvd2NoYXJ0IExSCiAgQVtPcGVuIGF0dGFjaG1lbnRdIC0tPiBCW1ByZXZpZXddCiAgQiAtLT4gQ1tSZXR1cm4gdG8gbWVzc2FnZV0K", + }, + "report.md": { + ossKey: "", + mimeType: "text/markdown", + size: 95, + inlineBase64: + "IyBBdHRhY2htZW50IHByZXZpZXcKCkEgKipyZWFkYWJsZSoqIGRvY3VtZW50LgoKfCBGaWxlIHwgU3RhdGUgfAp8IC0tLSB8IC0tLSB8CnwgUERGIHwgUmVhZHkgfAo=", + }, + "large.json": { + ossKey: "", + mimeType: "application/json", + size: 48_895, + inlineBase64: globalThis.btoa(Array.from({ length: 10_001 }, (_, index) => String(index)).join("\n")), + }, + "code.json": { + ossKey: "", + mimeType: "application/json", + size: 50, + inlineBase64: + "ewogICJwcmV2aWV3IjogdHJ1ZSwKICAiZmlsZW5hbWUiOiAic2FtcGxlLnBkZiIKfQo=", + }, +}; + +export function attachmentPreviewFixture(url: string) { + let path = url; + try { + const parsed = new URL(url); + path = parsed.searchParams.get("path") ?? parsed.pathname; + } catch { + /* raw host path */ + } + if (!path.startsWith("/attachment-preview/")) return null; + return files[path.slice("/attachment-preview/".length)] ?? null; +} diff --git a/apps/mobile/src/debug/visualMock.ts b/apps/mobile/src/debug/visualMock.ts index ba4c6895a5a..7a5b8f61789 100644 --- a/apps/mobile/src/debug/visualMock.ts +++ b/apps/mobile/src/debug/visualMock.ts @@ -1,3 +1,4 @@ +import { attachmentPreviewFixture } from "./attachmentPreviewFixture"; import { githubPrVisualFixture } from './githubPrVisualFixture'; import { groupMaskVisualFixture } from './groupMaskVisualFixture'; import { groupSettingsVisualFixture } from './groupSettingsVisualFixture'; @@ -58,7 +59,8 @@ const NOW = Date.parse('2026-07-18T08:00:00.000Z'); const ISO_NOW = new Date(NOW).toISOString(); // Built on first use: this module is imported by production code, and only the dev mock reads it. let uiMockRows: ReturnType | null = null; -const getUiMockRows = () => uiMockRows ??= buildUiMockRows({ now: NOW, projectRoot: '/Users/demo/Projects' }); +const getUiMockRows = () => + (uiMockRows ??= buildUiMockRows({ now: NOW, projectRoot: '/Users/demo/Projects' })); const IMAGE_DATA_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAABaAgMAAABCqLXBAAAADFBMVEX9/f2BgoIgISTY2NXfCK5xAAAACXBIWXMAAAsTAAALEwEAmpwYAAACCklEQVR42uWXsU7DMBBAk1rqgKyy9xM6nVSJoQv9BJaKlbVh4QcQfEIXWMsQiSZFav8Ao0hMGVhY+IRMmTogVebslsah1Hd0QYKrYrXOq+98Pp99QcAW+bUDPrtF9U5gt8q7kGfmgRwlAwGmxW7zLjdvu931v5lqxYCSU2VY6KWkPNgBIxpMFI7ZSknVg3SOIzZjei7tETa9OQ22jJGdPg2GE2wKjh+n+Aw54PAPguFmUWM/6MSP8oKdCrz2gk6gjbygE3l3vwzWN8doJxjWN0y8E2zUwcmeYFIueKrR5IIzmQS/HXHcE2O+CjngSAaZUUmCNmoKBtjPJECbAdqfBzSYyACEbNLgNMiEyYAM0ORpBmjWM8gaDFBIUEGDqVpybASQ1WRe3fx9UXcPHmKgntdgLdGf1B0uXIf7wD5OZrOEHtXmIJEmtdBhBqvI5gSueuIELnsrmC0UscCbarv6wUtmkkp0wQPv9bvhbklwrJfWABLUWkfGgLOtJcS7hxtmCL6l6Uy/bAUFigPOEET3POrSC0ZIoFgDvKo7htC6mK7B3Ufc1IKLMTbKe2jOLLi8wqb0HsPHuhIvqJmgcEHlAQ9dsPSAronf616Dej9QccHyn1y5aDn/ydWVfRlmX6/ZF3ZWCYDHuWQVFXZcVpmCKV7Rhc/AFj6C48esqtrIoktIQYuE1XhAfgL4ANer/BnN/IZTAAAAAElFTkSuQmCC'; @@ -198,9 +200,8 @@ export function createVisualMockDeviceLinkContext(): DeviceLinkContextValue { busy: false, lastSeenAt: NOW, }, - getPresenceAvailability: (candidateDeviceId: string) => ( - candidateDeviceId === deviceId ? true : null - ), + getPresenceAvailability: (candidateDeviceId: string) => + candidateDeviceId === deviceId ? true : null, openLink: async (): Promise => ({ appVersion: '0.0.0-visual-mock', allowlistHash: 'visual-mock', @@ -260,8 +261,14 @@ async function visualMockInvoke( } }], } as T; } + if (channel === "device-link:media:fetch") { + const fixture = attachmentPreviewFixture( + String((args[0] as { url?: string })?.url ?? ""), + ); + if (fixture) return fixture as T; + } const realData = await loadVisualRealDataSnapshot(); - const mailResult = realData ? undefined : mailVisualFixture(channel, args) ?? mailDraftCardFixture(channel, args); + const mailResult = realData ? undefined : (mailVisualFixture(channel, args) ?? mailDraftCardFixture(channel, args)); if (mailResult !== undefined) return mailResult as T; const pluginResult = realData ? undefined : pluginVisualFixture(channel, args); if (pluginResult !== undefined) return pluginResult as T; @@ -314,7 +321,8 @@ async function visualMockInvoke( case 'local-db:sessions:list': return visualMockSessions((typeof args[0] === 'string' ? args[0] : args[1]) as string | undefined) as T; case 'local-db:history:query': - return { ok: true, sessions: visualMockSessions('all').filter(row => row.source === 'bot'), hasMore: false } as T; + return { ok: true, sessions: visualMockSessions('all').filter( + (row) => row.source === 'bot'), hasMore: false } as T; case 'local-db:sessions:get-many': return (args[0] as string[]).map(visualMockSession) as T; case 'local-db:sessions:get': @@ -322,14 +330,14 @@ async function visualMockInvoke( case 'local-db:messages:work-details': { const rows = visualMockMessages(String(args[0])); const ref = args[1] as { firstMessageId: string; lastMessageId: string }; - const first = rows.findIndex(row => row.id === ref.firstMessageId); - const last = rows.findIndex(row => row.id === ref.lastMessageId); + const first = rows.findIndex((row) => row.id === ref.firstMessageId); + const last = rows.findIndex((row) => row.id === ref.lastMessageId); return { version: 1, messages: first >= 0 && last >= first ? rows.slice(first, last + 1) : [], hasMore: false, nextCursor: null } as T; } case 'local-db:messages:view-intent': return undefined as T; case 'local-db:messages:around-client-id': { const rows = visualMockMessages(String(args[0])); - const index = rows.findIndex(row => row.clientId === args[1]); + const index = rows.findIndex((row) => row.clientId === args[1]); const radius = Math.max(0, Number((args[2] as { radius?: number } | undefined)?.radius ?? 0)); return (index < 0 ? [] : rows.slice(Math.max(0, index - radius), index + radius + 1)) as T; } @@ -381,8 +389,19 @@ async function visualMockInvoke( return visualMockDirectory() as T; case 'fs:stat-path': return { kind: 'dir', resolvedPath: '/repo/xdt-maker' } satisfies RemotePathStatResult as T; - case 'text-file:read-preview': + case 'text-file:read-preview': { + const path = String((args[0] as { filePath?: string })?.filePath ?? ""); + const fixture = attachmentPreviewFixture( + `xdt-file://open?path=${encodeURIComponent(path)}`, + ); + if (fixture && /\.(mmd|mermaid|md|json)$/.test(path)) + return { + success: true, + data: globalThis.atob(fixture.inlineBase64), + size: fixture.size, + } as T; return visualMockTextPreview() as T; + } case 'maker:list-agent-commands': return { success: true, commands: [{ kind: 'agent-builtin', name: 'status', description: 'Show status' }] } as T; case 'maker:list-agent-skills': @@ -509,17 +528,21 @@ function realDataSessions(snapshot: VisualRealDataSnapshot, statusFilter?: strin } function realDataSession(snapshot: VisualRealDataSnapshot, sessionId: string): RemoteSession { - return snapshot.sessions.find((session) => session.id === sessionId) + return ( + snapshot.sessions.find((session) => session.id === sessionId) ?? snapshot.sessions.find((session) => session.id === snapshot.selectedSessionId) - ?? snapshot.sessions[0]; + ?? snapshot.sessions[0] + ); } function realDataProjection(snapshot: VisualRealDataSnapshot, sessionId: string): InputProjection { - return snapshot.projectionsBySession?.[sessionId] ?? { + return ( + snapshot.projectionsBySession?.[sessionId] ?? { ...visualMockProjection(sessionId), pendingQueue: [], queueExpanded: false, - }; + } + ); } function visualMockSessions(statusFilter?: string): RemoteSession[] { @@ -577,10 +600,13 @@ function visualMockSession(id: string): RemoteSession { if (id === 'visual-teammate-process') return { ...visualMockSession('ui-mock-rich-answer'), id, source: 'bot', title: 'Cindy' }; if (id === VISUAL_MAIL_DRAFT_SESSION_ID) return { ...visualMockSession('ui-mock-rich-answer'), id, title: '回复试玩反馈' }; if (id === VISUAL_TEAMMATE_SESSION_ID || id.startsWith(`${VISUAL_TEAMMATE_SESSION_ID}:`) - || [...visualTeammateThreads.values()].some(thread => thread.sessionId === id)) return { + || [...visualTeammateThreads.values()].some( + (thread) => thread.sessionId === id)) return { ...visualMockSession('ui-mock-rich-answer'), id, source: 'bot', title: 'Cindy · 演示群任务', }; - return visualMockSessions('all').find((session) => session.id === id) ?? visualMockSessions()[0]; + return ( + visualMockSessions('all').find((session) => session.id === id) ?? visualMockSessions()[0] + ); } function sessionFromShared( @@ -637,7 +663,8 @@ function visualMockMessages(sessionId: string): RemoteMessage[] { message(sessionId, 'fixture-note3', 'assistant', '任务与 PR 的信息已核对,正在整理摘要。', -40000, { agentMeta: { assistantPhase: 'commentary' } }), message(sessionId, 'fixture-result', 'assistant', '任务与 PR 的进展已整理,需要跟进的两项工作已列出。', -30000, { agentMeta: { assistantPhase: 'final_answer', turnCompleted: true } }), ]; - const topic = [...visualTeammateThreads.values()].find(thread => thread.sessionId === sessionId); + const topic = [...visualTeammateThreads.values()].find( + (thread) => thread.sessionId === sessionId); if (topic) return topic.replyCount ? [ message(sessionId, 'visual-topic-user', 'user', '把移动端和群聊的话题入口统一起来。', -180_000), message(sessionId, 'visual-topic-reply', 'assistant', '两处都会使用堆叠头像,回复数和时间保持同一字号。', -120_000), @@ -656,8 +683,36 @@ function visualMockMessages(sessionId: string): RemoteMessage[] { ...message, sessionId, role: message.role === 'tool_use' ? 'tool_use' : message.role, - } as RemoteMessage)); + }) as RemoteMessage, + ); } + if (sessionId === "ui-mock-showcase-feedback-zh") + return [ + message( + sessionId, + "attachment-preview-mixed", + "user", + { + text: "附件预览验收:图片、视频、PDF、文本使用同一条消息的集合。", + images: [ + { + name: "sample.webp", + url: "xdt-file://open?path=%2Fattachment-preview%2Fsample.webp", + mimeType: "image/webp", + }, + ], + files: [ + "sample.mp4", + "sample.pdf", + "code.json", + "large.json", + "cube.gltf", + "flow.mmd", + ].map((name) => ({ name, path: `/attachment-preview/${name}` })), + }, + 0, + ), + ]; const running = sessionId.includes('running'); const catalog = getUiMockRows().messages.filter((row) => row.sessionId === sessionId); if (catalog.length > 0) { @@ -671,7 +726,7 @@ function visualMockMessages(sessionId: string): RemoteMessage[] { role: row.role, content: streaming ? { text: row.content, isStreaming: true } : row.content, toolUseId: row.toolUseId, - agentMeta: streaming ? { isStreaming: true } : row.agentMeta ?? null, + agentMeta: streaming ? { isStreaming: true } : (row.agentMeta ?? null), createdAt: new Date(row.createdAt).toISOString(), } as RemoteMessage; }); @@ -910,7 +965,8 @@ function sharedUiMockFiles(input: unknown): unknown { if (!input || typeof input !== 'object') return undefined; const request = input as Record; const workdir = String(request.workdir ?? '').replace(/\\/g, '/').replace(/\/$/, ''); - const project = UI_MOCK_SHOWCASE_PROJECTS.find(item => workdir === `/Users/demo/Projects/${item.name}`); + const project = UI_MOCK_SHOWCASE_PROJECTS.find( + (item) => workdir === `/Users/demo/Projects/${item.name}`); if (!project) return undefined; // Keep the existing media/Markdown regression fixtures. const relPath = String(request.relPath ?? '').replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/$/, ''); const paths = Object.keys(project.files).sort(); @@ -921,7 +977,8 @@ function sharedUiMockFiles(input: unknown): unknown { } if (request.op === 'listDir') { const prefix = relPath ? `${relPath}/` : ''; - const entries = new Map(); + const entries = new Map(); for (const path of paths) { if (!path.startsWith(prefix)) continue; const remainder = path.slice(prefix.length); @@ -939,12 +996,12 @@ function sharedUiMockFiles(input: unknown): unknown { if (request.op === 'searchCollect') { const normalize = (value: string) => request.caseSensitive === true ? value : value.toLowerCase(); const query = normalize(String(request.query ?? '')); - const found = query ? paths.flatMap(path => project.files[path].split('\n').flatMap((line, index) => + const found = query ? paths.flatMap((path) => project.files[path].split('\n').flatMap((line, index) => normalize(line).includes(query) ? [{ relPath: path, lineNumber: index + 1, lineText: line }] : [], )) : []; const cap = typeof request.maxMatches === 'number' && request.maxMatches > 0 ? Math.min(500, request.maxMatches) : 500; const matches = found.slice(0, cap); - return { matches, truncated: found.length > cap, totalMatches: matches.length, totalFiles: new Set(matches.map(item => item.relPath)).size } satisfies FileBrowserSearchCollectResult; + return { matches, truncated: found.length > cap, totalMatches: matches.length, totalFiles: new Set(matches.map((item) => item.relPath)).size } satisfies FileBrowserSearchCollectResult; } if (request.op === 'thumbnail') return { ok: false, code: 'THUMB_UNSUPPORTED' } satisfies FileBrowserThumbnailResult; return { ok: false, message: 'Unsupported demo file operation.' }; diff --git a/apps/mobile/src/i18n/locales/de/message.json b/apps/mobile/src/i18n/locales/de/message.json index b6bc1f5f237..1e6343160e9 100644 --- a/apps/mobile/src/i18n/locales/de/message.json +++ b/apps/mobile/src/i18n/locales/de/message.json @@ -450,5 +450,32 @@ "provider": "API-Anbieter", "unknown": "Kanal nicht erfasst" } + }, + "attachmentPreview": { + "more": "Weitere Aktionen", + "retry": "Erneut versuchen", + "image": "Bild", + "video": "Video", + "file": "Datei", + "close": "Schließen", + "reset": "Ansicht zurücksetzen", + "open": "Vorschau öffnen", + "zoomIn": "Vergrößern", + "zoomOut": "Verkleinern", + "fit": "An Fenster anpassen", + "actualSize": "Originalgröße (1:1)", + "save": "Original speichern", + "share": "Teilen", + "previous": "Vorheriger Anhang", + "next": "Nächster Anhang", + "password": "PDF-Passwort eingeben", + "find": "Im Dokument suchen", + "matches": "{{count}} passende Seiten", + "previousPage": "Vorherige Seite", + "nextPage": "Nächste Seite", + "redo": "Wiederherstellen", + "wrap": "Zeilenumbruch", + "lineNumbers": "Zeilennummern", + "rendered": "Dokument anzeigen" } } diff --git a/apps/mobile/src/i18n/locales/en/message.json b/apps/mobile/src/i18n/locales/en/message.json index 656fe547c7e..574b64119f4 100644 --- a/apps/mobile/src/i18n/locales/en/message.json +++ b/apps/mobile/src/i18n/locales/en/message.json @@ -450,5 +450,32 @@ "provider": "API provider", "unknown": "Channel not recorded" } + }, + "attachmentPreview": { + "more": "More actions", + "retry": "Retry", + "image": "Image", + "video": "Video", + "file": "File", + "close": "Close", + "reset": "Reset view", + "open": "Open preview", + "zoomIn": "Zoom in", + "zoomOut": "Zoom out", + "fit": "Fit to window", + "actualSize": "Actual size (1:1)", + "save": "Save original", + "share": "Share", + "previous": "Previous attachment", + "next": "Next attachment", + "password": "Enter the PDF password", + "find": "Find in document", + "matches": "{{count}} matching pages", + "previousPage": "Previous page", + "nextPage": "Next page", + "redo": "Redo", + "wrap": "Wrap lines", + "lineNumbers": "Line numbers", + "rendered": "View document" } } diff --git a/apps/mobile/src/i18n/locales/es/message.json b/apps/mobile/src/i18n/locales/es/message.json index eac2927cb4d..54b67fd44e5 100644 --- a/apps/mobile/src/i18n/locales/es/message.json +++ b/apps/mobile/src/i18n/locales/es/message.json @@ -451,5 +451,32 @@ "provider": "Proveedor de API", "unknown": "Canal no registrado" } + }, + "attachmentPreview": { + "more": "Más acciones", + "retry": "Reintentar", + "image": "Imagen", + "video": "Vídeo", + "file": "Archivo", + "close": "Cerrar", + "reset": "Restablecer vista", + "open": "Abrir vista previa", + "zoomIn": "Acercar", + "zoomOut": "Alejar", + "fit": "Ajustar a la ventana", + "actualSize": "Tamaño real (1:1)", + "save": "Guardar original", + "share": "Compartir", + "previous": "Adjunto anterior", + "next": "Adjunto siguiente", + "password": "Introduce la contraseña del PDF", + "find": "Buscar en el documento", + "matches": "{{count}} páginas coincidentes", + "previousPage": "Página anterior", + "nextPage": "Página siguiente", + "redo": "Rehacer", + "wrap": "Ajustar líneas", + "lineNumbers": "Números de línea", + "rendered": "Ver documento" } } diff --git a/apps/mobile/src/i18n/locales/fr/message.json b/apps/mobile/src/i18n/locales/fr/message.json index c6e991cd407..09f54fd4767 100644 --- a/apps/mobile/src/i18n/locales/fr/message.json +++ b/apps/mobile/src/i18n/locales/fr/message.json @@ -451,5 +451,32 @@ "provider": "Fournisseur d’API", "unknown": "Canal non enregistré" } + }, + "attachmentPreview": { + "more": "Autres actions", + "retry": "Réessayer", + "image": "Image", + "video": "Vidéo", + "file": "Fichier", + "close": "Fermer", + "reset": "Réinitialiser la vue", + "open": "Ouvrir l’aperçu", + "zoomIn": "Zoom avant", + "zoomOut": "Zoom arrière", + "fit": "Ajuster à la fenêtre", + "actualSize": "Taille réelle (1:1)", + "save": "Enregistrer l’original", + "share": "Partager", + "previous": "Pièce jointe précédente", + "next": "Pièce jointe suivante", + "password": "Saisir le mot de passe du PDF", + "find": "Rechercher dans le document", + "matches": "{{count}} pages correspondantes", + "previousPage": "Page précédente", + "nextPage": "Page suivante", + "redo": "Rétablir", + "wrap": "Retour à la ligne", + "lineNumbers": "Numéros de ligne", + "rendered": "Afficher le document" } } diff --git a/apps/mobile/src/i18n/locales/ja/message.json b/apps/mobile/src/i18n/locales/ja/message.json index de1372c2248..b8ad55d6c15 100644 --- a/apps/mobile/src/i18n/locales/ja/message.json +++ b/apps/mobile/src/i18n/locales/ja/message.json @@ -450,5 +450,32 @@ "provider": "API プロバイダー", "unknown": "経路未記録" } + }, + "attachmentPreview": { + "more": "その他の操作", + "retry": "再試行", + "image": "画像", + "video": "動画", + "file": "ファイル", + "close": "閉じる", + "reset": "表示をリセット", + "open": "プレビューを開く", + "zoomIn": "拡大", + "zoomOut": "縮小", + "fit": "ウィンドウに合わせる", + "actualSize": "実際のサイズ(1:1)", + "save": "元のファイルを保存", + "share": "共有", + "previous": "前の添付ファイル", + "next": "次の添付ファイル", + "password": "PDF のパスワードを入力", + "find": "文書内を検索", + "matches": "一致するページ:{{count}}", + "previousPage": "前のページ", + "nextPage": "次のページ", + "redo": "やり直す", + "wrap": "行の折り返し", + "lineNumbers": "行番号", + "rendered": "文書を表示" } } diff --git a/apps/mobile/src/i18n/locales/ko/message.json b/apps/mobile/src/i18n/locales/ko/message.json index 3f126567a1d..b61122f25f0 100644 --- a/apps/mobile/src/i18n/locales/ko/message.json +++ b/apps/mobile/src/i18n/locales/ko/message.json @@ -450,5 +450,32 @@ "provider": "API 제공자", "unknown": "경로 미기록" } + }, + "attachmentPreview": { + "more": "추가 작업", + "retry": "다시 시도", + "image": "이미지", + "video": "동영상", + "file": "파일", + "close": "닫기", + "reset": "보기 초기화", + "open": "미리보기 열기", + "zoomIn": "확대", + "zoomOut": "축소", + "fit": "창에 맞추기", + "actualSize": "실제 크기(1:1)", + "save": "원본 저장", + "share": "공유", + "previous": "이전 첨부 파일", + "next": "다음 첨부 파일", + "password": "PDF 암호 입력", + "find": "문서에서 찾기", + "matches": "일치하는 페이지 {{count}}개", + "previousPage": "이전 페이지", + "nextPage": "다음 페이지", + "redo": "다시 실행", + "wrap": "자동 줄 바꿈", + "lineNumbers": "줄 번호", + "rendered": "문서 보기" } } diff --git a/apps/mobile/src/i18n/locales/pt-BR/message.json b/apps/mobile/src/i18n/locales/pt-BR/message.json index a72b6b935ec..a17dd4411fc 100644 --- a/apps/mobile/src/i18n/locales/pt-BR/message.json +++ b/apps/mobile/src/i18n/locales/pt-BR/message.json @@ -451,5 +451,32 @@ "provider": "Provedor de API", "unknown": "Canal não registrado" } + }, + "attachmentPreview": { + "more": "Mais ações", + "retry": "Tentar novamente", + "image": "Imagem", + "video": "Vídeo", + "file": "Arquivo", + "close": "Fechar", + "reset": "Redefinir visualização", + "open": "Abrir prévia", + "zoomIn": "Ampliar", + "zoomOut": "Reduzir", + "fit": "Ajustar à janela", + "actualSize": "Tamanho real (1:1)", + "save": "Salvar original", + "share": "Compartilhar", + "previous": "Anexo anterior", + "next": "Próximo anexo", + "password": "Digite a senha do PDF", + "find": "Buscar no documento", + "matches": "{{count}} páginas correspondentes", + "previousPage": "Página anterior", + "nextPage": "Próxima página", + "redo": "Refazer", + "wrap": "Quebra de linha", + "lineNumbers": "Números de linha", + "rendered": "Ver documento" } } diff --git a/apps/mobile/src/i18n/locales/pt-PT/message.json b/apps/mobile/src/i18n/locales/pt-PT/message.json index cb7b6815332..afa49596a86 100644 --- a/apps/mobile/src/i18n/locales/pt-PT/message.json +++ b/apps/mobile/src/i18n/locales/pt-PT/message.json @@ -451,5 +451,32 @@ "provider": "Fornecedor de API", "unknown": "Canal não registado" } + }, + "attachmentPreview": { + "more": "Mais ações", + "retry": "Tentar novamente", + "image": "Imagem", + "video": "Vídeo", + "file": "Ficheiro", + "close": "Fechar", + "reset": "Repor vista", + "open": "Abrir pré-visualização", + "zoomIn": "Ampliar", + "zoomOut": "Reduzir", + "fit": "Ajustar à janela", + "actualSize": "Tamanho real (1:1)", + "save": "Guardar original", + "share": "Partilhar", + "previous": "Anexo anterior", + "next": "Anexo seguinte", + "password": "Introduzir palavra-passe do PDF", + "find": "Pesquisar no documento", + "matches": "{{count}} páginas correspondentes", + "previousPage": "Página anterior", + "nextPage": "Página seguinte", + "redo": "Refazer", + "wrap": "Moldar linhas", + "lineNumbers": "Números de linha", + "rendered": "Ver documento" } } diff --git a/apps/mobile/src/i18n/locales/zh-CN/message.json b/apps/mobile/src/i18n/locales/zh-CN/message.json index 429c18d8eb0..8f34ae5f95a 100644 --- a/apps/mobile/src/i18n/locales/zh-CN/message.json +++ b/apps/mobile/src/i18n/locales/zh-CN/message.json @@ -450,5 +450,32 @@ "provider": "API 供应商", "unknown": "未记录渠道" } + }, + "attachmentPreview": { + "more": "更多操作", + "retry": "重试", + "image": "图片", + "video": "视频", + "file": "文件", + "close": "关闭", + "reset": "重置视角", + "open": "打开预览", + "zoomIn": "放大", + "zoomOut": "缩小", + "fit": "适配窗口", + "actualSize": "原始尺寸(1:1)", + "save": "保存原件", + "share": "分享", + "previous": "上一个附件", + "next": "下一个附件", + "password": "输入 PDF 密码", + "find": "在文档中查找", + "matches": "{{count}} 页匹配", + "previousPage": "上一页", + "nextPage": "下一页", + "redo": "重做", + "wrap": "自动换行", + "lineNumbers": "行号", + "rendered": "查看文稿" } } diff --git a/apps/mobile/src/i18n/locales/zh-Hans-x-lzh/message.json b/apps/mobile/src/i18n/locales/zh-Hans-x-lzh/message.json index 6c0a2ba1c4f..5785ab96bee 100644 --- a/apps/mobile/src/i18n/locales/zh-Hans-x-lzh/message.json +++ b/apps/mobile/src/i18n/locales/zh-Hans-x-lzh/message.json @@ -450,5 +450,32 @@ "provider": "API 供方", "unknown": "未录渠道" } + }, + "attachmentPreview": { + "more": "更多操作", + "retry": "重试", + "image": "图", + "video": "影片", + "file": "档案", + "close": "关闭", + "reset": "复原视角", + "open": "开启预览", + "zoomIn": "放大", + "zoomOut": "缩小", + "fit": "适于视窗", + "actualSize": "原始尺寸(1:1)", + "save": "存原件", + "share": "分享", + "previous": "上一附件", + "next": "下一附件", + "password": "请输入 PDF 密钥", + "find": "查此文", + "matches": "合者 {{count}} 页", + "previousPage": "前页", + "nextPage": "后页", + "redo": "复为", + "wrap": "折行", + "lineNumbers": "行数", + "rendered": "观文" } } diff --git a/apps/mobile/src/i18n/locales/zh-Hant-x-lzh/message.json b/apps/mobile/src/i18n/locales/zh-Hant-x-lzh/message.json index 55f7badfc12..0d8c5de5ccb 100644 --- a/apps/mobile/src/i18n/locales/zh-Hant-x-lzh/message.json +++ b/apps/mobile/src/i18n/locales/zh-Hant-x-lzh/message.json @@ -450,5 +450,32 @@ "provider": "API 供方", "unknown": "未錄渠道" } + }, + "attachmentPreview": { + "more": "更多操作", + "retry": "重試", + "image": "圖", + "video": "影片", + "file": "檔案", + "close": "關閉", + "reset": "復原視角", + "open": "開啟預覽", + "zoomIn": "放大", + "zoomOut": "縮小", + "fit": "適於視窗", + "actualSize": "原始尺寸(1:1)", + "save": "存原件", + "share": "分享", + "previous": "上一附件", + "next": "下一附件", + "password": "請輸入 PDF 密鑰", + "find": "查此文", + "matches": "合者 {{count}} 頁", + "previousPage": "前頁", + "nextPage": "後頁", + "redo": "復為", + "wrap": "折行", + "lineNumbers": "行數", + "rendered": "觀文" } } diff --git a/apps/mobile/src/i18n/locales/zh-TW/message.json b/apps/mobile/src/i18n/locales/zh-TW/message.json index c8b9541ca33..c46088cbbe3 100644 --- a/apps/mobile/src/i18n/locales/zh-TW/message.json +++ b/apps/mobile/src/i18n/locales/zh-TW/message.json @@ -450,5 +450,32 @@ "provider": "API 供應商", "unknown": "未記錄渠道" } + }, + "attachmentPreview": { + "more": "更多操作", + "retry": "重試", + "image": "圖片", + "video": "影片", + "file": "檔案", + "close": "關閉", + "reset": "重設視角", + "open": "開啟預覽", + "zoomIn": "放大", + "zoomOut": "縮小", + "fit": "符合視窗", + "actualSize": "原始尺寸(1:1)", + "save": "儲存原件", + "share": "分享", + "previous": "上一個附件", + "next": "下一個附件", + "password": "輸入 PDF 密碼", + "find": "在文件中尋找", + "matches": "{{count}} 頁符合", + "previousPage": "上一頁", + "nextPage": "下一頁", + "redo": "重做", + "wrap": "自動換行", + "lineNumbers": "行號", + "rendered": "查看文件" } } diff --git a/apps/mobile/src/plugins/PluginMediaViewer.tsx b/apps/mobile/src/plugins/PluginMediaViewer.tsx index 97b6937f86a..ae5b1595faf 100644 --- a/apps/mobile/src/plugins/PluginMediaViewer.tsx +++ b/apps/mobile/src/plugins/PluginMediaViewer.tsx @@ -3,7 +3,6 @@ import { ActivityIndicator, Alert, Modal, - Pressable, StyleSheet, View, } from "react-native"; @@ -18,7 +17,7 @@ import { Text } from "@/components/AppText"; import { ImageLightbox } from "@/session/ImageLightbox"; import { buildMediaPayload } from "@/session/messagePayload"; import { lightboxImagesForPayload } from "@/session/messageGallery"; -import { RemoteMediaPlayerWebView } from "@/session/mediaPlayerWebView"; +import { PreviewHeader } from "@/session/PreviewChrome"; import { fontWeight, lineHeight, @@ -49,6 +48,9 @@ export function PluginMediaViewer({ const sharing = useRef | undefined>(undefined), alive = useRef(true); useEffect(() => { + alive.current = true; + setFailed(false); + setMedia(undefined); const abort = new AbortController(); let owned: typeof media; void readPluginMediaFile(intent.path, intent.mediaKind, read, abort.signal) @@ -74,12 +76,12 @@ export function PluginMediaViewer({ ? lightboxImagesForPayload( [], buildMediaPayload( - { kind: "image", url: media.uri, previewable: true }, + { kind: intent.mediaKind, url: media.uri, previewable: true }, intent.ghostName, ), ) : [], - [media, intent.ghostName], + [media, intent.ghostName, intent.mediaKind], ); const share = async () => { if (!media || !alive.current || sharing.current) return; @@ -93,7 +95,7 @@ export function PluginMediaViewer({ if (sharing.current === work) sharing.current = undefined; } }; - if (media && intent.mediaKind === "image") + if (media) return ( - - - {t("plugins.returnToPlugin")} - - {media ? ( - void share()} - onLongPress={() => void share()} - > - {t("plugins.shareMedia")} - - ) : null} - - {media ? ( - - ) : failed ? ( + + {failed ? ( {t("plugins.previewFailed")} ) : ( diff --git a/apps/mobile/src/session/BotGroupMessageAttachments.tsx b/apps/mobile/src/session/BotGroupMessageAttachments.tsx index 756806cd3b8..334723bd155 100644 --- a/apps/mobile/src/session/BotGroupMessageAttachments.tsx +++ b/apps/mobile/src/session/BotGroupMessageAttachments.tsx @@ -1,56 +1,101 @@ +import { PreviewCollectionContext } from "./PreviewChrome"; /** * 群聊时间线里各成员消息的附件(docs/product-rules/bot-group-chat.md §8)。 * * 复用会话消息的附件条(MessageRenderer 的 AttachmentStrip):图片按原始比例显示缩略图, * 经远程媒体从电脑取件,点开进入与聊天相同的看图器;文件是「类型图标 + 文件名」的小条。 - * 旧电脑文件仍提示在电脑打开;服务器图片授权后显示预览,文本文件点击后在端内安全阅读。 + * 所有类型经已有来源授权后进入标准预览;未就绪附件保留原状态。 */ -import { useAuth } from '@/auth/AuthContext'; -import { openBrowserAsync } from 'expo-web-browser'; -import { Text } from '@/components/AppText'; -import { spacing, textStyles, useTheme } from '@/theme'; -import { useContext, useEffect, useMemo, useRef, useState } from 'react'; -import { ActivityIndicator, Alert, StyleSheet, View, useWindowDimensions } from 'react-native'; -import { useTranslation } from 'react-i18next'; -import type { BotGroupAttachment } from '@cindy/maker-shared/botGroupChat'; -import { ImageLightbox } from './ImageLightbox'; -import { BotGroupTextAttachmentPreview, botGroupTextAttachmentPreviewKind } from './BotGroupTextAttachmentPreview'; -import { MessageListVisibilityContext, useMessageListItemVisible } from './messageListVisibility'; -import { AttachmentStrip } from './MessageRenderer'; -import { buildMessageContentLayout } from './messageContentLayout'; -import type { MobileMessageGalleryImage } from './messageGallery'; -import type { NormalizedAttachment } from './messageNormalize'; -import { buildAttachmentPayload, isPayloadDirectPreviewableUrl, type MessagePayload } from './messagePayload'; -import type { ResolveRemoteMediaFn } from './remoteMedia'; +import { useAuth } from "@/auth/AuthContext"; +import { RemoteAttachmentPreview } from "./RemoteAttachmentPreview"; +import { Text } from "@/components/AppText"; +import { spacing, textStyles, useTheme } from "@/theme"; +import { useContext, useEffect, useMemo, useRef, useState } from "react"; +import { + ActivityIndicator, + Alert, + StyleSheet, + View, + useWindowDimensions, +} from "react-native"; +import { useTranslation } from "react-i18next"; +import type { BotGroupAttachment } from "@cindy/maker-shared/botGroupChat"; +import { + MessageListVisibilityContext, + useMessageListItemVisible, +} from "./messageListVisibility"; +import { AttachmentStrip } from "./MessageRenderer"; +import { buildMessageContentLayout } from "./messageContentLayout"; +import type { MobileMessageGalleryImage } from "./messageGallery"; +import type { NormalizedAttachment } from "./messageNormalize"; +import { + buildAttachmentPayload, + isPayloadDirectPreviewableUrl, + type MessagePayload, +} from "./messagePayload"; +import type { ResolveRemoteMediaFn } from "./remoteMedia"; /** * A group attachment in the shape session messages render; an image without an address is a file. * An authorized HTTPS image loads directly; `cindy-media://` and older media addresses are fetched from the computer. */ -export function botGroupAttachmentForDisplay(attachment: BotGroupAttachment): NormalizedAttachment { - return attachment.category === 'image' && attachment.url - ? { kind: 'image', name: attachment.name, uri: attachment.url, ...(attachment.mimeType ? { mimeType: attachment.mimeType } : {}), - previewable: isPayloadDirectPreviewableUrl(attachment.url) } - : { kind: 'file', name: attachment.name, ...(attachment.mimeType ? { mimeType: attachment.mimeType } : {}), previewable: false }; +export function botGroupAttachmentForDisplay( + attachment: BotGroupAttachment, +): NormalizedAttachment { + return attachment.category === "image" && attachment.url + ? { + kind: "image", + name: attachment.name, + uri: attachment.url, + ...(attachment.mimeType ? { mimeType: attachment.mimeType } : {}), + previewable: isPayloadDirectPreviewableUrl(attachment.url), + } + : { + kind: "file", + name: attachment.name, + ...(attachment.mimeType ? { mimeType: attachment.mimeType } : {}), + previewable: false, + }; } /** The message's pictures as one gallery, keyed like the strip's open payloads. */ -export function botGroupAttachmentGallery(messageId: string, attachments: readonly NormalizedAttachment[]): MobileMessageGalleryImage[] { +export function botGroupAttachmentGallery( + messageId: string, + attachments: readonly NormalizedAttachment[], +): MobileMessageGalleryImage[] { return attachments.flatMap((attachment, index) => { - if (attachment.kind !== 'image') return []; + if (attachment.kind !== "image") return []; const payload = buildAttachmentPayload(attachment); - return payload.kind === 'media' - ? [{ key: `${messageId}:attachment:${index}`, title: attachment.name, url: payload.media.url, payload, groupKey: messageId }] + return payload.kind === "media" + ? [ + { + key: `${messageId}:attachment:${index}`, + title: attachment.name, + url: payload.media.url, + payload, + groupKey: messageId, + }, + ] : []; }); } -export function BotGroupMessageAttachments({ messageId, attachments, align, onResolveRemoteMedia, resolveServerMedia, previewServerImages = true }: { +export function BotGroupMessageAttachments({ + messageId, + attachments, + align, + onResolveRemoteMedia, + resolveServerMedia, + previewServerImages = true, +}: { messageId: string; attachments: readonly BotGroupAttachment[]; - align: 'left' | 'right'; + align: "left" | "right"; onResolveRemoteMedia: ResolveRemoteMediaFn; - resolveServerMedia?: (id: string, signal?: AbortSignal) => Promise; + resolveServerMedia?: ( + id: string, + signal?: AbortSignal, + ) => Promise; /** Cloud transcripts keep the tap-only contract in cloud-companions.md §3.1. */ previewServerImages?: boolean; }) { @@ -60,30 +105,115 @@ export function BotGroupMessageAttachments({ messageId, attachments, align, onRe const visibility = useContext(MessageListVisibilityContext); const rowVisible = useMessageListItemVisible(messageId); const previewVisible = previewServerImages && (!visibility || rowVisible); - const layout = useMemo(() => buildMessageContentLayout({ screenWidth: width }), [width]); - const items = useMemo(() => attachments.filter(attachment => !attachment.state).map(botGroupAttachmentForDisplay), [attachments]); - const gallery = useMemo(() => botGroupAttachmentGallery(messageId, items), [items, messageId]); - const runs = useMemo(() => botGroupAttachmentRuns(attachments), [attachments]); - const [openUrl, setOpenUrl] = useState(null); - if (resolveServerMedia) return ; - const open = (payload: MessagePayload) => { - if (payload.kind === 'media' && payload.media.kind === 'image') { - setOpenUrl(payload.media.url); + const layout = useMemo( + () => buildMessageContentLayout({ screenWidth: width }), + [width], + ); + const runs = useMemo( + () => botGroupAttachmentRuns(attachments), + [attachments], + ); + const [deviceFile, setDeviceFile] = useState(null); + const [deviceCollection, setDeviceCollection] = useState< + readonly BotGroupAttachment[] + >([]); + if (resolveServerMedia) + return ( + + ); + const readyAttachments = attachments.filter( + (attachment) => !attachment.state, + ); + const openDeviceFile = (attachment: BotGroupAttachment | undefined) => { + if (!attachment) return; + if (!attachment.url && !attachment.path) { + Alert.alert(attachment.name, t("groupChat.files.onComputer")); return; } - Alert.alert(payload.kind === 'file' ? payload.title : '', t('groupChat.files.onComputer')); + setDeviceFile(attachment); + }; + const open = (payload: MessagePayload) => { + if (payload.kind === "media") { + setDeviceCollection([...readyAttachments]); + setDeviceFile( + readyAttachments.find( + (attachment) => attachment.url === payload.media.url, + ) ?? null, + ); + } }; - return <> - {/* Sent order: an attachment the computer has not fetched yet keeps its place with its state. */} - {runs.map((run, index) => run.kind === 'pending' - ? - : )} - {openUrl ? setOpenUrl(null)} - onResolveRemoteMedia={onResolveRemoteMedia} /> : null} - ; + const deviceSource = + deviceFile?.url ?? + (deviceFile?.path + ? `xdt-file://open?path=${encodeURIComponent(deviceFile.path)}` + : null); + return ( + 1 + ? { + names: deviceCollection.map((item) => item.name), + index: deviceCollection.findIndex( + (item) => item.id === deviceFile.id, + ), + onSelect: (index) => openDeviceFile(deviceCollection[index]), + } + : undefined + } + > + {/* Sent order: an attachment the computer has not fetched yet keeps its place with its state. */} + {runs.map((run, index) => + run.kind === "pending" ? ( + + ) : ( + { + setDeviceCollection([...readyAttachments]); + openDeviceFile(run.attachments[index]); + }} + onResolveRemoteMedia={onResolveRemoteMedia} + usePreviewState={useState} + /> + ), + )} + {deviceFile && deviceSource ? ( + + onResolveRemoteMedia( + { + kind: deviceFile.category === "image" ? "image" : "file", + url: deviceSource, + previewable: false, + }, + { front: true, signal }, + ) + } + onClose={() => setDeviceFile(null)} + /> + ) : null} + + ); } /** @@ -91,40 +221,85 @@ export function BotGroupMessageAttachments({ messageId, attachments, align, onRe * files, so a strip only ever holds neighbours of one display kind; each attachment not on * the computer yet stands alone. */ -export function botGroupAttachmentRuns(attachments: readonly BotGroupAttachment[]): Array< - { kind: 'ready'; display: NormalizedAttachment['kind']; attachments: BotGroupAttachment[] } - | { kind: 'pending'; attachment: BotGroupAttachment & { state: NonNullable } } +export function botGroupAttachmentRuns( + attachments: readonly BotGroupAttachment[], +): Array< + | { + kind: "ready"; + display: NormalizedAttachment["kind"]; + attachments: BotGroupAttachment[]; + } + | { + kind: "pending"; + attachment: BotGroupAttachment & { + state: NonNullable; + }; + } > { const runs: ReturnType = []; for (const attachment of attachments) { const { state } = attachment; - if (state) { runs.push({ kind: 'pending', attachment: { ...attachment, state } }); continue; } + if (state) { + runs.push({ kind: "pending", attachment: { ...attachment, state } }); + continue; + } const display = botGroupAttachmentForDisplay(attachment).kind; const last = runs.at(-1); - if (last?.kind === 'ready' && last.display === display) last.attachments.push(attachment); - else runs.push({ kind: 'ready', display, attachments: [attachment] }); + if (last?.kind === "ready" && last.display === display) + last.attachments.push(attachment); + else runs.push({ kind: "ready", display, attachments: [attachment] }); } return runs; } /** The same file chip, plus the computer's loading / unavailable state; tapping explains it. */ -function PendingAttachment({ attachment, align, layout }: { - attachment: BotGroupAttachment & { state: NonNullable }; - align: 'left' | 'right'; +function PendingAttachment({ + attachment, + align, + layout, +}: { + attachment: BotGroupAttachment & { + state: NonNullable; + }; + align: "left" | "right"; layout: ReturnType; }) { const { t } = useTranslation(); const { colors } = useTheme(); - const loading = attachment.state === 'loading'; - const status = t(loading ? 'groupChat.files.loading' : 'groupChat.files.unavailable'); - return - Alert.alert(attachment.name, status)} usePreviewState={useState} /> - - {loading ? : null} - {status} + const loading = attachment.state === "loading"; + const status = t( + loading ? "groupChat.files.loading" : "groupChat.files.unavailable", + ); + return ( + + Alert.alert(attachment.name, status)} + usePreviewState={useState} + /> + + {loading ? ( + + ) : null} + + {status} + + - ; + ); } // Bound passive authorization across mounted messages; no account data or URLs are cached here. @@ -146,8 +321,13 @@ function finishPreview(job: PreviewJob) { } drainPreviews(); } -function authorizePreview(run: PreviewJob['run']): () => void { - const job: PreviewJob = { run, controller: new AbortController(), active: false, finished: false }; +function authorizePreview(run: PreviewJob["run"]): () => void { + const job: PreviewJob = { + run, + controller: new AbortController(), + active: false, + finished: false, + }; previewJobs.push(job); drainPreviews(); return () => { @@ -161,134 +341,220 @@ function drainPreviews() { const job = previewJobs.shift()!; job.active = true; activePreviews++; - void job.run(job.controller.signal).then(() => finishPreview(job), () => finishPreview(job)); + void job.run(job.controller.signal).then( + () => finishPreview(job), + () => finishPreview(job), + ); } } /** Image preview URLs live only in this mounted message; opening always re-authorizes. */ -function ServerAttachments({ runs, visible, previewServerImages, align, layout, resolve }: { +function ServerAttachments({ + runs, + visible, + previewServerImages, + align, + layout, + resolve, +}: { visible: boolean; previewServerImages: boolean; - align: 'left' | 'right'; + align: "left" | "right"; runs: ReturnType; layout: ReturnType; resolve(id: string, signal?: AbortSignal): Promise; }) { const { t } = useTranslation(); const { colors } = useTheme(); - const [previews, setPreviews] = useState>({}); + const [previews, setPreviews] = useState>( + {}, + ); const failedPreviewIds = useRef(new Set()); const retriedPreviewIds = useRef(new Set()); const retryPreview = useRef<((id: string) => void) | null>(null); - const [previewVersions, setPreviewVersions] = useState>({}); - const resolveRef = useRef(resolve); resolveRef.current = resolve; - const previewsRef = useRef(previews); previewsRef.current = previews; - const previewIds = JSON.stringify(runs.flatMap(run => run.kind === 'ready' ? run.attachments.map(item => item.id) : [])); + const [previewVersions, setPreviewVersions] = useState< + Record + >({}); + const resolveRef = useRef(resolve); + resolveRef.current = resolve; + const previewsRef = useRef(previews); + previewsRef.current = previews; + const previewIds = JSON.stringify( + runs.flatMap((run) => + run.kind === "ready" ? run.attachments.map((item) => item.id) : [], + ), + ); useEffect(() => { let cancelled = false; if (!visible) return; const cancelJobs: Array<() => void> = []; const schedule = (id: string) => { - cancelJobs.push(authorizePreview(async signal => { - if (cancelled || (previewsRef.current[id] && !failedPreviewIds.current.has(id))) return; - const refreshing = failedPreviewIds.current.has(id); - const previousPreview = previewsRef.current[id]; - try { - const value = await resolveRef.current(id, signal); - const preview = value.category === 'image' ? value : { ...value, url: null }; - if (!cancelled && !signal.aborted && previewsRef.current[id] === previousPreview) { - previewsRef.current = { ...previewsRef.current, [id]: preview }; - setPreviews(previewsRef.current); - failedPreviewIds.current.delete(id); - // A renewed authorization can return the same URL after a transient CDN failure. - // Remount only the failed image too, resetting its native error state. - if (refreshing) setPreviewVersions(versions => ({ ...versions, [id]: (versions[id] ?? 0) + 1 })); + cancelJobs.push( + authorizePreview(async (signal) => { + if ( + cancelled || + (previewsRef.current[id] && !failedPreviewIds.current.has(id)) + ) + return; + const refreshing = failedPreviewIds.current.has(id); + const previousPreview = previewsRef.current[id]; + try { + const value = await resolveRef.current(id, signal); + const preview = + value.category === "image" ? value : { ...value, url: null }; + if ( + !cancelled && + !signal.aborted && + previewsRef.current[id] === previousPreview + ) { + previewsRef.current = { ...previewsRef.current, [id]: preview }; + setPreviews(previewsRef.current); + failedPreviewIds.current.delete(id); + // A renewed authorization can return the same URL after a transient CDN failure. + // Remount only the failed image too, resetting its native error state. + if (refreshing) + setPreviewVersions((versions) => ({ + ...versions, + [id]: (versions[id] ?? 0) + 1, + })); + } + } catch { + if (!cancelled && previewsRef.current[id] === previousPreview) + failedPreviewIds.current.delete(id); + // Keep the chip / fallback available for an explicit retry on tap. } - } catch { - if (!cancelled && previewsRef.current[id] === previousPreview) failedPreviewIds.current.delete(id); - // Keep the chip / fallback available for an explicit retry on tap. - } - })); + }), + ); }; retryPreview.current = schedule; for (const id of JSON.parse(previewIds) as string[]) schedule(id); return () => { cancelled = true; retryPreview.current = null; - cancelJobs.forEach(cancel => cancel()); + cancelJobs.forEach((cancel) => cancel()); }; }, [previewIds, visible]); - const displayRuns = botGroupAttachmentRuns(runs.flatMap(run => run.kind === 'pending' - ? [run.attachment] : run.attachments.map(item => previews[item.id] ?? { ...item, url: null }))); - const [image, setImage] = useState(null); - const [textPreview, setTextPreview] = useState<{ id: string; name: string; kind: NonNullable> } | null>(null); - const [busy, setBusy] = useState(null); - const pending = useRef(false); - const mounted = useRef(true); - useEffect(() => { mounted.current = true; return () => { mounted.current = false; }; }, []); - const open = async (id: string) => { - if (pending.current) return; - const attachment = runs.flatMap(run => run.kind === 'ready' ? run.attachments : []).find(item => item.id === id); - const previewKind = attachment?.category === 'file' ? botGroupTextAttachmentPreviewKind(attachment.name) : null; - if (attachment && previewKind) { - setTextPreview({ id, name: attachment.name, kind: previewKind }); - return; - } - pending.current = true; setBusy(id); - try { - const value = await resolve(id); - if (!mounted.current) return; - if (!value.url) throw new Error('MEDIA_NOT_FOUND'); - if (value.category === 'image') { - setImage(value); - // Cloud transcripts never populate this cache: their media remains tap-only. - if (previewServerImages) { - previewsRef.current = { ...previewsRef.current, [id]: value }; - setPreviews(previewsRef.current); - failedPreviewIds.current.delete(id); - setPreviewVersions(versions => ({ ...versions, [id]: (versions[id] ?? 0) + 1 })); - } - } - else await openBrowserAsync(value.url); - } catch { if (mounted.current) Alert.alert(t('groupChat.server.attachmentFailed')); } - finally { pending.current = false; if (mounted.current) setBusy(null); } + const displayRuns = botGroupAttachmentRuns( + runs.flatMap((run) => + run.kind === "pending" + ? [run.attachment] + : run.attachments.map( + (item) => previews[item.id] ?? { ...item, url: null }, + ), + ), + ); + const [filePreview, setFilePreview] = useState<{ + id: string; + name: string; + } | null>(null); + const readyAttachments = runs.flatMap((run) => + run.kind === "ready" ? run.attachments : [], + ); + const [openedCollection, setOpenedCollection] = useState< + readonly BotGroupAttachment[] + >([]); + const open = (id: string) => { + setOpenedCollection([...readyAttachments]); + const attachment = readyAttachments.find((item) => item.id === id); + if (attachment) setFilePreview({ id, name: attachment.name }); }; - return <> - {/* Stretch like a session strip, so the chip keeps the same width as in a task message. */} - {displayRuns.map(run => run.kind === 'pending' - ? - : - previewVersions[item.id] ?? 0)} - messageKey={run.attachments[0]!.id} align={align} layout={layout} - onOpen={payload => { - if (payload.kind === 'media') { - const attachment = run.attachments.find(item => item.url === payload.media.url); - if (attachment) void open(attachment.id); + return ( + 1 + ? { + names: openedCollection.map((item) => item.name), + index: openedCollection.findIndex( + (item) => item.id === filePreview.id, + ), + onSelect: (index) => { + const item = openedCollection[index]; + if (item) setFilePreview({ id: item.id, name: item.name }); + }, } + : undefined + } + > + {/* Stretch like a session strip, so the chip keeps the same width as in a task message. */} + {displayRuns.map((run) => + run.kind === "pending" ? ( + + ) : ( + + previewVersions[item.id] ?? 0, + )} + messageKey={run.attachments[0]!.id} + align={align} + layout={layout} + onOpen={(payload) => { + if (payload.kind === "media") { + const attachment = run.attachments.find( + (item) => item.url === payload.media.url, + ); + if (attachment) void open(attachment.id); + } + }} + onImageError={(index) => { + const attachment = run.attachments[index]; + if ( + !attachment || + retriedPreviewIds.current.has(attachment.id) || + previewsRef.current[attachment.id]?.url !== attachment.url + ) + return; + retriedPreviewIds.current.add(attachment.id); + failedPreviewIds.current.add(attachment.id); + retryPreview.current?.(attachment.id); + }} + onOpenFile={(index) => { + void open(run.attachments[index]!.id); + }} + usePreviewState={useState} + /> + + ), + )} + {filePreview ? ( + setFilePreview(null)} + resolve={async (signal) => { + const value = await resolve(filePreview.id, signal); + const url = new URL(value.url ?? ""); + if (url.protocol !== "https:" || url.username || url.password) + throw new Error("ATTACHMENT_PREVIEW_URL_INVALID"); + if (signal.aborted) throw new Error("ABORTED"); + if (previewServerImages && value.category === "image") { + previewsRef.current = { + ...previewsRef.current, + [filePreview.id]: value, + }; + setPreviews(previewsRef.current); + failedPreviewIds.current.delete(filePreview.id); + setPreviewVersions((versions) => ({ + ...versions, + [filePreview.id]: (versions[filePreview.id] ?? 0) + 1, + })); + } + return { url: url.href, mimeType: value.mimeType }; }} - onImageError={index => { - const attachment = run.attachments[index]; - if (!attachment || retriedPreviewIds.current.has(attachment.id) - || previewsRef.current[attachment.id]?.url !== attachment.url) return; - retriedPreviewIds.current.add(attachment.id); - failedPreviewIds.current.add(attachment.id); - retryPreview.current?.(attachment.id); - }} - onOpenFile={index => { void open(run.attachments[index]!.id); }} usePreviewState={useState} /> - {run.attachments.some(attachment => busy === attachment.id) - ? - : null} - )} - {image?.url ? setImage(null)} /> : null} - {textPreview ? setTextPreview(null)} /> : null} - ; + /> + ) : null} + + ); } const styles = StyleSheet.create({ - serverAttachment: { alignSelf: 'stretch' }, - busyLeft: { alignSelf: 'flex-start' }, - busyRight: { alignSelf: 'flex-end' }, + serverAttachment: { alignSelf: "stretch" }, + busyLeft: { alignSelf: "flex-start" }, + busyRight: { alignSelf: "flex-end" }, }); diff --git a/apps/mobile/src/session/BotGroupTextAttachmentPreview.tsx b/apps/mobile/src/session/BotGroupTextAttachmentPreview.tsx index 0889c4c7461..9fa203e7fd1 100644 --- a/apps/mobile/src/session/BotGroupTextAttachmentPreview.tsx +++ b/apps/mobile/src/session/BotGroupTextAttachmentPreview.tsx @@ -1,24 +1,37 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { TextDocumentPreview } from "./TextDocumentPreview"; +import { + useCallback, + useContext, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { ExternalLink, X } from 'lucide-react-native'; -import { ActivityIndicator, FlatList, Modal, Pressable, StyleSheet, View } from 'react-native'; -import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context'; +import { ExternalLink } from 'lucide-react-native'; +import { ActivityIndicator, + Alert, Modal, Pressable, StyleSheet, View } from 'react-native'; +import { SafeAreaProvider, SafeAreaView, + useSafeAreaInsets, +} from 'react-native-safe-area-context'; import { openBrowserAsync } from 'expo-web-browser'; import type { BotGroupAttachment } from '@cindy/maker-shared/botGroupChat'; import { extractRemoteFileExt, isHtmlFilePreviewCandidate, remoteFilePreviewKind } from '@cindy/maker-shared/file-preview'; import { Text } from '@/components/AppText'; +import * as Sharing from "expo-sharing"; +import { File } from "expo-file-system"; +import type { ResolveRemoteMediaFn } from "./remoteMedia"; +import { + PreviewHeader, + PreviewFooter, + PreviewCollectionContext, +} from "./PreviewChrome"; +import { DiagramDocumentPreview } from "./DiagramDocumentPreview"; +import { downloadRemoteMediaShareTemp } from "./remoteMediaDiskCacheExpo"; import { HtmlFileReader } from '@/session/HtmlFileReader'; import { MarkdownFileReader } from '@/session/MarkdownFileReader'; import { withDownloadedRemoteMediaFile } from '@/session/remoteMediaDiskCacheExpo'; -import { fontWeight, iconSize, lineHeight, monoFont, radius, spacing, textStyles, typeScale, useTheme } from '@/theme'; +import { iconSize, radius, spacing, textStyles, useTheme } from '@/theme'; const TEXT_ATTACHMENT_PREVIEW_MAX_BYTES = 10 * 1024 * 1024; -const JSON_ATTACHMENT_PREVIEW_MAX_LINES = 10_000; -const JSON_ATTACHMENT_PREVIEW_MAX_CHARACTERS = 100_000; -const JSON_ATTACHMENT_PREVIEW_MAX_LINE_CHARACTERS = 4_096; const NO_EXTERNAL_IMAGES: ReadonlyMap = new Map(); -export type BotGroupTextAttachmentPreviewKind = 'html' | 'markdown' | 'json'; +export type BotGroupTextAttachmentPreviewKind = 'html' | 'markdown' | 'json' | "text" | "diagram"; type PreviewState = | { status: 'loading' } @@ -33,28 +46,57 @@ type ResolveServerMedia = (id: string, signal?: AbortSignal) => Promise({ status: 'loading' }); const controller = useRef(null); const resolveRef = useRef(resolve); resolveRef.current = resolve; + const deviceRef = useRef(resolveDeviceMedia); + deviceRef.current = resolveDeviceMedia; + const authorizedSource = async (signal?: AbortSignal) => { + if (deviceRef.current && deviceSource) + return deviceRef.current( + { kind: "file", url: deviceSource, previewable: false }, + { front: true, signal }, + ); + if (!resolveRef.current) throw new Error("ATTACHMENT_RESOLVER_MISSING"); + const attachment = await resolveRef.current(attachmentId, signal); + if (attachment.category !== 'file' || !attachment.url || !isCredentialFreeHttpsUrl(attachment.url)) + throw new Error('ATTACHMENT_PREVIEW_URL_INVALID'); + return { url: attachment.url, mimeType: attachment.mimeType }; + }; + const authorizedRef = useRef(authorizedSource); + authorizedRef.current = authorizedSource; + const load = useCallback(async () => { controller.current?.abort(); const request = new AbortController(); @@ -62,16 +104,13 @@ export function BotGroupTextAttachmentPreview({ attachmentId, fileName, kind, re setState({ status: 'loading' }); let tooLarge = false; try { - const attachment = await resolveRef.current(attachmentId, request.signal); + const attachment = await authorizedRef.current(request.signal); if (request.signal.aborted) return; - if (attachment.category !== 'file' || !attachment.url || !isCredentialFreeHttpsUrl(attachment.url)) { - throw new Error('ATTACHMENT_PREVIEW_URL_INVALID'); - } const result = await withDownloadedRemoteMediaFile( attachment.url, attachment.mimeType || 'application/octet-stream', TEXT_ATTACHMENT_PREVIEW_MAX_BYTES, - async file => { + async (file) => { const text = await file.text(); return isLikelyBinaryText(text) ? { kind: 'binary' } : { kind: 'text', text }; }, @@ -97,10 +136,6 @@ export function BotGroupTextAttachmentPreview({ attachmentId, fileName, kind, re return () => { controller.current?.abort(); }; }, [load]); - const jsonPreview = useMemo( - () => state.status === 'ready' && kind === 'json' ? splitJsonPreviewLines(state.text) : null, - [kind, state], - ); const message = state.status === 'too-large' ? t('groupChat.server.attachmentPreviewTooLarge') : state.status === 'binary' @@ -114,11 +149,8 @@ export function BotGroupTextAttachmentPreview({ attachmentId, fileName, kind, re controller.current = request; setState({ status: 'loading' }); try { - const attachment = await resolveRef.current(attachmentId, request.signal); + const attachment = await authorizedRef.current(request.signal); if (request.signal.aborted) return; - if (attachment.category !== 'file' || !attachment.url || !isCredentialFreeHttpsUrl(attachment.url)) { - throw new Error('ATTACHMENT_PREVIEW_URL_INVALID'); - } await openBrowserAsync(attachment.url); if (!request.signal.aborted) onClose(); } catch { @@ -126,22 +158,73 @@ export function BotGroupTextAttachmentPreview({ attachmentId, fileName, kind, re } }; + const share = async () => { + if (sharing) return; + setSharing(true); + let uri: string | null = null; + const request = controller.current; + try { + const attachment = await authorizedRef.current(request?.signal); + if (request?.signal.aborted) return; + uri = await downloadRemoteMediaShareTemp( + attachment.url, + attachment.mimeType ?? "application/octet-stream", + fileName, + request?.signal, + ); + if (!uri) throw new Error("ATTACHMENT_DOWNLOAD_FAILED"); + if (!request?.signal.aborted) + await Sharing.shareAsync(uri, { mimeType: attachment.mimeType }); + } catch { + if (!request?.signal.aborted) + Alert.alert(t("groupChat.server.attachmentFailed")); + } finally { + if (uri) { + try { + const file = new File(uri); + if (file.exists) file.delete(); + } catch { + /* cache reclaimed */ + } + } + if (!request?.signal.aborted) setSharing(false); + } + }; + return ( - - - - - - {fileName} - + 1 ? 56 : 0, + }]}> + setSource((value) => !value), + }, + ] + : []), + { + key: "share", + label: t("message.attachmentPreview.share"), + disabled: sharing, + onPress: () => { + void share(); + }, + }, + ]} + /> {state.status === 'loading' ? ( @@ -156,7 +239,8 @@ export function BotGroupTextAttachmentPreview({ attachmentId, fileName, kind, re {t('groupChat.server.attachmentPreviewRetry')} ) : null} - {kind !== 'html' && (state.status === 'too-large' || state.status === 'binary') ? ( + {!resolveDeviceMedia && + kind !== 'html' && (state.status === 'too-large' || state.status === 'binary') ? ( { void openOutside(); }} style={[styles.action, { backgroundColor: colors.surfaceElevated, borderColor: colors.border }]} testID="groupChat.server.attachmentPreviewOpenOutside"> {t('groupChat.server.attachmentPreviewOpenOutside')} @@ -164,32 +248,20 @@ export function BotGroupTextAttachmentPreview({ attachmentId, fileName, kind, re ) : null} ) : null} - {state.status === 'ready' && kind === 'html' ? ( + {state.status === 'ready' && !source && kind === "diagram" ? ( + + ) : null} + {state.status === "ready" && !source && kind === 'html' ? ( ) : null} - {state.status === 'ready' && kind === 'markdown' ? ( + {state.status === 'ready' && !source && kind === 'markdown' ? ( ) : null} - {state.status === 'ready' && kind === 'json' ? ( - - {jsonPreview?.truncated ? ( - {t('groupChat.server.attachmentPreviewTruncated')} + {state.status === 'ready' && + (source || kind === "json" || kind === "text") ? ( + ) : null} - String(index)} - renderItem={({ item, index }) => ( - - {index + 1} - {item || ' '} - - )} - style={styles.fill} - contentContainerStyle={styles.codeList} - testID="groupChat.server.attachmentJsonPreview" - /> - - ) : null} + @@ -209,35 +281,9 @@ function isLikelyBinaryText(value: string): boolean { return /[\x00-\x08\x0B\x0E-\x1F]/.test(value); } -function splitJsonPreviewLines(text: string): { lines: string[]; truncated: boolean } { - const lines: string[] = []; - let start = 0; - let characters = 0; - while (lines.length < JSON_ATTACHMENT_PREVIEW_MAX_LINES && characters < JSON_ATTACHMENT_PREVIEW_MAX_CHARACTERS) { - const end = text.indexOf('\n', start); - const lineEnd = end < 0 ? text.length : end > start && text.charCodeAt(end - 1) === 13 ? end - 1 : end; - const line = text.slice(start, lineEnd); - const visible = line.slice(0, Math.min(JSON_ATTACHMENT_PREVIEW_MAX_LINE_CHARACTERS, JSON_ATTACHMENT_PREVIEW_MAX_CHARACTERS - characters)); - lines.push(visible); - characters += visible.length; - start = end < 0 ? text.length : end + 1; - if (visible.length < line.length) return { lines, truncated: true }; - if (end < 0) return { lines, truncated: false }; - } - return { lines, truncated: start < text.length }; -} - const styles = StyleSheet.create({ fill: { flex: 1 }, - header: { minHeight: 56, flexDirection: 'row', alignItems: 'center', gap: spacing.sm, paddingHorizontal: spacing.md, borderBottomWidth: StyleSheet.hairlineWidth }, - closeButton: { width: 44, height: 44, alignItems: 'center', justifyContent: 'center' }, - title: { flex: 1, minWidth: 0, fontWeight: fontWeight.semibold }, messageState: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: spacing.md, padding: spacing.xl }, stateCopy: { textAlign: 'center' }, - truncated: { paddingHorizontal: spacing.md, paddingTop: spacing.sm }, action: { minHeight: 44, flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: spacing.xs, paddingHorizontal: spacing.lg, borderRadius: radius.pill, borderWidth: StyleSheet.hairlineWidth }, - codeList: { paddingHorizontal: spacing.md, paddingVertical: spacing.sm }, - codeRow: { flexDirection: 'row', alignItems: 'flex-start' }, - lineNumber: { width: 40, paddingRight: spacing.sm, textAlign: 'right' }, - codeText: { flex: 1, minWidth: 0, fontFamily: monoFont, fontSize: typeScale.bodySmall, lineHeight: lineHeight.bodySmall }, }); diff --git a/apps/mobile/src/session/CompanionProfileArtifacts.tsx b/apps/mobile/src/session/CompanionProfileArtifacts.tsx index 867dbe817e3..1d8627359d4 100644 --- a/apps/mobile/src/session/CompanionProfileArtifacts.tsx +++ b/apps/mobile/src/session/CompanionProfileArtifacts.tsx @@ -1,34 +1,120 @@ -import { StyleSheet, View } from 'react-native'; -import { useTranslation } from 'react-i18next'; -import type { BotDelegationListResult } from '@cindy/maker-shared/botDelegation'; -import { Text } from '@/components/AppText'; -import { MainWindowActionButton } from '@/components/MobilePrimitives'; -import { useRemoteCompanionQuery } from './useRemoteCompanionQuery'; -import { companionArtifactRows } from './companionProfileData'; -import { lineHeight, spacing, typeScale, useThemedStyles, type ThemeColors } from '@/theme'; +import { Pressable, StyleSheet, View } from "react-native"; +import { useGuardedPush } from "@/utils/useGuardedPush"; +import { useTranslation } from "react-i18next"; +import type { BotDelegationListResult } from "@cindy/maker-shared/botDelegation"; +import { Text } from "@/components/AppText"; +import { MainWindowActionButton } from "@/components/MobilePrimitives"; +import { useRemoteCompanionQuery } from "./useRemoteCompanionQuery"; +import { companionArtifactRows } from "./companionProfileData"; +import { + lineHeight, + spacing, + typeScale, + useThemedStyles, + type ThemeColors, +} from "@/theme"; /** Existing task-output references only: no workspace scan, copied files or guessed media URLs. */ -export function CompanionProfileArtifacts({ deviceId, botId, sessionId, online, onOpenTask }: { - deviceId: string; botId: string; sessionId: string; online: boolean; onOpenTask: (sessionId: string) => void; +export function CompanionProfileArtifacts({ + deviceId, + botId, + sessionId, + online, + onOpenTask, +}: { + deviceId: string; + botId: string; + sessionId: string; + online: boolean; + onOpenTask: (sessionId: string) => void; }) { const { t } = useTranslation(); + const push = useGuardedPush(); const styles = useThemedStyles(makeStyles); - const { value, error, refresh } = useRemoteCompanionQuery(deviceId, 'maker:bot-delegations:list', [sessionId], { enabled: online && !!sessionId }); + const { value, error, refresh } = + useRemoteCompanionQuery( + deviceId, + "maker:bot-delegations:list", + [sessionId], + { enabled: online && !!sessionId }, + ); const rows = companionArtifactRows(value, botId); - return - {!online ? {t('devices.resources.hostOffline')} : null} - {rows.map(row => - {row.title} - {row.files.map((file, i) => {file})} - {row.childSessionId ? { - onOpenTask(row.childSessionId!); - } }} /> : null} - )} - {error || value?.ok === false ? <>{t('devices.companionProfile.readFailed')} : value?.ok && rows.length === 0 ? {t('devices.companionProfile.artifactsEmpty')} : !value && online ? {t('devices.resources.loading')} : null} - ; + return ( + + {!online ? ( + {t("devices.resources.hostOffline")} + ) : null} + {rows.map((row) => ( + + {row.title} + {row.files.map((file, i) => ( + { + if (row.childSessionId) + push({ + pathname: "/files/preview/[sessionId]", + params: { + sessionId: row.childSessionId, + deviceId, + relPath: file, + }, + }); + }} + > + {file} + + ))} + {row.childSessionId ? ( + { + onOpenTask(row.childSessionId!); + }, + }} + /> + ) : null} + + ))} + {error || value?.ok === false ? ( + <> + + {t("devices.companionProfile.readFailed")} + + + + ) : value?.ok && rows.length === 0 ? ( + + {t("devices.companionProfile.artifactsEmpty")} + + ) : !value && online ? ( + {t("devices.resources.loading")} + ) : null} + + ); } -const makeStyles = (colors: ThemeColors) => StyleSheet.create({ - content: { gap: spacing.md }, - body: { color: colors.textPrimary, fontSize: typeScale.body, lineHeight: lineHeight.body }, - note: { color: colors.textSecondary, fontSize: typeScale.footnote, lineHeight: lineHeight.caption }, -}); +const makeStyles = (colors: ThemeColors) => + StyleSheet.create({ + content: { gap: spacing.md }, + body: { + color: colors.textPrimary, + fontSize: typeScale.body, + lineHeight: lineHeight.body, + }, + note: { + color: colors.textSecondary, + fontSize: typeScale.footnote, + lineHeight: lineHeight.caption, + }, + }); diff --git a/apps/mobile/src/session/ComposerAttachmentPreview.tsx b/apps/mobile/src/session/ComposerAttachmentPreview.tsx new file mode 100644 index 00000000000..c5289e6fdc1 --- /dev/null +++ b/apps/mobile/src/session/ComposerAttachmentPreview.tsx @@ -0,0 +1,79 @@ +import { + ImageLightbox, + type ImageLightboxAnnotationConfig, +} from "./ImageLightbox"; +import { PreviewCollectionContext } from "./PreviewChrome"; +import { RemoteAttachmentPreview } from "./RemoteAttachmentPreview"; +import { isPayloadDirectPreviewableUrl } from "./messagePayload"; +import type { MobileMessageGalleryImage } from "./messageGallery"; +import type { ResolveRemoteMediaFn } from "./remoteMedia"; +import type { RemoteSerializedAttachment } from "./types"; + +/** One explicit draft collection; annotation continues to use the original image and strokes. */ +export function ComposerAttachmentPreview({ + attachments, + images, + selectedId, + annotation, + resolveRemoteMedia, + onSelect, + onClose, +}: { + attachments: readonly RemoteSerializedAttachment[]; + images: readonly MobileMessageGalleryImage[]; + selectedId: string | null; + annotation: ImageLightboxAnnotationConfig; + resolveRemoteMedia: ResolveRemoteMediaFn; + onSelect(id: string): void; + onClose(): void; +}) { + const index = attachments.findIndex((item) => item.id === selectedId); + const attachment = attachments[index]; + if (!attachment) return null; + const image = images.find((item) => item.key === attachment.id); + return ( + item.name), + onSelect: (next) => { + const item = attachments[next]; + if (item) onSelect(item.id); + }, + }} + > + {image ? ( + + ) : ( + { + const source = attachment.url || attachment.path; + if (!source) throw new Error("MEDIA_SOURCE_MISSING"); + const direct: boolean = isPayloadDirectPreviewableUrl(source); + if (direct) + return { url: source, mimeType: attachment.mimeType }; + return resolveRemoteMedia( + { + kind: "file", + url: source.startsWith("/") + ? `xdt-file://open?path=${encodeURIComponent(source)}` + : source, + previewable: false, + }, + { front: true, signal }, + ); + }} + /> + )} + + ); +} diff --git a/apps/mobile/src/session/ComposerAttachmentTray.tsx b/apps/mobile/src/session/ComposerAttachmentTray.tsx index d32a5fdcb41..b4dd945491c 100644 --- a/apps/mobile/src/session/ComposerAttachmentTray.tsx +++ b/apps/mobile/src/session/ComposerAttachmentTray.tsx @@ -109,10 +109,23 @@ export function ComposerAttachmentTray({ } return ( - + onPreview(attachment.id)} + style={{ + flex: 1, + flexDirection: "row", + alignItems: "center", + minHeight: 48, + gap: 8, + }} + > + {attachmentDisplayLabel(attachment)} + ); })} - {pendingUploads.map((pending) => (pending.kind === 'image' ? ( + {pendingUploads.map((pending) => + pending.kind === 'image' ? ( {pending.previewUri ? ( @@ -205,7 +219,7 @@ export function ComposerAttachmentTray({ - )))} + ))} {Array.from({ length: pastePlaceholderCount }, (_, index) => ( // 粘贴占位卡:还拿不到图片数据(原生后台读取中),灰底 + 转圈占住席位, // 让「粘贴」到「缩略图可见」之间没有任何空窗。无 X:此阶段没有可取消的 diff --git a/apps/mobile/src/session/DiagramDocumentPreview.tsx b/apps/mobile/src/session/DiagramDocumentPreview.tsx new file mode 100644 index 00000000000..598d03e6214 --- /dev/null +++ b/apps/mobile/src/session/DiagramDocumentPreview.tsx @@ -0,0 +1,112 @@ +import { useMemo, useRef, useState } from "react"; +import { Pressable, StyleSheet, View } from "react-native"; +import { WebView } from "react-native-webview"; +import { useTranslation } from "react-i18next"; +import { Text } from "@/components/AppText"; +import { spacing, textStyles, useTheme } from "@/theme"; +import { PreviewIconButton } from "./PreviewChrome"; +import { Minus, Plus, Scan } from "lucide-react-native"; +import { + MermaidDiagramWebView, + type MermaidDiagramWebViewHandle, +} from "./mermaidWebView"; +import { MOBILE_DRAWIO_JS } from "./documentPreviewAssets.generated"; + +/** The same offline draw.io distribution as desktop; document data never becomes script. */ +export function DiagramDocumentPreview({ + source, + name, +}: { + source: string; + name: string; +}) { + const { colors, mode } = useTheme(); + const { t } = useTranslation(); + const diagram = useRef(null); + const [failed, setFailed] = useState(false); + const drawio = /\.(drawio(?:\.svg)?|dio)$/i.test(name); + const html = useMemo(() => { + const options = JSON.stringify({ + xml: source, + nav: true, + resize: true, + toolbar: "zoom layers pages", + "dark-mode": mode === "dark" ? "1" : "0", + "check-visible-state": false, + }).replace(/
`; + }, [source, colors.surface, mode]); + if (!drawio) + return ( + + + + diagram.current?.zoom(0.8)} + /> + diagram.current?.zoom("fit")} + /> + diagram.current?.zoom(1.25)} + /> + + + ); + if (failed) + return ( + + + {t("files.preview.readFailed")} + + setFailed(false)} + style={{ minHeight: 48, justifyContent: "center" }} + > + + {t("message.attachmentPreview.retry")} + + + + ); + return ( + request.url === "about:blank"} + onMessage={(event) => { + if (event.nativeEvent.data === "error") setFailed(true); + }} + onError={() => setFailed(true)} + allowFileAccess={false} + allowUniversalAccessFromFileURLs={false} + javaScriptCanOpenWindowsAutomatically={false} + setSupportMultipleWindows={false} + style={styles.fill} + /> + ); +} + +const styles = StyleSheet.create({ fill: { flex: 1 } }); diff --git a/apps/mobile/src/session/DocumentPreviewChrome.tsx b/apps/mobile/src/session/DocumentPreviewChrome.tsx new file mode 100644 index 00000000000..c80717f00d7 --- /dev/null +++ b/apps/mobile/src/session/DocumentPreviewChrome.tsx @@ -0,0 +1,95 @@ +import { ActivityIndicator, Pressable, StyleSheet, View } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { useTranslation } from "react-i18next"; +import { Text } from "@/components/AppText"; +import { lineHeight, spacing, typeScale, useTheme } from "@/theme"; +import { PreviewHeader } from "./PreviewChrome"; +import type { DocumentPreviewChromeProps } from "./HtmlBrowserChrome.types"; + +export function DocumentPreviewChrome(p: DocumentPreviewChromeProps) { + const { t } = useTranslation(); + const { colors } = useTheme(); + const insets = useSafeAreaInsets(); + return ( + + + {p.notice ? ( + + {p.busy ? : null} + + {p.notice} + + {p.onCancelNotice ? ( + + + {t("message.lightbox.cancel")} + + + ) : null} + + ) : null} + + ); +} diff --git a/apps/mobile/src/session/HtmlBrowserChrome.ios.tsx b/apps/mobile/src/session/HtmlBrowserChrome.ios.tsx index a10ca0477f3..cc9acf28505 100644 --- a/apps/mobile/src/session/HtmlBrowserChrome.ios.tsx +++ b/apps/mobile/src/session/HtmlBrowserChrome.ios.tsx @@ -1,3 +1,4 @@ +import { DocumentPreviewChrome } from "./DocumentPreviewChrome"; import { Host } from "@expo/ui"; import { Button, @@ -33,7 +34,6 @@ import { shapes, } from "@expo/ui/swift-ui/modifiers"; import { ActivityIndicator, Alert, StyleSheet, View } from "react-native"; -import { Stack } from "expo-router"; import { useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { Text } from "@/components/AppText"; @@ -50,6 +50,14 @@ import { /** Native SwiftUI controls float above the WebView; the empty space passes touches through. */ export function HtmlBrowserChrome(p: HtmlBrowserChromeProps | DocumentPreviewChromeProps) { + return p.document ? ( + + ) : ( + + ); +} + +function WebsitePreviewChrome(p: HtmlBrowserChromeProps) { const { colors, mode } = useTheme(); const { t } = useTranslation(); const glass = useLiquidGlassAvailable(); @@ -57,14 +65,14 @@ export function HtmlBrowserChrome(p: HtmlBrowserChromeProps | DocumentPreviewChr const systemBack = useSystemNavigationBack() && !p.standalone; const sharedStatusRow = usePreviewChromeFrame().sharedStatusRow && !p.standalone; // Center the document title on screen, except beside a status area sharing its row. - const centeredTitle = p.document && !sharedStatusRow; const [editing, setEditing] = useState(false); - const addressText = useNativeState(p.document ? '' : p.address); + const addressText = useNativeState(p.address); const addressRef = useRef(null); - const beginEditing = () => { if (!p.document) { addressText.set(p.address); setEditing(true); } }; - const submitAddress = () => { if (!p.document && p.onNavigate(addressText.get())) setEditing(false); }; + const beginEditing = () => { + { addressText.set(p.address); setEditing(true); } }; + const submitAddress = () => { if (p.onNavigate(addressText.get())) setEditing(false); }; const showFileInfo = () => Alert.alert(p.title, [p.meta, p.path].filter(Boolean).join('\n')); - const website = !p.document && p.website; + const website = p.website; // Size the visible glass itself, not an outer frame around a system-sized label. // Menu and Button have different intrinsic sizes (especially ellipsis vs share). // Untinted glass adapts to the page behind it: a light page in Dark mode turned it light @@ -112,30 +120,27 @@ export function HtmlBrowserChrome(p: HtmlBrowserChromeProps | DocumentPreviewChr accessibilityLabel(t("files.preview.browserMore")), ]} > - {p.document ? - } + + )} - {p.document ? {p.title} : editing ? : } - {p.document ? null : editing ? \n \n \n\n \n\n
\n \n
\n
\n
\n
\n\n
\n
\n \n ${Ko`\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n`}\n \n
\n
\n\n
\n \n \n
\n
\n \n \n
\n\n
\n \n\n
\n \n
\n
\n
\n
\n
\n\n \n
\n\n
\n \n
`,$f=(function(){var s=new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,3,2,0,0,5,3,1,0,1,12,1,0,10,22,2,12,0,65,0,65,0,65,0,252,10,0,0,11,7,0,65,0,253,15,26,11]),e=new Uint8Array([32,0,65,2,1,106,34,33,3,128,11,4,13,64,6,253,10,7,15,116,127,5,8,12,40,16,19,54,20,9,27,255,113,17,42,67,24,23,146,148,18,14,22,45,70,69,56,114,101,21,25,63,75,136,108,28,118,29,73,115]);if(typeof WebAssembly!=\"object\")return{supported:!1};var t,i=WebAssembly.validate(s)?r(\"b9H79TebbbeKl9Gbb9Gvuuuuueu9Giuuub9Geueuixkbbebeeddddilve9Weeeviebeoweuec:q:6dkr;Neqo9TW9T9VV95dbH9F9F939H79T9F9J9H229F9Jt9VV7bb8A9TW79O9V9Wt9F9KW9J9V9KW9wWVtW949c919M9MWVbdY9TW79O9V9Wt9F9KW9J9V9KW69U9KW949c919M9MWVblE9TW79O9V9Wt9F9KW9J9V9KW69U9KW949tWG91W9U9JWbvL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9p9JtboK9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9r919HtbrL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWVT949WbwY9TW79O9V9Wt9F9KW9J9V9KWS9P2tWVJ9V29VVbDl79IV9Rbqq:Ctklbzik9:evu8Jjjjjbcz9Rhbcbheincbhdcbhiinabcwfadfaicjuaead4ceGglE86bbaialfhiadcefgdcw9hmbkaec:q:yjjbfai86bbaecitc:q1jjbfab8Piw83ibaecefgecjd9hmbkk:183lYud97dur978Jjjjjbcj;kb9Rgv8Kjjjjbc9:hodnalTmbcuhoaiRbbgrc;WeGc:Ge9hmbarcsGgwce0mbc9:hoalcufadcd4cbawEgDadfgrcKcaawEgqaraq0Egk6mbaicefhxavaialfgmar9Rgoad;8qbbcj;abad9Uc;WFbGcjdadca0EhPdndndnadTmbaoadfhscbhzinaeaz9nmdamax9RaD6miabazad2fhHaxaDfhOaPaeaz9RazaPfae6EgAcsfgocl4cifcd4hCavcj;cbfaoc9WGgXcetfhQavcj;cbfaXci2fhLavcj;cbfaXfhKcbhYaoc;ab6h8AincbhodnawTmbaxaYcd4fRbbhokaocFeGhEcbh3avcj;cbfh5indndndndnaEa3cet4ciGgoc9:fPdebdkamaO9RaX6mwavcj;cbfa3aX2faOaX;8qbbaOaAfhOxdkavcj;cbfa3aX2fcbaX;8kbxekamaO9RaC6moaoclVcbawEhraOaCfhocbhidna8Ambamao9Rc;Gb6mbcbhlina5alfhidndndndndndnaOalco4fRbbgqciGarfPDbedibledibkaipxbbbbbbbbbbbbbbbbpklbxlkaiaopbblaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLg8Ecdp:mea8EpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9og8Fpxiiiiiiiiiiiiiiiip8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaaaoclffahc:q:yjjbfRbbfhoxikaiaopbbwaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9og8Fpxssssssssssssssssp8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaaaocwffahc:q:yjjbfRbbfhoxdkaiaopbbbpklbaoczfhoxekaiaopbbdaoRbbgacitc:q1jjbfpbibaac:q:yjjbfRbbgapsaoRbeghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPpklbaaaocdffahc:q:yjjbfRbbfhokdndndndndndnaqcd4ciGarfPDbedibledibkaiczfpxbbbbbbbbbbbbbbbbpklbxlkaiczfaopbblaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLg8Ecdp:mea8EpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9og8Fpxiiiiiiiiiiiiiiiip8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaaaoclffahc:q:yjjbfRbbfhoxikaiczfaopbbwaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9og8Fpxssssssssssssssssp8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaaaocwffahc:q:yjjbfRbbfhoxdkaiczfaopbbbpklbaoczfhoxekaiczfaopbbdaoRbbgacitc:q1jjbfpbibaac:q:yjjbfRbbgapsaoRbeghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPpklbaaaocdffahc:q:yjjbfRbbfhokdndndndndndnaqcl4ciGarfPDbedibledibkaicafpxbbbbbbbbbbbbbbbbpklbxlkaicafaopbblaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLg8Ecdp:mea8EpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9og8Fpxiiiiiiiiiiiiiiiip8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaaaoclffahc:q:yjjbfRbbfhoxikaicafaopbbwaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9og8Fpxssssssssssssssssp8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaaaocwffahc:q:yjjbfRbbfhoxdkaicafaopbbbpklbaoczfhoxekaicafaopbbdaoRbbgacitc:q1jjbfpbibaac:q:yjjbfRbbgapsaoRbeghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPpklbaaaocdffahc:q:yjjbfRbbfhokdndndndndndnaqco4arfPDbedibledibkaic8Wfpxbbbbbbbbbbbbbbbbpklbxlkaic8Wfaopbblaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLg8Ecdp:mea8EpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9og8Fpxiiiiiiiiiiiiiiiip8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngicitc:q1jjbfpbibaic:q:yjjbfRbbgipsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Ngqcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaiaoclffaqc:q:yjjbfRbbfhoxikaic8Wfaopbbwaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9og8Fpxssssssssssssssssp8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngicitc:q1jjbfpbibaic:q:yjjbfRbbgipsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Ngqcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spklbaiaocwffaqc:q:yjjbfRbbfhoxdkaic8Wfaopbbbpklbaoczfhoxekaic8WfaopbbdaoRbbgicitc:q1jjbfpbibaic:q:yjjbfRbbgipsaoRbegqcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPpklbaiaocdffaqc:q:yjjbfRbbfhokalc;abfhialcjefaX0meaihlamao9Rc;Fb0mbkkdnaiaX9pmbaici4hlinamao9RcK6mwa5aifhqdndndndndndnaOaico4fRbbalcoG4ciGarfPDbedibledibkaqpxbbbbbbbbbbbbbbbbpkbbxlkaqaopbblaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLg8Ecdp:mea8EpmbzeHdOiAlCvXoQrLpxiiiiiiiiiiiiiiiip9og8Fpxiiiiiiiiiiiiiiiip8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spkbbaaaoclffahc:q:yjjbfRbbfhoxikaqaopbbwaopbbbg8Eclp:mea8EpmbzeHdOiAlCvXoQrLpxssssssssssssssssp9og8Fpxssssssssssssssssp8Jg8Ep5b9cjF;8;4;W;G;ab9:9cU1:Ngacitc:q1jjbfpbibaac:q:yjjbfRbbgapsa8Ep5e9cjF;8;4;W;G;ab9:9cU1:Nghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPa8Fa8Ep9spkbbaaaocwffahc:q:yjjbfRbbfhoxdkaqaopbbbpkbbaoczfhoxekaqaopbbdaoRbbgacitc:q1jjbfpbibaac:q:yjjbfRbbgapsaoRbeghcitc:q1jjbfpbibp9UpmbedilvorzHOACXQLpPpkbbaaaocdffahc:q:yjjbfRbbfhokalcdfhlaiczfgiaX6mbkkaohOaoTmoka5aXfh5a3cefg3cl9hmbkdndndndnawTmbasaYcd4fRbbglciGPlbedwbkaXTmdavcjdfaYfhlavaYfpbdbhgcbhoinalavcj;cbfaofpblbg8JaKaofpblbg8KpmbzeHdOiAlCvXoQrLg8LaQaofpblbg8MaLaofpblbg8NpmbzeHdOiAlCvXoQrLgypmbezHdiOAlvCXorQLg8Ecep9Ta8Epxeeeeeeeeeeeeeeeeg8Fp9op9Hp9rg8Eagp9Uggp9Abbbaladfglaga8Ea8Epmlvorlvorlvorlvorp9Uggp9Abbbaladfglaga8Ea8EpmwDqkwDqkwDqkwDqkp9Uggp9Abbbaladfglaga8Ea8EpmxmPsxmPsxmPsxmPsp9Uggp9Abbbaladfglaga8LaypmwDKYqk8AExm35Ps8E8Fg8Ecep9Ta8Ea8Fp9op9Hp9rg8Ep9Uggp9Abbbaladfglaga8Ea8Epmlvorlvorlvorlvorp9Uggp9Abbbaladfglaga8Ea8EpmwDqkwDqkwDqkwDqkp9Uggp9Abbbaladfglaga8Ea8EpmxmPsxmPsxmPsxmPsp9Uggp9Abbbaladfglaga8Ja8KpmwKDYq8AkEx3m5P8Es8Fg8Ja8Ma8NpmwKDYq8AkEx3m5P8Es8Fg8KpmbezHdiOAlvCXorQLg8Ecep9Ta8Ea8Fp9op9Hp9rg8Ep9Uggp9Abbbaladfglaga8Ea8Epmlvorlvorlvorlvorp9Uggp9Abbbaladfglaga8Ea8EpmwDqkwDqkwDqkwDqkp9Uggp9Abbbaladfglaga8Ea8EpmxmPsxmPsxmPsxmPsp9Uggp9Abbbaladfglaga8Ja8KpmwDKYqk8AExm35Ps8E8Fg8Ecep9Ta8Ea8Fp9op9Hp9rg8Ep9Ug8Fp9Abbbaladfgla8Fa8Ea8Epmlvorlvorlvorlvorp9Ug8Fp9Abbbaladfgla8Fa8Ea8EpmwDqkwDqkwDqkwDqkp9Ug8Fp9Abbbaladfgla8Fa8Ea8EpmxmPsxmPsxmPsxmPsp9Uggp9AbbbaladfhlaoczfgoaX6mbxikkaXTmeavcjdfaYfhlavaYfpbdbhgcbhoinalavcj;cbfaofpblbg8JaKaofpblbg8KpmbzeHdOiAlCvXoQrLg8LaQaofpblbg8MaLaofpblbg8NpmbzeHdOiAlCvXoQrLgypmbezHdiOAlvCXorQLg8Ecep:nea8Epxebebebebebebebebg8Fp9op:bep9rg8Eagp:oeggp9Abbbaladfglaga8Ea8Epmlvorlvorlvorlvorp:oeggp9Abbbaladfglaga8Ea8EpmwDqkwDqkwDqkwDqkp:oeggp9Abbbaladfglaga8Ea8EpmxmPsxmPsxmPsxmPsp:oeggp9Abbbaladfglaga8LaypmwDKYqk8AExm35Ps8E8Fg8Ecep:nea8Ea8Fp9op:bep9rg8Ep:oeggp9Abbbaladfglaga8Ea8Epmlvorlvorlvorlvorp:oeggp9Abbbaladfglaga8Ea8EpmwDqkwDqkwDqkwDqkp:oeggp9Abbbaladfglaga8Ea8EpmxmPsxmPsxmPsxmPsp:oeggp9Abbbaladfglaga8Ja8KpmwKDYq8AkEx3m5P8Es8Fg8Ja8Ma8NpmwKDYq8AkEx3m5P8Es8Fg8KpmbezHdiOAlvCXorQLg8Ecep:nea8Ea8Fp9op:bep9rg8Ep:oeggp9Abbbaladfglaga8Ea8Epmlvorlvorlvorlvorp:oeggp9Abbbaladfglaga8Ea8EpmwDqkwDqkwDqkwDqkp:oeggp9Abbbaladfglaga8Ea8EpmxmPsxmPsxmPsxmPsp:oeggp9Abbbaladfglaga8Ja8KpmwDKYqk8AExm35Ps8E8Fg8Ecep:nea8Ea8Fp9op:bep9rg8Ep:oeg8Fp9Abbbaladfgla8Fa8Ea8Epmlvorlvorlvorlvorp:oeg8Fp9Abbbaladfgla8Fa8Ea8EpmwDqkwDqkwDqkwDqkp:oeg8Fp9Abbbaladfgla8Fa8Ea8EpmxmPsxmPsxmPsxmPsp:oeggp9AbbbaladfhlaoczfgoaX6mbxdkkaXTmbcbhocbalcl4gl9Rc8FGhiavcjdfaYfhravaYfpbdbh8Finaravcj;cbfaofpblbggaKaofpblbg8JpmbzeHdOiAlCvXoQrLg8KaQaofpblbg8LaLaofpblbg8MpmbzeHdOiAlCvXoQrLg8NpmbezHdiOAlvCXorQLg8Eaip:Rea8Ealp:Sep9qg8Ea8Fp9rg8Fp9Abbbaradfgra8Fa8Ea8Epmlvorlvorlvorlvorp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmwDqkwDqkwDqkwDqkp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmxmPsxmPsxmPsxmPsp9rg8Fp9Abbbaradfgra8Fa8Ka8NpmwDKYqk8AExm35Ps8E8Fg8Eaip:Rea8Ealp:Sep9qg8Ep9rg8Fp9Abbbaradfgra8Fa8Ea8Epmlvorlvorlvorlvorp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmwDqkwDqkwDqkwDqkp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmxmPsxmPsxmPsxmPsp9rg8Fp9Abbbaradfgra8Faga8JpmwKDYq8AkEx3m5P8Es8Fgga8La8MpmwKDYq8AkEx3m5P8Es8Fg8JpmbezHdiOAlvCXorQLg8Eaip:Rea8Ealp:Sep9qg8Ep9rg8Fp9Abbbaradfgra8Fa8Ea8Epmlvorlvorlvorlvorp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmwDqkwDqkwDqkwDqkp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmxmPsxmPsxmPsxmPsp9rg8Fp9Abbbaradfgra8Faga8JpmwDKYqk8AExm35Ps8E8Fg8Eaip:Rea8Ealp:Sep9qg8Ep9rg8Fp9Abbbaradfgra8Fa8Ea8Epmlvorlvorlvorlvorp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmwDqkwDqkwDqkwDqkp9rg8Fp9Abbbaradfgra8Fa8Ea8EpmxmPsxmPsxmPsxmPsp9rg8Fp9AbbbaradfhraoczfgoaX6mbkkaYclfgYad6mbkaHavcjdfaAad2;8qbbavavcjdfaAcufad2fad;8qbbaAazfhzc9:hoaOhxaOmbxlkkaeTmbaDalfhrcbhocuhlinaralaD9RglfaD6mdaPaeao9RaoaPfae6Eaofgoae6mbkaial9Rhxkcbc99amax9RakSEhoxekc9:hokavcj;kbf8Kjjjjbaokwbz:bjjjbk:TseHu8Jjjjjbc;ae9Rgv8Kjjjjbc9:hodnaeci9UgrcHfal0mbcuhoaiRbbgwc;WeGc;Ge9hmbawcsGgDce0mbavc;abfcFecje;8kbavcUf9cu83ibavc8Wf9cu83ibavcyf9cu83ibavcaf9cu83ibavcKf9cu83ibavczf9cu83ibav9cu83iwav9cu83ibaialfc9WfhqaicefgwarfhldnaeTmbcmcsaDceSEhkcbhxcbhmcbhrcbhicbhoindnalaq9nmbc9:hoxikdndnawRbbgDc;Ve0mbavc;abfaoaDcu7gPcl4fcsGcitfgsydlhzasydbhHdndnaDcsGgsak9pmbavaiaPfcsGcdtfydbaxasEhDaxasTgOfhxxekdndnascsSmbcehOasc987asamffcefhDxekalcefhDal8SbbgscFeGhPdndnascu9mmbaDhlxekalcvfhlaPcFbGhPcrhsdninaD8SbbgOcFbGastaPVhPaOcu9kmeaDcefhDascrfgsc8J9hmbxdkkaDcefhlkcehOaPce4cbaPceG9R7amfhDkaDhmkavc;abfaocitfgsaDBdbasazBdlavaicdtfaDBdbavc;abfaocefcsGcitfgsaHBdbasaDBdlaocdfhoaOaifhidnadcd9hmbabarcetfgsaH87ebasclfaD87ebascdfaz87ebxdkabarcdtfgsaHBdbascwfaDBdbasclfazBdbxekdnaDcpe0mbaxcefgOavaiaqaDcsGfRbbgscl49RcsGcdtfydbascz6gPEhDavaias9RcsGcdtfydbaOaPfgzascsGgOEhsaOThOdndnadcd9hmbabarcetfgHax87ebaHclfas87ebaHcdfaD87ebxekabarcdtfgHaxBdbaHcwfasBdbaHclfaDBdbkavaicdtfaxBdbavc;abfaocitfgHaDBdbaHaxBdlavaicefgicsGcdtfaDBdbavc;abfaocefcsGcitfgHasBdbaHaDBdlavaiaPfgicsGcdtfasBdbavc;abfaocdfcsGcitfgDaxBdbaDasBdlaocifhoaiaOfhiazaOfhxxekaxcbalRbbgHEgAaDc;:eSgDfhzaHcsGhCaHcl4hXdndnaHcs0mbazcefhOxekazhOavaiaX9RcsGcdtfydbhzkdndnaCmbaOcefhxxekaOhxavaiaH9RcsGcdtfydbhOkdndnaDTmbalcefhDxekalcdfhDal8SbegPcFeGhsdnaPcu9kmbalcofhAascFbGhscrhldninaD8SbbgPcFbGaltasVhsaPcu9kmeaDcefhDalcrfglc8J9hmbkaAhDxekaDcefhDkasce4cbasceG9R7amfgmhAkdndnaXcsSmbaDhsxekaDcefhsaD8SbbglcFeGhPdnalcu9kmbaDcvfhzaPcFbGhPcrhldninas8SbbgDcFbGaltaPVhPaDcu9kmeascefhsalcrfglc8J9hmbkazhsxekascefhskaPce4cbaPceG9R7amfgmhzkdndnaCcsSmbashlxekascefhlas8SbbgDcFeGhPdnaDcu9kmbascvfhOaPcFbGhPcrhDdninal8SbbgscFbGaDtaPVhPascu9kmealcefhlaDcrfgDc8J9hmbkaOhlxekalcefhlkaPce4cbaPceG9R7amfgmhOkdndnadcd9hmbabarcetfgDaA87ebaDclfaO87ebaDcdfaz87ebxekabarcdtfgDaABdbaDcwfaOBdbaDclfazBdbkavc;abfaocitfgDazBdbaDaABdlavaicdtfaABdbavc;abfaocefcsGcitfgDaOBdbaDazBdlavaicefgicsGcdtfazBdbavc;abfaocdfcsGcitfgDaABdbaDaOBdlavaiaHcz6aXcsSVfgicsGcdtfaOBdbaiaCTaCcsSVfhiaocifhokawcefhwaocsGhoaicsGhiarcifgrae6mbkkcbc99alaqSEhokavc;aef8Kjjjjbaok:clevu8Jjjjjbcz9Rhvdnaecvfal9nmbc9:skdnaiRbbc;:eGc;qeSmbcuskav9cb83iwaicefhoaialfc98fhrdnaeTmbdnadcdSmbcbhwindnaoar6mbc9:skaocefhlao8SbbgicFeGhddndnaicu9mmbalhoxekaocvfhoadcFbGhdcrhidninal8SbbgDcFbGaitadVhdaDcu9kmealcefhlaicrfgic8J9hmbxdkkalcefhokabawcdtfadc8Etc8F91adcd47avcwfadceGcdtVglydbfgiBdbalaiBdbawcefgwae9hmbxdkkcbhwindnaoar6mbc9:skaocefhlao8SbbgicFeGhddndnaicu9mmbalhoxekaocvfhoadcFbGhdcrhidninal8SbbgDcFbGaitadVhdaDcu9kmealcefhlaicrfgic8J9hmbxdkkalcefhokabawcetfadc8Etc8F91adcd47avcwfadceGcdtVglydbfgi87ebalaiBdbawcefgwae9hmbkkcbc99aoarSEk:2Pliur97eue978Jjjjjbc8W9Rhiaec98Ghldndnadcl9hmbdnalTmbcbhvabhdinadadpbbbgocKp:RecKp:Sep;6egraocwp:RecKp:Sep;6earp;Geaoczp:RecKp:Sep;6egwp;Gep;Kep;LegDpxbbbbbbbbbbbbbbbbp:2egqarpxbbbjbbbjbbbjbbbjgkp9op9rp;Kegrpxbb;:9cbb;:9cbb;:9cbb;:9cararp;MeaDaDp;Meawaqawakp9op9rp;Kegrarp;Mep;Kep;Kep;Jep;Negwp;Mepxbbn0bbn0bbn0bbn0gqp;KepxFbbbFbbbFbbbFbbbp9oaopxbbbFbbbFbbbFbbbFp9op9qarawp;Meaqp;Kecwp:RepxbFbbbFbbbFbbbFbbp9op9qaDawp;Meaqp;Keczp:RepxbbFbbbFbbbFbbbFbp9op9qpkbbadczfhdavclfgval6mbkkalaeSmeaipxbbbbbbbbbbbbbbbbgqpklbaiabalcdtfgdaeciGglcdtgv;8qbbdnalTmbaiaipblbgocKp:RecKp:Sep;6egraocwp:RecKp:Sep;6earp;Geaoczp:RecKp:Sep;6egwp;Gep;Kep;LegDaqp:2egqarpxbbbjbbbjbbbjbbbjgkp9op9rp;Kegrpxbb;:9cbb;:9cbb;:9cbb;:9cararp;MeaDaDp;Meawaqawakp9op9rp;Kegrarp;Mep;Kep;Kep;Jep;Negwp;Mepxbbn0bbn0bbn0bbn0gqp;KepxFbbbFbbbFbbbFbbbp9oaopxbbbFbbbFbbbFbbbFp9op9qarawp;Meaqp;Kecwp:RepxbFbbbFbbbFbbbFbbp9op9qaDawp;Meaqp;Keczp:RepxbbFbbbFbbbFbbbFbp9op9qpklbkadaiav;8qbbskaipxFubbFubbFubbFubbgxpklbdnalTmbcbhvabhdinadczfgmampbbbgopxbbbbbbFFbbbbbbFFgkp9oadpbbbgDaopmbediwDqkzHOAKY8AEgwczp:Reczp:Sep;6egraipblbaDaopmlvorxmPsCXQL358E8Fp9op;6eawczp:Sep;6egwp;Gearp;Gep;Kep;Legopxbbbbbbbbbbbbbbbbp:2egqarpxbbbjbbbjbbbjbbbjgPp9op9rp;Kegrpxb;:FSb;:FSb;:FSb;:FSararp;Meaoaop;MeawaqawaPp9op9rp;Kegrarp;Mep;Kep;Kep;Jep;Negwp;Mepxbbn0bbn0bbn0bbn0gqp;KepxFFbbFFbbFFbbFFbbp9oaoawp;Meaqp;Keczp:Rep9qgoarawp;Meaqp;KepxFFbbFFbbFFbbFFbbp9ogrpmwDKYqk8AExm35Ps8E8Fp9qpkbbadaDakp9oaoarpmbezHdiOAlvCXorQLp9qpkbbadcafhdavclfgval6mbkkalaeSmbaiczfpxbbbbbbbbbbbbbbbbgopklbaiaopklbaiabalcitfgdaeciGglcitgv;8qbbaiaxpkladnalTmbaiaipblzgopxbbbbbbFFbbbbbbFFgkp9oaipblbgDaopmbediwDqkzHOAKY8AEgwczp:Reczp:Sep;6egraipblaaDaopmlvorxmPsCXQL358E8Fp9op;6eawczp:Sep;6egwp;Gearp;Gep;Kep;Legopxbbbbbbbbbbbbbbbbp:2egqarpxbbbjbbbjbbbjbbbjgPp9op9rp;Kegrpxb;:FSb;:FSb;:FSb;:FSararp;Meaoaop;MeawaqawaPp9op9rp;Kegrarp;Mep;Kep;Kep;Jep;Negwp;Mepxbbn0bbn0bbn0bbn0gqp;KepxFFbbFFbbFFbbFFbbp9oaoawp;Meaqp;Keczp:Rep9qgoarawp;Meaqp;KepxFFbbFFbbFFbbFFbbp9ogrpmwDKYqk8AExm35Ps8E8Fp9qpklzaiaDakp9oaoarpmbezHdiOAlvCXorQLp9qpklbkadaiav;8qbbkk:Iwllue97euo978Jjjjjbca9Rhidnaec98GglTmbcbhvabhoinaocKfpx:ji:1S:ji:1S:ji:1S:ji:1SaopbbbgraoczfgwpbbbgDpmlvorxmPsCXQL358E8Fgqczp:Segkpxibbbibbbibbbibbbp9qgxp;6ep;Negmaxaxp:1ep;7egxaxp;KearaDpmbediwDqkzHOAKY8AEgxczp:Reczp:Sep;6egrarp;Meaxczp:Sep;6egDaDp;Meaqczp:Reczp:Sep;6egqaqp;Mep;Kep;Kep;Lepxbbbbbbbbbbbbbbbbp:4ep;Jep;Mepxbbn0bbn0bbn0bbn0gxp;KepxFFbbFFbbFFbbFFbbgPp9oamaDp;Meaxp;Keczp:Rep9qgDamarp;Meaxp;KeaPp9oamaqp;Meaxp;Keczp:Rep9qgxpmwDKYqk8AExm35Ps8E8Fgrp5eakclp:RegmpEi:T:j83ibawarp5bampEd:T:j83ibaocwfaDaxpmbezHdiOAlvCXorQLgxp5eampEe:T:j83ibaoaxp5bampEb:T:j83ibaocafhoavclfgval6mbkkdnalaeSmbaiczfpxbbbbbbbbbbbbbbbbgmpklbaiampklbaiabalcitfgoaeciGgvcitgw;8qbbdnavTmbaipx:ji:1S:ji:1S:ji:1S:ji:1SaipblbgraipblzgDpmlvorxmPsCXQL358E8Fgqczp:Segkpxibbbibbbibbbibbbp9qgxp;6ep;Negmaxaxp:1ep;7egxaxp;KearaDpmbediwDqkzHOAKY8AEgxczp:Reczp:Sep;6egrarp;Meaxczp:Sep;6egDaDp;Meaqczp:Reczp:Sep;6egqaqp;Mep;Kep;Kep;Lepxbbbbbbbbbbbbbbbbp:4ep;Jep;Mepxbbn0bbn0bbn0bbn0gxp;KepxFFbbFFbbFFbbFFbbgPp9oamaDp;Meaxp;Keczp:Rep9qgDamarp;Meaxp;KeaPp9oamaqp;Meaxp;Keczp:Rep9qgxpmwDKYqk8AExm35Ps8E8Fgrp5eakclp:RegmpEi:T:j83iKaiarp5bampEd:T:j83izaiaDaxpmbezHdiOAlvCXorQLgxp5eampEe:T:j83iwaiaxp5bampEb:T:j83ibkaoaiaw;8qbbkk;uddiue978Jjjjjbc;ab9Rhidnadcd4ae2glc98GgvTmbcbheabhdinadadpbbbgocwp:Recwp:Sep;6eaocep:SepxbbjFbbjFbbjFbbjFp9opxbbjZbbjZbbjZbbjZp:Uep;Mepkbbadczfhdaeclfgeav6mbkkdnavalSmbaic8WfpxbbbbbbbbbbbbbbbbgopklbaicafaopklbaiczfaopklbaiaopklbaiabavcdtfgdalciGgecdtgv;8qbbdnaeTmbaiaipblbgocwp:Recwp:Sep;6eaocep:SepxbbjFbbjFbbjFbbjFp9opxbbjZbbjZbbjZbbjZp:Uep;Mepklbkadaiav;8qbbkk:CPvdue97euw97eu8Jjjjjbc8W9Rhiaec98Ghldndnadcl9hmbaipxbbbbbbbbbbbbbbbbgvpklbdnalTmbcbhoabhdinadpbbbhradpxbbuJbbuJbbuJbbuJaipblbarcKp:Tep9qgwcep:Seawp9qgDcdp:SeaDp9qgDclp:SeaDp9qgqp;6ep;NegDarcwp:RecKp:SegkarpxFbbbFbbbFbbbFbbbgxp9ogmp:Uep;6ep;Mepxbbn0bbn0bbn0bbn0gPp;Kecwp:RepxbFbbbFbbbFbbbFbbp9oaDamakp:Xearczp:RecKp:Segrp:Uep;6ep;MeaPp;Keaxp9op9qaDamakarp:Uep:Xep;6ep;MeaPp;Keczp:RepxbbFbbbFbbbFbbbFbp9op9qaDaqawcep:Rep9oawpxebbbebbbebbbebbbp9op9qp;6ep;MeaPp;KecKp:Rep9qpkbbadczfhdaoclfgoal6mbkkalaeSmeaiavpklaaicafabalcdtfgdaeciGglcdtgo;8qbbaiavpklbdnalTmbaipblahraipxbbuJbbuJbbuJbbuJaipblbarcKp:Tep9qgwcep:Seawp9qgDcdp:SeaDp9qgDclp:SeaDp9qgqp;6ep;NegDarcwp:RecKp:SegkarpxFbbbFbbbFbbbFbbbgxp9ogmp:Uep;6ep;Mepxbbn0bbn0bbn0bbn0gPp;Kecwp:RepxbFbbbFbbbFbbbFbbp9oaDamakp:Xearczp:RecKp:Segrp:Uep;6ep;MeaPp;Keaxp9op9qaDamakarp:Uep:Xep;6ep;MeaPp;Keczp:RepxbbFbbbFbbbFbbbFbp9op9qaDaqawcep:Rep9oawpxebbbebbbebbbebbbp9op9qp;6ep;MeaPp;KecKp:Rep9qpklakadaicafao;8qbbskaipxbbbbbbbbbbbbbbbbgvpklbdnalTmbcbhoabhdinadczfgspxbFu9hbFu9hbFu9hbFu9hadpbbbgDaspbbbgPpmlvorxmPsCXQL358E8Fgmczp:Teaipblbp9qgrcep:Searp9qgwcdp:Seawp9qgwclp:Seawp9qgwcwp:Seawp9qgqp;6ep;NegwaDaPpmbediwDqkzHOAKY8AEgDpxFFbbFFbbFFbbFFbbgPp9ogkaDczp:Segxp:Ueamczp:Reczp:Segmp:Xep;6ep;Mepxbbn0bbn0bbn0bbn0gDp;KeaPp9oawakaxamp:Uep:Xep;6ep;MeaDp;Keczp:Rep9qgxawaqarcep:Rep9oarpxebbbebbbebbbebbbp9op9qp;6ep;MeaDp;Keczp:Reawamakp:Uep;6ep;MeaDp;KeaPp9op9qgrpmwDKYqk8AExm35Ps8E8FpkbbadaxarpmbezHdiOAlvCXorQLpkbbadcafhdaoclfgoal6mbkkalaeSmbaiczfpxbbbbbbbbbbbbbbbbgrpklbaiarpklbaiabalcitfgdaeciGglcitgo;8qbbaiavpkladnalTmbaipxbFu9hbFu9hbFu9hbFu9haipblbgDaipblzgPpmlvorxmPsCXQL358E8Fgmczp:Teaipblap9qgrcep:Searp9qgwcdp:Seawp9qgwclp:Seawp9qgwcwp:Seawp9qgqp;6ep;NegwaDaPpmbediwDqkzHOAKY8AEgDpxFFbbFFbbFFbbFFbbgPp9ogkaDczp:Segxp:Ueamczp:Reczp:Segmp:Xep;6ep;Mepxbbn0bbn0bbn0bbn0gDp;KeaPp9oawakaxamp:Uep:Xep;6ep;MeaDp;Keczp:Rep9qgxawaqarcep:Rep9oarpxebbbebbbebbbebbbp9op9qp;6ep;MeaDp;Keczp:Reawamakp:Uep;6ep;MeaDp;KeaPp9op9qgrpmwDKYqk8AExm35Ps8E8FpklzaiaxarpmbezHdiOAlvCXorQLpklbkadaiao;8qbbkk9teiucbcbydj1jjbgeabcifc98GfgbBdj1jjbdndnabZbcztgd9nmbcuhiabad9RcFFifcz4nbcuSmekaehikaikkkebcjwklz:Dbb\"):r(\"b9H79Tebbbe8Fv9Gbb9Gvuuuuueu9Giuuub9Geueu9Giuuueuixkbeeeddddillviebeoweuec:W:Odkr;Neqo9TW9T9VV95dbH9F9F939H79T9F9J9H229F9Jt9VV7bb8A9TW79O9V9Wt9F9KW9J9V9KW9wWVtW949c919M9MWVbeY9TW79O9V9Wt9F9KW9J9V9KW69U9KW949c919M9MWVbdE9TW79O9V9Wt9F9KW9J9V9KW69U9KW949tWG91W9U9JWbiL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9p9JtblK9TW79O9V9Wt9F9KW9J9V9KWS9P2tWV9r919HtbvL9TW79O9V9Wt9F9KW9J9V9KWS9P2tWVT949WboY9TW79O9V9Wt9F9KW9J9V9KWS9P2tWVJ9V29VVbrl79IV9Rbwq;lZkdbk;jYi5ud9:du8Jjjjjbcj;kb9Rgv8Kjjjjbc9:hodnalTmbcuhoaiRbbgrc;WeGc:Ge9hmbarcsGgwce0mbc9:hoalcufadcd4cbawEgDadfgrcKcaawEgqaraq0Egk6mbaicefhxcj;abad9Uc;WFbGcjdadca0EhmaialfgPar9Rgoadfhsavaoadz:jjjjbgzceVhHcbhOdndninaeaO9nmeaPax9RaD6mdamaeaO9RaOamfgoae6EgAcsfglc9WGhCabaOad2fhXaAcethQaxaDfhiaOaeaoaeao6E9RhLalcl4cifcd4hKazcj;cbfaAfhYcbh8AazcjdfhEaHh3incbhodnawTmbaxa8Acd4fRbbhokaocFeGh5cbh8Eazcj;cbfhqinaih8Fdndndndna5a8Ecet4ciGgoc9:fPdebdkaPa8F9RaA6mrazcj;cbfa8EaA2fa8FaAz:jjjjb8Aa8FaAfhixdkazcj;cbfa8EaA2fcbaAz:kjjjb8Aa8FhixekaPa8F9RaK6mva8FaKfhidnaCTmbaPai9RcK6mbaocdtc:q1jjbfcj1jjbawEhaczhrcbhlinargoc9Wfghaqfhrdndndndndndnaaa8Fahco4fRbbalcoG4ciGcdtfydbPDbedvivvvlvkar9cb83bbarcwf9cb83bbxlkarcbaiRbdai8Xbb9c:c:qj:bw9:9c:q;c1:I1e:d9c:b:c:e1z9:gg9cjjjjjz:dg8J9qE86bbaqaofgrcGfag9c8F1:NghcKtc8F91aicdfa8J9c8N1:Nfg8KRbbG86bbarcVfcba8KahcjeGcr4fghRbbag9cjjjjjl:dg8J9qE86bbarc7fcbaha8J9c8L1:NfghRbbag9cjjjjjd:dg8J9qE86bbarctfcbaha8J9c8K1:NfghRbbag9cjjjjje:dg8J9qE86bbarc91fcbaha8J9c8J1:NfghRbbag9cjjjj;ab:dg8J9qE86bbarc4fcbaha8J9cg1:NfghRbbag9cjjjja:dg8J9qE86bbarc93fcbaha8J9ch1:NfghRbbag9cjjjjz:dgg9qE86bbarc94fcbahag9ca1:NfghRbbai8Xbe9c:c:qj:bw9:9c:q;c1:I1e:d9c:b:c:e1z9:gg9cjjjjjz:dg8J9qE86bbarc95fag9c8F1:NgicKtc8F91aha8J9c8N1:NfghRbbG86bbarc96fcbahaicjeGcr4fgiRbbag9cjjjjjl:dg8J9qE86bbarc97fcbaia8J9c8L1:NfgiRbbag9cjjjjjd:dg8J9qE86bbarc98fcbaia8J9c8K1:NfgiRbbag9cjjjjje:dg8J9qE86bbarc99fcbaia8J9c8J1:NfgiRbbag9cjjjj;ab:dg8J9qE86bbarc9:fcbaia8J9cg1:NfgiRbbag9cjjjja:dg8J9qE86bbarcufcbaia8J9ch1:NfgiRbbag9cjjjjz:dgg9qE86bbaiag9ca1:NfhixikaraiRblaiRbbghco4g8Ka8KciSg8KE86bbaqaofgrcGfaiclfa8Kfg8KRbbahcl4ciGg8La8LciSg8LE86bbarcVfa8Ka8Lfg8KRbbahcd4ciGg8La8LciSg8LE86bbarc7fa8Ka8Lfg8KRbbahciGghahciSghE86bbarctfa8Kahfg8KRbbaiRbeghco4g8La8LciSg8LE86bbarc91fa8Ka8Lfg8KRbbahcl4ciGg8La8LciSg8LE86bbarc4fa8Ka8Lfg8KRbbahcd4ciGg8La8LciSg8LE86bbarc93fa8Ka8Lfg8KRbbahciGghahciSghE86bbarc94fa8Kahfg8KRbbaiRbdghco4g8La8LciSg8LE86bbarc95fa8Ka8Lfg8KRbbahcl4ciGg8La8LciSg8LE86bbarc96fa8Ka8Lfg8KRbbahcd4ciGg8La8LciSg8LE86bbarc97fa8Ka8Lfg8KRbbahciGghahciSghE86bbarc98fa8KahfghRbbaiRbigico4g8Ka8KciSg8KE86bbarc99faha8KfghRbbaicl4ciGg8Ka8KciSg8KE86bbarc9:faha8KfghRbbaicd4ciGg8Ka8KciSg8KE86bbarcufaha8KfgrRbbaiciGgiaiciSgiE86bbaraifhixdkaraiRbwaiRbbghcl4g8Ka8KcsSg8KE86bbaqaofgrcGfaicwfa8Kfg8KRbbahcsGghahcsSghE86bbarcVfa8KahfghRbbaiRbeg8Kcl4g8La8LcsSg8LE86bbarc7faha8LfghRbba8KcsGg8Ka8KcsSg8KE86bbarctfaha8KfghRbbaiRbdg8Kcl4g8La8LcsSg8LE86bbarc91faha8LfghRbba8KcsGg8Ka8KcsSg8KE86bbarc4faha8KfghRbbaiRbig8Kcl4g8La8LcsSg8LE86bbarc93faha8LfghRbba8KcsGg8Ka8KcsSg8KE86bbarc94faha8KfghRbbaiRblg8Kcl4g8La8LcsSg8LE86bbarc95faha8LfghRbba8KcsGg8Ka8KcsSg8KE86bbarc96faha8KfghRbbaiRbvg8Kcl4g8La8LcsSg8LE86bbarc97faha8LfghRbba8KcsGg8Ka8KcsSg8KE86bbarc98faha8KfghRbbaiRbog8Kcl4g8La8LcsSg8LE86bbarc99faha8LfghRbba8KcsGg8Ka8KcsSg8KE86bbarc9:faha8KfghRbbaiRbrgicl4g8Ka8KcsSg8KE86bbarcufaha8KfgrRbbaicsGgiaicsSgiE86bbaraifhixekarai8Pbb83bbarcwfaicwf8Pbb83bbaiczfhikdnaoaC9pmbalcdfhlaoczfhraPai9RcL0mekkaoaC6moaimexokaCmva8FTmvkaqaAfhqa8Ecefg8Ecl9hmbkdndndndnawTmbasa8Acd4fRbbgociGPlbedrbkaATmdaza8Afh8Fazcj;cbfhhcbh8EaEhaina8FRbbhraahocbhlinaoahalfRbbgqce4cbaqceG9R7arfgr86bbaoadfhoaAalcefgl9hmbkaacefhaa8Fcefh8FahaAfhha8Ecefg8Ecl9hmbxikkaATmeaza8Afhaazcj;cbfhhcbhoceh8EaYh8FinaEaofhlaa8Vbbhrcbhoinala8FaofRbbcwtahaofRbbgqVc;:FiGce4cbaqceG9R7arfgr87bbaladfhlaLaocefgofmbka8FaQfh8FcdhoaacdfhaahaQfhha8EceGhlcbh8EalmbxdkkaATmbcbaocl49Rh8Eaza8AfRbbhqcwhoa3hlinalRbbaotaqVhqalcefhlaocwfgoca9hmbkcbhhaEh8FaYhainazcj;cbfahfRbbhrcwhoaahlinalRbbaotarVhralaAfhlaocwfgoca9hmbkara8E93aq7hqcbhoa8Fhlinalaqao486bbalcefhlaocwfgoca9hmbka8Fadfh8FaacefhaahcefghaA9hmbkkaEclfhEa3clfh3a8Aclfg8Aad6mbkaXazcjdfaAad2z:jjjjb8AazazcjdfaAcufad2fadz:jjjjb8AaAaOfhOaihxaimbkc9:hoxdkcbc99aPax9RakSEhoxekc9:hokavcj;kbf8Kjjjjbaok:XseHu8Jjjjjbc;ae9Rgv8Kjjjjbc9:hodnaeci9UgrcHfal0mbcuhoaiRbbgwc;WeGc;Ge9hmbawcsGgDce0mbavc;abfcFecjez:kjjjb8AavcUf9cu83ibavc8Wf9cu83ibavcyf9cu83ibavcaf9cu83ibavcKf9cu83ibavczf9cu83ibav9cu83iwav9cu83ibaialfc9WfhqaicefgwarfhldnaeTmbcmcsaDceSEhkcbhxcbhmcbhrcbhicbhoindnalaq9nmbc9:hoxikdndnawRbbgDc;Ve0mbavc;abfaoaDcu7gPcl4fcsGcitfgsydlhzasydbhHdndnaDcsGgsak9pmbavaiaPfcsGcdtfydbaxasEhDaxasTgOfhxxekdndnascsSmbcehOasc987asamffcefhDxekalcefhDal8SbbgscFeGhPdndnascu9mmbaDhlxekalcvfhlaPcFbGhPcrhsdninaD8SbbgOcFbGastaPVhPaOcu9kmeaDcefhDascrfgsc8J9hmbxdkkaDcefhlkcehOaPce4cbaPceG9R7amfhDkaDhmkavc;abfaocitfgsaDBdbasazBdlavaicdtfaDBdbavc;abfaocefcsGcitfgsaHBdbasaDBdlaocdfhoaOaifhidnadcd9hmbabarcetfgsaH87ebasclfaD87ebascdfaz87ebxdkabarcdtfgsaHBdbascwfaDBdbasclfazBdbxekdnaDcpe0mbaxcefgOavaiaqaDcsGfRbbgscl49RcsGcdtfydbascz6gPEhDavaias9RcsGcdtfydbaOaPfgzascsGgOEhsaOThOdndnadcd9hmbabarcetfgHax87ebaHclfas87ebaHcdfaD87ebxekabarcdtfgHaxBdbaHcwfasBdbaHclfaDBdbkavaicdtfaxBdbavc;abfaocitfgHaDBdbaHaxBdlavaicefgicsGcdtfaDBdbavc;abfaocefcsGcitfgHasBdbaHaDBdlavaiaPfgicsGcdtfasBdbavc;abfaocdfcsGcitfgDaxBdbaDasBdlaocifhoaiaOfhiazaOfhxxekaxcbalRbbgHEgAaDc;:eSgDfhzaHcsGhCaHcl4hXdndnaHcs0mbazcefhOxekazhOavaiaX9RcsGcdtfydbhzkdndnaCmbaOcefhxxekaOhxavaiaH9RcsGcdtfydbhOkdndnaDTmbalcefhDxekalcdfhDal8SbegPcFeGhsdnaPcu9kmbalcofhAascFbGhscrhldninaD8SbbgPcFbGaltasVhsaPcu9kmeaDcefhDalcrfglc8J9hmbkaAhDxekaDcefhDkasce4cbasceG9R7amfgmhAkdndnaXcsSmbaDhsxekaDcefhsaD8SbbglcFeGhPdnalcu9kmbaDcvfhzaPcFbGhPcrhldninas8SbbgDcFbGaltaPVhPaDcu9kmeascefhsalcrfglc8J9hmbkazhsxekascefhskaPce4cbaPceG9R7amfgmhzkdndnaCcsSmbashlxekascefhlas8SbbgDcFeGhPdnaDcu9kmbascvfhOaPcFbGhPcrhDdninal8SbbgscFbGaDtaPVhPascu9kmealcefhlaDcrfgDc8J9hmbkaOhlxekalcefhlkaPce4cbaPceG9R7amfgmhOkdndnadcd9hmbabarcetfgDaA87ebaDclfaO87ebaDcdfaz87ebxekabarcdtfgDaABdbaDcwfaOBdbaDclfazBdbkavc;abfaocitfgDazBdbaDaABdlavaicdtfaABdbavc;abfaocefcsGcitfgDaOBdbaDazBdlavaicefgicsGcdtfazBdbavc;abfaocdfcsGcitfgDaABdbaDaOBdlavaiaHcz6aXcsSVfgicsGcdtfaOBdbaiaCTaCcsSVfhiaocifhokawcefhwaocsGhoaicsGhiarcifgrae6mbkkcbc99alaqSEhokavc;aef8Kjjjjbaok:clevu8Jjjjjbcz9Rhvdnaecvfal9nmbc9:skdnaiRbbc;:eGc;qeSmbcuskav9cb83iwaicefhoaialfc98fhrdnaeTmbdnadcdSmbcbhwindnaoar6mbc9:skaocefhlao8SbbgicFeGhddndnaicu9mmbalhoxekaocvfhoadcFbGhdcrhidninal8SbbgDcFbGaitadVhdaDcu9kmealcefhlaicrfgic8J9hmbxdkkalcefhokabawcdtfadc8Etc8F91adcd47avcwfadceGcdtVglydbfgiBdbalaiBdbawcefgwae9hmbxdkkcbhwindnaoar6mbc9:skaocefhlao8SbbgicFeGhddndnaicu9mmbalhoxekaocvfhoadcFbGhdcrhidninal8SbbgDcFbGaitadVhdaDcu9kmealcefhlaicrfgic8J9hmbxdkkalcefhokabawcetfadc8Etc8F91adcd47avcwfadceGcdtVglydbfgi87ebalaiBdbawcefgwae9hmbkkcbc99aoarSEk:Lvoeue99dud99eud99dndnadcl9hmbaeTmeindndnabcdfgd8Sbb:Yab8Sbbgi:Ygl:l:tabcefgv8Sbbgo:Ygr:l:tgwJbb;:9cawawNJbbbbawawJbbbb9GgDEgq:mgkaqaicb9iEalMgwawNakaqaocb9iEarMgqaqNMM:r:vglNJbbbZJbbb:;aDEMgr:lJbbb9p9DTmbar:Ohixekcjjjj94hikadai86bbdndnaqalNJbbbZJbbb:;aqJbbbb9GEMgq:lJbbb9p9DTmbaq:Ohdxekcjjjj94hdkavad86bbdndnawalNJbbbZJbbb:;awJbbbb9GEMgw:lJbbb9p9DTmbaw:Ohdxekcjjjj94hdkabad86bbabclfhbaecufgembxdkkaeTmbindndnabclfgd8Ueb:Yab8Uebgi:Ygl:l:tabcdfgv8Uebgo:Ygr:l:tgwJb;:FSawawNJbbbbawawJbbbb9GgDEgq:mgkaqaicb9iEalMgwawNakaqaocb9iEarMgqaqNMM:r:vglNJbbbZJbbb:;aDEMgr:lJbbb9p9DTmbar:Ohixekcjjjj94hikadai87ebdndnaqalNJbbbZJbbb:;aqJbbbb9GEMgq:lJbbb9p9DTmbaq:Ohdxekcjjjj94hdkavad87ebdndnawalNJbbbZJbbb:;awJbbbb9GEMgw:lJbbb9p9DTmbaw:Ohdxekcjjjj94hdkabad87ebabcwfhbaecufgembkkk::ioiue99dud99dud99dnaeTmbcbhiabhlindndnal8Uebgv:YgoJ:ji:1Salcof8UebgrciVgw:Y:vgDNJbbbZJbbb:;avcu9kEMgq:lJbbb9p9DTmbaq:Ohkxekcjjjj94hkkalclf8Uebhvalcdf8UebhxabaiarcefciGfcetfak87ebdndnax:YgqaDNJbbbZJbbb:;axcu9kEMgm:lJbbb9p9DTmbam:Ohxxekcjjjj94hxkabaiarciGfgkcd7cetfax87ebdndnav:YgmaDNJbbbZJbbb:;avcu9kEMgP:lJbbb9p9DTmbaP:Ohvxekcjjjj94hvkabaiarcufciGfcetfav87ebdndnawaw2:ZgPaPMaoaoN:taqaqN:tamamN:tgoJbbbbaoJbbbb9GE:raDNJbbbZMgD:lJbbb9p9DTmbaD:Ohrxekcjjjj94hrkabakcetfar87ebalcwfhlaiclfhiaecufgembkkk9mbdnadcd4ae2gdTmbinababydbgecwtcw91:Yaece91cjjj98Gcjjj;8if::NUdbabclfhbadcufgdmbkkk:Tvirud99eudndnadcl9hmbaeTmeindndnabRbbgiabcefgl8Sbbgvabcdfgo8Sbbgrf9R:YJbbuJabcifgwRbbgdce4adVgDcd4aDVgDcl4aDVgD:Z:vgqNJbbbZMgk:lJbbb9p9DTmbak:Ohxxekcjjjj94hxkaoax86bbdndnaraif:YaqNJbbbZMgk:lJbbb9p9DTmbak:Ohoxekcjjjj94hokalao86bbdndnavaifar9R:YaqNJbbbZMgk:lJbbb9p9DTmbak:Ohixekcjjjj94hikabai86bbdndnaDadcetGadceGV:ZaqNJbbbZMgq:lJbbb9p9DTmbaq:Ohdxekcjjjj94hdkawad86bbabclfhbaecufgembxdkkaeTmbindndnab8Vebgiabcdfgl8Uebgvabclfgo8Uebgrf9R:YJbFu9habcofgw8Vebgdce4adVgDcd4aDVgDcl4aDVgDcw4aDVgD:Z:vgqNJbbbZMgk:lJbbb9p9DTmbak:Ohxxekcjjjj94hxkaoax87ebdndnaraif:YaqNJbbbZMgk:lJbbb9p9DTmbak:Ohoxekcjjjj94hokalao87ebdndnavaifar9R:YaqNJbbbZMgk:lJbbb9p9DTmbak:Ohixekcjjjj94hikabai87ebdndnaDadcetGadceGV:ZaqNJbbbZMgq:lJbbb9p9DTmbaq:Ohdxekcjjjj94hdkawad87ebabcwfhbaecufgembkkk9teiucbcbyd:K1jjbgeabcifc98GfgbBd:K1jjbdndnabZbcztgd9nmbcuhiabad9RcFFifcz4nbcuSmekaehikaik;teeeudndnaeabVciGTmbabhixekdndnadcz9pmbabhixekabhiinaiaeydbBdbaiaeydlBdlaiaeydwBdwaiaeydxBdxaeczfheaiczfhiadc9Wfgdcs0mbkkadcl6mbinaiaeydbBdbaeclfheaiclfhiadc98fgdci0mbkkdnadTmbinaiaeRbb86bbaicefhiaecefheadcufgdmbkkabk:3eedudndnabciGTmbabhixekaecFeGc:b:c:ew2hldndnadcz9pmbabhixekabhiinaialBdxaialBdwaialBdlaialBdbaiczfhiadc9Wfgdcs0mbkkadcl6mbinaialBdbaiclfhiadc98fgdci0mbkkdnadTmbinaiae86bbaicefhiadcufgdmbkkabkk81dbcjwk8Kbbbbdbbblbbbwbbbbbbbebbbdbbblbbbwbbbbc:Kwkl8WNbb\"),n=WebAssembly.instantiate(i,{}).then(function(p){(t=p.instance).exports.__wasm_call_ctors()});function r(p){for(var A=new Uint8Array(p.length),g=0;g96?b-97:b>64?b-39:b+4}var m=0;for(g=0;g0?(function(f,y,v,x,_){for(var w=c[0],B=1;B{let l=o.draco,c=new l.Decoder;try{let h=(function(d,p,A,g){let b=g.attributeIDs,m=g.attributeTypes,f,y,v=p.GetEncodedGeometryType(A);if(v===d.TRIANGULAR_MESH)f=new d.Mesh,y=p.DecodeArrayToMesh(A,A.byteLength,f);else{if(v!==d.POINT_CLOUD)throw new Error(\"THREE.DRACOLoader: Unexpected geometry type.\");f=new d.PointCloud,y=p.DecodeArrayToPointCloud(A,A.byteLength,f)}if(!y.ok()||f.ptr===0)throw new Error(\"THREE.DRACOLoader: Decoding failed: \"+y.error_msg());let x={index:null,attributes:[]};for(let _ in b){let w=self[m[_]],B,I;if(g.useUniqueIDs)I=b[_],B=p.GetAttributeByUniqueId(f,I);else{if(I=p.GetAttributeId(f,d[b[_]]),I===-1)continue;B=p.GetAttribute(f,I)}let F=t(d,p,f,_,w,B);_===\"color\"&&(F.vertexColorSpace=g.vertexColorSpace),x.attributes.push(F)}return v===d.TRIANGULAR_MESH&&(x.index=(function(_,w,B){let I=B.num_faces(),F=3*I,Q=4*F,R=_._malloc(Q);w.GetTrianglesUInt32Array(B,Q,R);let U=new Uint32Array(_.HEAPF32.buffer,R,F).slice();return _._free(R),{array:U,itemSize:1}})(d,p,f)),d.destroy(f),x})(l,c,new Int8Array(r),a),u=h.attributes.map(d=>d.array.buffer);h.index&&u.push(h.index.array.buffer),self.postMessage({type:\"decode\",id:n.id,geometry:h},u)}catch(h){console.error(h),self.postMessage({type:\"error\",id:n.id,error:h.message})}finally{l.destroy(c)}})}}}function Zf(s,e){if(e===0)return console.warn(\"THREE.BufferGeometryUtils.toTrianglesDrawMode(): Geometry already defined as triangles.\"),s;if(e===2||e===1){let t=s.getIndex();if(t===null){let a=[],o=s.getAttribute(\"position\");if(o===void 0)return console.error(\"THREE.BufferGeometryUtils.toTrianglesDrawMode(): Undefined position attribute. Processing not possible.\"),s;for(let l=0;l=2.0 are supported.\")));let c=new RA(r,{path:t||this.resourcePath||\"\",crossOrigin:this.crossOrigin,requestHeader:this.requestHeader,manager:this.manager,ktx2Loader:this.ktx2Loader,meshoptDecoder:this.meshoptDecoder});c.fileLoader.setRequestHeader(this.requestHeader);for(let h=0;h=0&&o[u]===void 0&&console.warn('THREE.GLTFLoader: Unknown extension \"'+u+'\".')}}c.setExtensions(a),c.setPlugins(o),c.parse(i,n)}parseAsync(e,t){let i=this;return new Promise(function(n,r){i.parse(e,t,n,r)})}};function MS(){let s={};return{get:function(e){return s[e]},add:function(e,t){s[e]=t},remove:function(e){delete s[e]},removeAll:function(){s={}}}}function Rt(s,e,t){let i=s.json.materials[e];return i.extensions&&i.extensions[t]?i.extensions[t]:null}var He={KHR_BINARY_GLTF:\"KHR_binary_glTF\",KHR_DRACO_MESH_COMPRESSION:\"KHR_draco_mesh_compression\",KHR_LIGHTS_PUNCTUAL:\"KHR_lights_punctual\",KHR_MATERIALS_CLEARCOAT:\"KHR_materials_clearcoat\",KHR_MATERIALS_DISPERSION:\"KHR_materials_dispersion\",KHR_MATERIALS_IOR:\"KHR_materials_ior\",KHR_MATERIALS_SHEEN:\"KHR_materials_sheen\",KHR_MATERIALS_SPECULAR:\"KHR_materials_specular\",KHR_MATERIALS_TRANSMISSION:\"KHR_materials_transmission\",KHR_MATERIALS_IRIDESCENCE:\"KHR_materials_iridescence\",KHR_MATERIALS_ANISOTROPY:\"KHR_materials_anisotropy\",KHR_MATERIALS_UNLIT:\"KHR_materials_unlit\",KHR_MATERIALS_VOLUME:\"KHR_materials_volume\",KHR_TEXTURE_BASISU:\"KHR_texture_basisu\",KHR_TEXTURE_TRANSFORM:\"KHR_texture_transform\",KHR_MESH_QUANTIZATION:\"KHR_mesh_quantization\",KHR_MATERIALS_EMISSIVE_STRENGTH:\"KHR_materials_emissive_strength\",EXT_MATERIALS_BUMP:\"EXT_materials_bump\",EXT_TEXTURE_WEBP:\"EXT_texture_webp\",EXT_TEXTURE_AVIF:\"EXT_texture_avif\",EXT_MESHOPT_COMPRESSION:\"EXT_meshopt_compression\",KHR_MESHOPT_COMPRESSION:\"KHR_meshopt_compression\",EXT_MESH_GPU_INSTANCING:\"EXT_mesh_gpu_instancing\"},yA=class{constructor(e){this.parser=e,this.name=He.KHR_LIGHTS_PUNCTUAL,this.cache={refs:{},uses:{}}}_markDefs(){let e=this.parser,t=this.parser.json.nodes||[];for(let i=0,n=t.length;i=0)throw new Error(\"THREE.GLTFLoader: setKTX2Loader must be called before loading KTX2 textures\");return null}return t.loadTextureImage(e,r.source,a)}},_A=class{constructor(e){this.parser=e,this.name=He.EXT_TEXTURE_WEBP}loadTexture(e){let t=this.name,i=this.parser,n=i.json,r=n.textures[e];if(!r.extensions||!r.extensions[t])return null;let a=r.extensions[t],o=n.images[a.source],l=i.textureLoader;if(o.uri){let c=i.options.manager.getHandler(o.uri);c!==null&&(l=c)}return i.loadTextureImage(e,a.source,l)}},EA=class{constructor(e){this.parser=e,this.name=He.EXT_TEXTURE_AVIF}loadTexture(e){let t=this.name,i=this.parser,n=i.json,r=n.textures[e];if(!r.extensions||!r.extensions[t])return null;let a=r.extensions[t],o=n.images[a.source],l=i.textureLoader;if(o.uri){let c=i.options.manager.getHandler(o.uri);c!==null&&(l=c)}return i.loadTextureImage(e,a.source,l)}},gh=class{constructor(e,t){this.name=t,this.parser=e}loadBufferView(e){let t=this.parser.json,i=t.bufferViews[e];if(i.extensions&&i.extensions[this.name]){let n=i.extensions[this.name],r=this.parser.getDependency(\"buffer\",n.buffer),a=this.parser.options.meshoptDecoder;if(!a||!a.supported){if(t.extensionsRequired&&t.extensionsRequired.indexOf(this.name)>=0)throw new Error(\"THREE.GLTFLoader: setMeshoptDecoder must be called before loading compressed files\");return null}return r.then(function(o){let l=n.byteOffset||0,c=n.byteLength||0,h=n.count,u=n.byteStride,d=new Uint8Array(o,l,c);return a.decodeGltfBufferAsync?a.decodeGltfBufferAsync(h,u,d,n.mode,n.filter).then(function(p){return p.buffer}):a.ready.then(function(){let p=new ArrayBuffer(h*u);return a.decodeGltfBuffer(new Uint8Array(p),h,u,d,n.mode,n.filter),p})})}return null}},QS=class{constructor(s){this.name=He.EXT_MESH_GPU_INSTANCING,this.parser=s}createNodeMesh(s){let e=this.parser.json,t=e.nodes[s];if(!t.extensions||!t.extensions[this.name]||t.mesh===void 0)return null;let i=e.meshes[t.mesh];for(let o of i.primitives)if(o.mode!==Si.TRIANGLES&&o.mode!==Si.TRIANGLE_STRIP&&o.mode!==Si.TRIANGLE_FAN&&o.mode!==void 0)return null;let n=t.extensions[this.name].attributes,r=[],a={};for(let o in n)r.push(this.parser.getDependency(\"accessor\",n[o]).then(l=>(a[o]=l,a[o])));return r.length<1?null:(r.push(this.parser.createNodeMesh(s)),Promise.all(r).then(o=>{let l=o.pop(),c=l.isGroup?l.children:[l],h=o[0].count,u=[];for(let d of c){let p=new Re,A=new T,g=new Kt,b=new T(1,1,1),m=new Ip(d.geometry,d.material,h);for(let f=0;f-1,a=r?o.match(/Firefox\\/([0-9]+)\\./)[1]:-1}typeof createImageBitmap>\"u\"||i&&n<17||r&&a<98?this.textureLoader=new oh(this.options.manager):this.textureLoader=new sA(this.options.manager),this.textureLoader.setCrossOrigin(this.options.crossOrigin),this.textureLoader.setRequestHeader(this.options.requestHeader),this.fileLoader=new Hi(this.options.manager),this.fileLoader.setResponseType(\"arraybuffer\"),this.options.crossOrigin===\"use-credentials\"&&this.fileLoader.setWithCredentials(!0)}setExtensions(e){this.extensions=e}setPlugins(e){this.plugins=e}parse(e,t){let i=this,n=this.json,r=this.extensions;this.cache.removeAll(),this.nodeCache={},this._invokeAll(function(a){return a._markDefs&&a._markDefs()}),Promise.all(this._invokeAll(function(a){return a.beforeRoot&&a.beforeRoot()})).then(function(){return Promise.all([i.getDependencies(\"scene\"),i.getDependencies(\"animation\"),i.getDependencies(\"camera\")])}).then(function(a){let o={scene:a[0][n.scene||0],scenes:a[0],animations:a[1],cameras:a[2],asset:n.asset,parser:i,userData:{}};return Es(r,o,n),Ji(o,n),Promise.all(i._invokeAll(function(l){return l.afterRoot&&l.afterRoot(o)})).then(function(){for(let l of o.scenes)l.updateMatrixWorld();e(o)})}).catch(t)}_markDefs(){let e=this.json.nodes||[],t=this.json.skins||[],i=this.json.meshes||[];for(let n=0,r=t.length;n{let l=this.associations.get(a);l!=null&&this.associations.set(o,l);for(let[c,h]of a.children.entries())r(h,o.children[c])};return r(i,n),n.name+=\"_instance_\"+e.uses[t]++,n}_invokeOne(e){let t=Object.values(this.plugins);t.push(this);for(let i=0;i=2&&b.setY(I,_[w*l+1]),l>=3&&b.setZ(I,_[w*l+2]),l>=4&&b.setW(I,_[w*l+3]),l>=5)throw new Error(\"THREE.GLTFLoader: Unsupported itemSize in sparse BufferAttribute.\")}b.normalized=A}return b})}loadTexture(e){let t=this.json,i=this.options,n=t.textures[e].source,r=t.images[n],a=this.textureLoader;if(r.uri){let o=i.manager.getHandler(r.uri);o!==null&&(a=o)}return this.loadTextureImage(e,n,a)}loadTextureImage(e,t,i){let n=this,r=this.json,a=r.textures[e],o=r.images[t],l=(o.uri||o.bufferView)+\":\"+a.sampler;if(this.textureCache[l])return this.textureCache[l];let c=this.loadImageSource(t,i).then(function(h){h.flipY=!1,h.name=a.name||o.name||\"\",h.name===\"\"&&typeof o.uri==\"string\"&&o.uri.startsWith(\"data:image/\")===!1&&(h.name=o.uri);let u=(r.samplers||{})[a.sampler]||{};return h.magFilter=e0[u.magFilter]||Ve,h.minFilter=e0[u.minFilter]||mi,h.wrapS=t0[u.wrapS]||sn,h.wrapT=t0[u.wrapT]||sn,h.generateMipmaps=!h.isCompressedTexture&&h.minFilter!==kt&&h.minFilter!==Ve,n.associations.set(h,{textures:e}),h}).catch(function(){return null});return this.textureCache[l]=c,c}loadImageSource(e,t){let i=this,n=this.json,r=this.options;if(this.sourceCache[e]!==void 0)return this.sourceCache[e].then(u=>u.clone());let a=n.images[e],o=self.URL||self.webkitURL,l=a.uri||\"\",c=!1;if(a.bufferView!==void 0)l=i.getDependency(\"bufferView\",a.bufferView).then(function(u){c=!0;let d=new Blob([u],{type:a.mimeType});return l=o.createObjectURL(d),l});else if(a.uri===void 0)throw new Error(\"THREE.GLTFLoader: Image \"+e+\" is missing URI and bufferView\");let h=Promise.resolve(l).then(function(u){return new Promise(function(d,p){let A=d;t.isImageBitmapLoader===!0&&(A=function(g){let b=new xt(g);b.needsUpdate=!0,d(b)}),t.load(Ws.resolveURL(u,r.path),A,void 0,p)})}).then(function(u){var d;return c===!0&&o.revokeObjectURL(l),Ji(u,a),u.userData.mimeType=a.mimeType||((d=a.uri).search(/\\.jpe?g($|\\?)/i)>0||d.search(/^data\\:image\\/jpeg/)===0?\"image/jpeg\":d.search(/\\.webp($|\\?)/i)>0||d.search(/^data\\:image\\/webp/)===0?\"image/webp\":d.search(/\\.ktx2($|\\?)/i)>0||d.search(/^data\\:image\\/ktx2/)===0?\"image/ktx2\":\"image/png\"),u}).catch(function(u){throw console.error(\"THREE.GLTFLoader: Couldn't load texture\",l),u});return this.sourceCache[e]=h,h}assignTexture(e,t,i,n){let r=this;return this.getDependency(\"texture\",i.index).then(function(a){if(!a)return null;if(i.texCoord!==void 0&&i.texCoord>0&&((a=a.clone()).channel=i.texCoord),r.extensions[He.KHR_TEXTURE_TRANSFORM]){let o=i.extensions!==void 0?i.extensions[He.KHR_TEXTURE_TRANSFORM]:void 0;if(o){let l=r.associations.get(a);a=r.extensions[He.KHR_TEXTURE_TRANSFORM].extendTexture(a,o),r.associations.set(a,l)}}return n!==void 0&&(a.colorSpace=n),e[t]=a,a})}assignFinalMaterial(e){let t=e.geometry,i=e.material,n=t.attributes.tangent===void 0,r=t.attributes.color!==void 0,a=t.attributes.normal===void 0;if(e.isPoints){let o=\"PointsMaterial:\"+i.uuid,l=this.cache.get(o);l||(l=new Xc,zi.prototype.copy.call(l,i),l.color.copy(i.color),l.map=i.map,l.sizeAttenuation=!1,this.cache.add(o,l)),i=l}else if(e.isLine){let o=\"LineBasicMaterial:\"+i.uuid,l=this.cache.get(o);l||(l=new qc,zi.prototype.copy.call(l,i),l.color.copy(i.color),l.map=i.map,this.cache.add(o,l)),i=l}if(n||r||a){let o=\"ClonedMaterial:\"+i.uuid+\":\";n&&(o+=\"derivative-tangents:\"),r&&(o+=\"vertex-colors:\"),a&&(o+=\"flat-shading:\");let l=this.cache.get(o);l||(l=i.clone(),r&&(l.vertexColors=!0),a&&(l.flatShading=!0),n&&(l.normalScale&&(l.normalScale.y*=-1),l.clearcoatNormalScale&&(l.clearcoatNormalScale.y*=-1)),this.cache.add(o,l),this.associations.set(l,this.associations.get(i))),i=l}e.material=i}getMaterialType(){return ir}loadMaterial(e){let t=this,i=this.json,n=this.extensions,r=i.materials[e],a,o={},l=[];if((r.extensions||{})[He.KHR_MATERIALS_UNLIT]){let h=n[He.KHR_MATERIALS_UNLIT];a=h.getMaterialType(),l.push(h.extendParams(o,r,t))}else{let h=r.pbrMetallicRoughness||{};if(o.color=new ve(1,1,1),o.opacity=1,Array.isArray(h.baseColorFactor)){let u=h.baseColorFactor;o.color.setRGB(u[0],u[1],u[2],At),o.opacity=u[3]}h.baseColorTexture!==void 0&&l.push(t.assignTexture(o,\"map\",h.baseColorTexture,lt)),o.metalness=h.metallicFactor!==void 0?h.metallicFactor:1,o.roughness=h.roughnessFactor!==void 0?h.roughnessFactor:1,h.metallicRoughnessTexture!==void 0&&(l.push(t.assignTexture(o,\"metalnessMap\",h.metallicRoughnessTexture)),l.push(t.assignTexture(o,\"roughnessMap\",h.metallicRoughnessTexture))),a=this._invokeOne(function(u){return u.getMaterialType&&u.getMaterialType(e)}),l.push(Promise.all(this._invokeAll(function(u){return u.extendMaterialParams&&u.extendMaterialParams(e,o)})))}r.doubleSided===!0&&(o.side=2);let c=r.alphaMode||qS;if(c===WS?(o.transparent=!0,o.depthWrite=!1):(o.transparent=!1,c===jS&&(o.alphaTest=r.alphaCutoff!==void 0?r.alphaCutoff:.5)),r.normalTexture!==void 0&&a!==Xt&&(l.push(t.assignTexture(o,\"normalMap\",r.normalTexture)),o.normalScale=new pe(1,1),r.normalTexture.scale!==void 0)){let h=r.normalTexture.scale;o.normalScale.set(h,h)}if(r.occlusionTexture!==void 0&&a!==Xt&&(l.push(t.assignTexture(o,\"aoMap\",r.occlusionTexture)),r.occlusionTexture.strength!==void 0&&(o.aoMapIntensity=r.occlusionTexture.strength)),r.emissiveFactor!==void 0&&a!==Xt){let h=r.emissiveFactor;o.emissive=new ve().setRGB(h[0],h[1],h[2],At)}return r.emissiveTexture!==void 0&&a!==Xt&&l.push(t.assignTexture(o,\"emissiveMap\",r.emissiveTexture,lt)),Promise.all(l).then(function(){let h=new a(o);return r.name&&(h.name=r.name),Ji(h,r),t.associations.set(h,{materials:e}),r.extensions&&Es(n,h,r),h})}createUniqueName(e){let t=it.sanitizeNodeName(e||\"\");return t in this.nodeNamesUsed?t+\"_\"+ ++this.nodeNamesUsed[t]:(this.nodeNamesUsed[t]=0,t)}loadGeometries(e){let t=this,i=this.extensions,n=this.primitiveCache;function r(o){return i[He.KHR_DRACO_MESH_COMPRESSION].decodePrimitive(o,t).then(function(l){return i0(l,o,t)})}let a=[];for(let o=0,l=e.length;o0&&KS(m,r),m.name=t.createUniqueName(r.name||\"mesh_\"+e),Ji(m,r),b.extensions&&Es(n,m,b),t.assignFinalMaterial(m),u.push(m)}for(let p=0,A=u.length;p0){let d=c.userData.pivot,p=h[0];c.pivot=new T().fromArray(d),c.position.x-=d[0],c.position.y-=d[1],c.position.z-=d[2],p.position.set(0,0,0),delete c.userData.pivot}return c})}_loadNodeShallow(e){let t=this.json,i=this.extensions,n=this;if(this.nodeCache[e]!==void 0)return this.nodeCache[e];let r=t.nodes[e],a=r.name?n.createUniqueName(r.name):\"\",o=[],l=n._invokeOne(function(c){return c.createNodeMesh&&c.createNodeMesh(e)});return l&&o.push(l),r.camera!==void 0&&o.push(n.getDependency(\"camera\",r.camera).then(function(c){return n._getNodeRef(n.cameraCache,r.camera,c)})),n._invokeAll(function(c){return c.createNodeAttachment&&c.createNodeAttachment(e)}).forEach(function(c){o.push(c)}),this.nodeCache[e]=Promise.all(o).then(function(c){let h;if(h=r.isBone===!0?new Vc:c.length>1?new tn:c.length===1?c[0]:new ct,h!==c[0])for(let u=0,d=c.length;u1){let u=n.associations.get(h);n.associations.set(h,{...u})}}else n.associations.set(h,{});return n.associations.get(h).nodes=e,h}),this.nodeCache[e]}loadScene(e){let t=this.extensions,i=this.json.scenes[e],n=this,r=new tn;i.name&&(r.name=n.createUniqueName(i.name)),Ji(r,i),i.extensions&&Es(t,r,i);let a=i.nodes||[],o=[];for(let l=0,c=a.length;l{let h=new Map;for(let[u,d]of n.associations)(u instanceof zi||u instanceof xt)&&h.set(u,d);return c.traverse(u=>{let d=n.associations.get(u);d!=null&&h.set(u,d)}),h})(r),r})}_createAnimationTracks(e,t,i,n,r){let a=[],o=e.name?e.name:e.uuid,l=[],c;switch(Gn[r.path]===Gn.weights?e.traverse(function(d){d.morphTargetInfluences&&l.push(d.name?d.name:d.uuid)}):l.push(o),Gn[r.path]){case Gn.weights:c=as;break;case Gn.rotation:c=os;break;case Gn.translation:case Gn.scale:c=ls;break;default:i.itemSize===1?c=as:c=ls}let h=n.interpolation!==void 0?VS[n.interpolation]:ha,u=this._getArrayFromAccessor(i);for(let d=0,p=l.length;d{let n=this._getIdleWorker();n!==-1?(this._initWorker(n),this.workerStatus|=1<e.terminate()),this.workersResolve.length=0,this.workers.length=0,this.queue.length=0,this.workerStatus=0}},py=9,Ay=15,gy=16,my=22,fy=37,by=43,vy=76,yy=83,xy=97,_y=100,Ey=103,Sy=109,Cy=122,wy=123,My=131,Iy=132,Ty=133,By=134,Ry=137,Dy=138,Ly=139,Py=140,Fy=141,Uy=142,Ny=145,Oy=146,ky=148,Qy=152,Gy=153,zy=154,Hy=155,Vy=156,qy=157,jy=158,Wy=165,Xy=166,Ky=1000054e3,Jy=1000054001,Yy=1000054004,$y=1000054005,tm=1000066e3,Zy=1000066004,Ns=class{constructor(e,t,i,n){this._dataView=void 0,this._littleEndian=void 0,this._offset=void 0,this._dataView=new DataView(e.buffer,e.byteOffset+t,i),this._littleEndian=n,this._offset=0}_nextUint8(){let e=this._dataView.getUint8(this._offset);return this._offset+=1,e}_nextUint16(){let e=this._dataView.getUint16(this._offset,this._littleEndian);return this._offset+=2,e}_nextUint32(){let e=this._dataView.getUint32(this._offset,this._littleEndian);return this._offset+=4,e}_nextUint64(){let e=this._dataView.getUint32(this._offset,this._littleEndian)+4294967296*this._dataView.getUint32(this._offset+4,this._littleEndian);return this._offset+=8,e}_nextInt32(){let e=this._dataView.getInt32(this._offset,this._littleEndian);return this._offset+=4,e}_nextUint8Array(e){let t=new Uint8Array(this._dataView.buffer,this._dataView.byteOffset+this._offset,e);return this._offset+=e,t}_skip(e){return this._offset+=e,this}_scan(e,t=0){let i=this._offset,n=0;for(;this._dataView.getUint8(this._offset)!==t&&ne.arrayBuffer()).then(e=>WebAssembly.instantiate(e,zu)).then(this._init):WebAssembly.instantiate(Buffer.from(s0,\"base64\"),zu).then(this._init),Gu)}_init(e){En=e.instance,zu.env.emscripten_notify_memory_growth(0)}decode(e,t=0){if(!En)throw new Error(\"ZSTDDecoder: Await .init() before decoding.\");let i=e.byteLength,n=En.exports.malloc(i);LA.set(e,n),t=t||Number(En.exports.ZSTD_findDecompressedSize(n,i));let r=En.exports.malloc(t),a=En.exports.ZSTD_decompress(r,t,n,i),o=LA.slice(r,r+a);return En.exports.free(n),En.exports.free(r),o}},s0=\"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IAMNACAEQQNxBEADQCACRQ0FIAAgAkF/aiICaiIDIAEgAmotAAA6AAAgA0EDcQ0ACwsgAkEDTQ0AA0AgACACQXxqIgJqIAEgAmooAgA2AgAgAkEDSw0ACwsgAkUNAgNAIAAgAkF/aiICaiABIAJqLQAAOgAAIAINAAsMAgsgAkEDTQ0AIAIhBANAIAMgASgCADYCACABQQRqIQEgA0EEaiEDIARBfGoiBEEDSw0ACyACQQNxIQILIAJFDQADQCADIAEtAAA6AAAgA0EBaiEDIAFBAWohASACQX9qIgINAAsLIAAL8wICAn8BfgJAIAJFDQAgACACaiIDQX9qIAE6AAAgACABOgAAIAJBA0kNACADQX5qIAE6AAAgACABOgABIANBfWogAToAACAAIAE6AAIgAkEHSQ0AIANBfGogAToAACAAIAE6AAMgAkEJSQ0AIABBACAAa0EDcSIEaiIDIAFB/wFxQYGChAhsIgE2AgAgAyACIARrQXxxIgRqIgJBfGogATYCACAEQQlJDQAgAyABNgIIIAMgATYCBCACQXhqIAE2AgAgAkF0aiABNgIAIARBGUkNACADIAE2AhggAyABNgIUIAMgATYCECADIAE2AgwgAkFwaiABNgIAIAJBbGogATYCACACQWhqIAE2AgAgAkFkaiABNgIAIAQgA0EEcUEYciIEayICQSBJDQAgAa0iBUIghiAFhCEFIAMgBGohAQNAIAEgBTcDGCABIAU3AxAgASAFNwMIIAEgBTcDACABQSBqIQEgAkFgaiICQR9LDQALCyAACy8BAn8gACgCBCAAKAIAQQJ0aiICLQACIQMgACACLwEAIAEgAi0AAxAIajYCACADCy8BAn8gACgCBCAAKAIAQQJ0aiICLQACIQMgACACLwEAIAEgAi0AAxAFajYCACADCx8AIAAgASACKAIEEAg2AgAgARAEGiAAIAJBCGo2AgQLCAAgAGdBH3MLugUBDX8jAEEQayIKJAACfyAEQQNNBEAgCkEANgIMIApBDGogAyAEEAsaIAAgASACIApBDGpBBBAVIgBBbCAAEAMbIAAgACAESxsMAQsgAEEAIAEoAgBBAXRBAmoQECENQVQgAygAACIGQQ9xIgBBCksNABogAiAAQQVqNgIAIAMgBGoiAkF8aiEMIAJBeWohDiACQXtqIRAgAEEGaiELQQQhBSAGQQR2IQRBICAAdCIAQQFyIQkgASgCACEPQQAhAiADIQYCQANAIAlBAkggAiAPS3JFBEAgAiEHAkAgCARAA0AgBEH//wNxQf//A0YEQCAHQRhqIQcgBiAQSQR/IAZBAmoiBigAACAFdgUgBUEQaiEFIARBEHYLIQQMAQsLA0AgBEEDcSIIQQNGBEAgBUECaiEFIARBAnYhBCAHQQNqIQcMAQsLIAcgCGoiByAPSw0EIAVBAmohBQNAIAIgB0kEQCANIAJBAXRqQQA7AQAgAkEBaiECDAELCyAGIA5LQQAgBiAFQQN1aiIHIAxLG0UEQCAHKAAAIAVBB3EiBXYhBAwCCyAEQQJ2IQQLIAYhBwsCfyALQX9qIAQgAEF/anEiBiAAQQF0QX9qIgggCWsiEUkNABogBCAIcSIEQQAgESAEIABIG2shBiALCyEIIA0gAkEBdGogBkF/aiIEOwEAIAlBASAGayAEIAZBAUgbayEJA0AgCSAASARAIABBAXUhACALQX9qIQsMAQsLAn8gByAOS0EAIAcgBSAIaiIFQQN1aiIGIAxLG0UEQCAFQQdxDAELIAUgDCIGIAdrQQN0awshBSACQQFqIQIgBEUhCCAGKAAAIAVBH3F2IQQMAQsLQWwgCUEBRyAFQSBKcg0BGiABIAJBf2o2AgAgBiAFQQdqQQN1aiADawwBC0FQCyEAIApBEGokACAACwkAQQFBBSAAGwsMACAAIAEoAAA2AAALqgMBCn8jAEHwAGsiCiQAIAJBAWohDiAAQQhqIQtBgIAEIAVBf2p0QRB1IQxBACECQQEhBkEBIAV0IglBf2oiDyEIA0AgAiAORkUEQAJAIAEgAkEBdCINai8BACIHQf//A0YEQCALIAhBA3RqIAI2AgQgCEF/aiEIQQEhBwwBCyAGQQAgDCAHQRB0QRB1ShshBgsgCiANaiAHOwEAIAJBAWohAgwBCwsgACAFNgIEIAAgBjYCACAJQQN2IAlBAXZqQQNqIQxBACEAQQAhBkEAIQIDQCAGIA5GBEADQAJAIAAgCUYNACAKIAsgAEEDdGoiASgCBCIGQQF0aiICIAIvAQAiAkEBajsBACABIAUgAhAUayIIOgADIAEgAiAIQf8BcXQgCWs7AQAgASAEIAZBAnQiAmooAgA6AAIgASACIANqKAIANgIEIABBAWohAAwBCwsFIAEgBkEBdGouAQAhDUEAIQcDQCAHIA1ORQRAIAsgAkEDdGogBjYCBANAIAIgDGogD3EiAiAISw0ACyAHQQFqIQcMAQsLIAZBAWohBgwBCwsgCkHwAGokAAsjAEIAIAEQCSAAhUKHla+vmLbem55/fkLj3MqV/M7y9YV/fAsQACAAQn43AwggACABNgIACyQBAX8gAARAIAEoAgQiAgRAIAEoAgggACACEQEADwsgABAmCwsfACAAIAEgAi8BABAINgIAIAEQBBogACACQQRqNgIEC0oBAX9BoCAoAgAiASAAaiIAQX9MBEBBiCBBMDYCAEF/DwsCQCAAPwBBEHRNDQAgABBmDQBBiCBBMDYCAEF/DwtBoCAgADYCACABC9cBAQh/Qbp/IQoCQCACKAIEIgggAigCACIJaiIOIAEgAGtLDQBBbCEKIAkgBCADKAIAIgtrSw0AIAAgCWoiBCACKAIIIgxrIQ0gACABQWBqIg8gCyAJQQAQKSADIAkgC2o2AgACQAJAIAwgBCAFa00EQCANIQUMAQsgDCAEIAZrSw0CIAcgDSAFayIAaiIBIAhqIAdNBEAgBCABIAgQDxoMAgsgBCABQQAgAGsQDyEBIAIgACAIaiIINgIEIAEgAGshBAsgBCAPIAUgCEEBECkLIA4hCgsgCgubAgEBfyMAQYABayINJAAgDSADNgJ8AkAgAkEDSwRAQX8hCQwBCwJAAkACQAJAIAJBAWsOAwADAgELIAZFBEBBuH8hCQwEC0FsIQkgBS0AACICIANLDQMgACAHIAJBAnQiAmooAgAgAiAIaigCABA7IAEgADYCAEEBIQkMAwsgASAJNgIAQQAhCQwCCyAKRQRAQWwhCQwCC0EAIQkgC0UgDEEZSHINAUEIIAR0QQhqIQBBACECA0AgAiAATw0CIAJBQGshAgwAAAsAC0FsIQkgDSANQfwAaiANQfgAaiAFIAYQFSICEAMNACANKAJ4IgMgBEsNACAAIA0gDSgCfCAHIAggAxAYIAEgADYCACACIQkLIA1BgAFqJAAgCQsLACAAIAEgAhALGgsQACAALwAAIAAtAAJBEHRyCy8AAn9BuH8gAUEISQ0AGkFyIAAoAAQiAEF3Sw0AGkG4fyAAQQhqIgAgACABSxsLCwkAIAAgATsAAAsDAAELigYBBX8gACAAKAIAIgVBfnE2AgBBACAAIAVBAXZqQYQgKAIAIgQgAEYbIQECQAJAIAAoAgQiAkUNACACKAIAIgNBAXENACACQQhqIgUgA0EBdkF4aiIDQQggA0EISxtnQR9zQQJ0QYAfaiIDKAIARgRAIAMgAigCDDYCAAsgAigCCCIDBEAgAyACKAIMNgIECyACKAIMIgMEQCADIAIoAgg2AgALIAIgAigCACAAKAIAQX5xajYCAEGEICEAAkACQCABRQ0AIAEgAjYCBCABKAIAIgNBAXENASADQQF2QXhqIgNBCCADQQhLG2dBH3NBAnRBgB9qIgMoAgAgAUEIakYEQCADIAEoAgw2AgALIAEoAggiAwRAIAMgASgCDDYCBAsgASgCDCIDBEAgAyABKAIINgIAQYQgKAIAIQQLIAIgAigCACABKAIAQX5xajYCACABIARGDQAgASABKAIAQQF2akEEaiEACyAAIAI2AgALIAIoAgBBAXZBeGoiAEEIIABBCEsbZ0Efc0ECdEGAH2oiASgCACEAIAEgBTYCACACIAA2AgwgAkEANgIIIABFDQEgACAFNgIADwsCQCABRQ0AIAEoAgAiAkEBcQ0AIAJBAXZBeGoiAkEIIAJBCEsbZ0Efc0ECdEGAH2oiAigCACABQQhqRgRAIAIgASgCDDYCAAsgASgCCCICBEAgAiABKAIMNgIECyABKAIMIgIEQCACIAEoAgg2AgBBhCAoAgAhBAsgACAAKAIAIAEoAgBBfnFqIgI2AgACQCABIARHBEAgASABKAIAQQF2aiAANgIEIAAoAgAhAgwBC0GEICAANgIACyACQQF2QXhqIgFBCCABQQhLG2dBH3NBAnRBgB9qIgIoAgAhASACIABBCGoiAjYCACAAIAE2AgwgAEEANgIIIAFFDQEgASACNgIADwsgBUEBdkF4aiIBQQggAUEISxtnQR9zQQJ0QYAfaiICKAIAIQEgAiAAQQhqIgI2AgAgACABNgIMIABBADYCCCABRQ0AIAEgAjYCAAsLDgAgAARAIABBeGoQJQsLgAIBA38CQCAAQQ9qQXhxQYQgKAIAKAIAQQF2ayICEB1Bf0YNAAJAQYQgKAIAIgAoAgAiAUEBcQ0AIAFBAXZBeGoiAUEIIAFBCEsbZ0Efc0ECdEGAH2oiASgCACAAQQhqRgRAIAEgACgCDDYCAAsgACgCCCIBBEAgASAAKAIMNgIECyAAKAIMIgFFDQAgASAAKAIINgIAC0EBIQEgACAAKAIAIAJBAXRqIgI2AgAgAkEBcQ0AIAJBAXZBeGoiAkEIIAJBCEsbZ0Efc0ECdEGAH2oiAygCACECIAMgAEEIaiIDNgIAIAAgAjYCDCAAQQA2AgggAkUNACACIAM2AgALIAELtwIBA38CQAJAIABBASAAGyICEDgiAA0AAkACQEGEICgCACIARQ0AIAAoAgAiA0EBcQ0AIAAgA0EBcjYCACADQQF2QXhqIgFBCCABQQhLG2dBH3NBAnRBgB9qIgEoAgAgAEEIakYEQCABIAAoAgw2AgALIAAoAggiAQRAIAEgACgCDDYCBAsgACgCDCIBBEAgASAAKAIINgIACyACECchAkEAIQFBhCAoAgAhACACDQEgACAAKAIAQX5xNgIAQQAPCyACQQ9qQXhxIgMQHSICQX9GDQIgAkEHakF4cSIAIAJHBEAgACACaxAdQX9GDQMLAkBBhCAoAgAiAUUEQEGAICAANgIADAELIAAgATYCBAtBhCAgADYCACAAIANBAXRBAXI2AgAMAQsgAEUNAQsgAEEIaiEBCyABC7kDAQJ/IAAgA2ohBQJAIANBB0wEQANAIAAgBU8NAiAAIAItAAA6AAAgAEEBaiEAIAJBAWohAgwAAAsACyAEQQFGBEACQCAAIAJrIgZBB00EQCAAIAItAAA6AAAgACACLQABOgABIAAgAi0AAjoAAiAAIAItAAM6AAMgAEEEaiACIAZBAnQiBkHAHmooAgBqIgIQFyACIAZB4B5qKAIAayECDAELIAAgAhAMCyACQQhqIQIgAEEIaiEACwJAAkACQAJAIAUgAU0EQCAAIANqIQEgBEEBRyAAIAJrQQ9Kcg0BA0AgACACEAwgAkEIaiECIABBCGoiACABSQ0ACwwFCyAAIAFLBEAgACEBDAQLIARBAUcgACACa0EPSnINASAAIQMgAiEEA0AgAyAEEAwgBEEIaiEEIANBCGoiAyABSQ0ACwwCCwNAIAAgAhAHIAJBEGohAiAAQRBqIgAgAUkNAAsMAwsgACEDIAIhBANAIAMgBBAHIARBEGohBCADQRBqIgMgAUkNAAsLIAIgASAAa2ohAgsDQCABIAVPDQEgASACLQAAOgAAIAFBAWohASACQQFqIQIMAAALAAsLQQECfyAAIAAoArjgASIDNgLE4AEgACgCvOABIQQgACABNgK84AEgACABIAJqNgK44AEgACABIAQgA2tqNgLA4AELpgEBAX8gACAAKALs4QEQFjYCyOABIABCADcD+OABIABCADcDuOABIABBwOABakIANwMAIABBqNAAaiIBQYyAgOAANgIAIABBADYCmOIBIABCADcDiOEBIABCAzcDgOEBIABBrNABakHgEikCADcCACAAQbTQAWpB6BIoAgA2AgAgACABNgIMIAAgAEGYIGo2AgggACAAQaAwajYCBCAAIABBEGo2AgALYQEBf0G4fyEDAkAgAUEDSQ0AIAIgABAhIgFBA3YiADYCCCACIAFBAXE2AgQgAiABQQF2QQNxIgM2AgACQCADQX9qIgFBAksNAAJAIAFBAWsOAgEAAgtBbA8LIAAhAwsgAwsMACAAIAEgAkEAEC4LiAQCA38CfiADEBYhBCAAQQBBKBAQIQAgBCACSwRAIAQPCyABRQRAQX8PCwJAAkAgA0EBRg0AIAEoAAAiBkGo6r5pRg0AQXYhAyAGQXBxQdDUtMIBRw0BQQghAyACQQhJDQEgAEEAQSgQECEAIAEoAAQhASAAQQE2AhQgACABrTcDAEEADwsgASACIAMQLyIDIAJLDQAgACADNgIYQXIhAyABIARqIgVBf2otAAAiAkEIcQ0AIAJBIHEiBkUEQEFwIQMgBS0AACIFQacBSw0BIAVBB3GtQgEgBUEDdkEKaq2GIgdCA4h+IAd8IQggBEEBaiEECyACQQZ2IQMgAkECdiEFAkAgAkEDcUF/aiICQQJLBEBBACECDAELAkACQAJAIAJBAWsOAgECAAsgASAEai0AACECIARBAWohBAwCCyABIARqLwAAIQIgBEECaiEEDAELIAEgBGooAAAhAiAEQQRqIQQLIAVBAXEhBQJ+AkACQAJAIANBf2oiA0ECTQRAIANBAWsOAgIDAQtCfyAGRQ0DGiABIARqMQAADAMLIAEgBGovAACtQoACfAwCCyABIARqKAAArQwBCyABIARqKQAACyEHIAAgBTYCICAAIAI2AhwgACAHNwMAQQAhAyAAQQA2AhQgACAHIAggBhsiBzcDCCAAIAdCgIAIIAdCgIAIVBs+AhALIAMLWwEBf0G4fyEDIAIQFiICIAFNBH8gACACakF/ai0AACIAQQNxQQJ0QaAeaigC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C0Egak8NAQsgByAHKQNoNwMQIAcgBykDYDcDCCAEIA0gB0EIaiAHQdwAaiAPIA4gESAQEB4hCwwBCyACIARqIQggBCAKEAcgAkERTwRAIARBEGohAgNAIAIgCkEQaiIKEAcgAkEQaiICIAhJDQALCyAIIAlrIQIgByATNgJcIAkgCCAOa0sEQCAJIAggEWtLBEBBbCELDAILIBAgAiAOayICaiIKIANqIBBNBEAgCCAKIAMQDxoMAgsgCCAKQQAgAmsQDyEIIAcgAiADaiIDNgJkIAggAmshCCAOIQILIAlBEE8EQCADIAhqIQMDQCAIIAIQByACQRBqIQIgCEEQaiIIIANJDQALDAELAkAgCUEHTQRAIAggAi0AADoAACAIIAItAAE6AAEgCCACLQACOgACIAggAi0AAzoAAyAIQQRqIAIgCUECdCIDQcAeaigCAGoiAhAXIAIgA0HgHmooAgBrIQIgBygCZCEDDAELIAggAhAMCyADQQlJDQAgAyAIaiEDIAhBCGoiCCACQQhqIgJrQQ9MBEADQCAIIAIQDCACQQhqIQIgCEEIaiIIIANJDQAMAgALAAsDQCAIIAIQByACQRBqIQIgCEEQaiIIIANJDQALCyAHQRhqEAQaIAsgDCALEAMiAhshDCAEIAQgC2ogAhshBCAFQX9qIgUNAAsgDBADDQFBbCEMIAdBGGoQBEECSQ0BQQAhCANAIAhBA0cEQCAAIAhBAnQiAmpBrNABaiACIAdqKAJENgIAIAhBAWohCAwBCwsgBygCXCEIC0G6fyEMIA8gCGsiACANIARrSw0AIAQEfyAEIAggABALIABqBUEACyABayEMCyAHQfAAaiQAIAwLkRcCFn8FfiMAQdABayIHJAAgByAAKALw4QEiCDYCvAEgASACaiESIAggACgCgOIBaiETAkACQCAFRQRAIAEhAwwBCyAAKALE4AEhESAAKALA4AEhFSAAKAK84AEhDyAAQQE2AozhAUEAIQgDQCAIQQNHBEAgByAIQQJ0IgJqIAAgAmpBrNABaigCADYCVCAIQQFqIQgMAQsLIAcgETYCZCAHIA82AmAgByABIA9rNgJoQWwhECAHQShqIAMgBBAGEAMNASAFQQQgBUEESBshFyAHQTxqIAdBKGogACgCABATIAdBxABqIAdBKGogACgCCBATIAdBzABqIAdB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IAdB0AFqJAAgEAslACAAQgA3AgAgAEEAOwEIIABBADoACyAAIAE2AgwgACACOgAKC7QFAQN/IwBBMGsiBCQAIABB/wFqIgVBfWohBgJAIAMvAQIEQCAEQRhqIAEgAhAGIgIQAw0BIARBEGogBEEYaiADEBwgBEEIaiAEQRhqIAMQHCAAIQMDQAJAIARBGGoQBCADIAZPckUEQCADIARBEGogBEEYahASOgAAIAMgBEEIaiAEQRhqEBI6AAEgBEEYahAERQ0BIANBAmohAwsgBUF+aiEFAn8DQEG6fyECIAMiASAFSw0FIAEgBEEQaiAEQRhqEBI6AAAgAUEBaiEDIARBGGoQBEEDRgRAQQIhAiAEQQhqDAILIAMgBUsNBSABIARBCGogBEEYahASOgABIAFBAmohA0EDIQIgBEEYahAEQQNHDQALIARBEGoLIQUgAyAFIARBGGoQEjoAACABIAJqIABrIQIMAwsgAyAEQRBqIARBGGoQEjoAAiADIARBCGogBEEYahASOgADIANBBGohAwwAAAsACyAEQRhqIAEgAhAGIgIQAw0AIARBEGogBEEYaiADEBwgBEEIaiAEQRhqIAMQHCAAIQMDQAJAIARBGGoQBCADIAZPckUEQCADIARBEGogBEEYahAROgAAIAMgBEEIaiAEQRhqEBE6AAEgBEEYahAERQ0BIANBAmohAwsgBUF+aiEFAn8DQEG6fyECIAMiASAFSw0EIAEgBEEQaiAEQRhqEBE6AAAgAUEBaiEDIARBGGoQBEEDRgRAQQIhAiAEQQhqDAILIAMgBUsNBCABIARBCGogBEEYahAROgABIAFBAmohA0EDIQIgBEEYahAEQQNHDQALIARBEGoLIQUgAyAFIARBGGoQEToAACABIAJqIABrIQIMAgsgAyAEQRBqIARBGGoQEToAAiADIARBCGogBEEYahAROgADIANBBGohAwwAAAsACyAEQTBqJAAgAgtpAQF/An8CQAJAIAJBB00NACABKAAAQbfIwuF+Rw0AIAAgASgABDYCmOIBQWIgAEEQaiABIAIQPiIDEAMNAhogAEKBgICAEDcDiOEBIAAgASADaiACIANrECoMAQsgACABIAIQKgtBAAsLrQMBBn8jAEGAAWsiAyQAQWIhCAJAIAJBCUkNACAAQZjQAGogAUEIaiIEIAJBeGogAEGY0AAQMyIFEAMiBg0AIANBHzYCfCADIANB/ABqIANB+ABqIAQgBCAFaiAGGyIEIAEgAmoiAiAEaxAVIgUQAw0AIAMoAnwiBkEfSw0AIAMoAngiB0EJTw0AIABBiCBqIAMgBkGAC0GADCAHEBggA0E0NgJ8IAMgA0H8AGogA0H4AGogBCAFaiIEIAIgBGsQFSIFEAMNACADKAJ8IgZBNEsNACADKAJ4IgdBCk8NACAAQZAwaiADIAZBgA1B4A4gBxAYIANBIzYCfCADIANB/ABqIANB+ABqIAQgBWoiBCACIARrEBUiBRADDQAgAygCfCIGQSNLDQAgAygCeCIHQQpPDQAgACADIAZBwBBB0BEgBxAYIAQgBWoiBEEMaiIFIAJLDQAgAiAFayEFQQAhAgNAIAJBA0cEQCAEKAAAIgZBf2ogBU8NAiAAIAJBAnRqQZzQAWogBjYCACACQQFqIQIgBEEEaiEEDAELCyAEIAFrIQgLIANBgAFqJAAgCAtGAQN/IABBCGohAyAAKAIEIQJBACEAA0AgACACdkUEQCABIAMgAEEDdGotAAJBFktqIQEgAEEBaiEADAELCyABQQggAmt0C4YDAQV/Qbh/IQcCQCADRQ0AIAItAAAiBEUEQCABQQA2AgBBAUG4fyADQQFGGw8LAn8gAkEBaiIFIARBGHRBGHUiBkF/Sg0AGiAGQX9GBEAgA0EDSA0CIAUvAABBgP4BaiEEIAJBA2oMAQsgA0ECSA0BIAItAAEgBEEIdHJBgIB+aiEEIAJBAmoLIQUgASAENgIAIAVBAWoiASACIANqIgNLDQBBbCEHIABBEGogACAFLQAAIgVBBnZBI0EJIAEgAyABa0HAEEHQEUHwEiAAKAKM4QEgACgCnOIBIAQQHyIGEAMiCA0AIABBmCBqIABBCGogBUEEdkEDcUEfQQggASABIAZqIAgbIgEgAyABa0GAC0GADEGAFyAAKAKM4QEgACgCnOIBIAQQHyIGEAMiCA0AIABBoDBqIABBBGogBUECdkEDcUE0QQkgASABIAZqIAgbIgEgAyABa0GADUHgDkGQGSAAKAKM4QEgACgCnOIBIAQQHyIAEAMNACAAIAFqIAJrIQcLIAcLrQMBCn8jAEGABGsiCCQAAn9BUiACQf8BSw0AGkFUIANBDEsNABogAkEBaiELIABBBGohCUGAgAQgA0F/anRBEHUhCkEAIQJBASEEQQEgA3QiB0F/aiIMIQUDQCACIAtGRQRAAkAgASACQQF0Ig1qLwEAIgZB//8DRgRAIAkgBUECdGogAjoAAiAFQX9qIQVBASEGDAELIARBACAKIAZBEHRBEHVKGyEECyAIIA1qIAY7AQAgAkEBaiECDAELCyAAIAQ7AQIgACADOwEAIAdBA3YgB0EBdmpBA2ohBkEAIQRBACECA0AgBCALRkUEQCABIARBAXRqLgEAIQpBACEAA0AgACAKTkUEQCAJIAJBAnRqIAQ6AAIDQCACIAZqIAxxIgIgBUsNAAsgAEEBaiEADAELCyAEQQFqIQQMAQsLQX8gAg0AGkEAIQIDfyACIAdGBH9BAAUgCCAJIAJBAnRqIgAtAAJBAXRqIgEgAS8BACIBQQFqOwEAIAAgAyABEBRrIgU6AAMgACABIAVB/wFxdCAHazsBACACQQFqIQIMAQsLCyEFIAhBgARqJAAgBQvjBgEIf0FsIQcCQCACQQNJDQACQAJAAkACQCABLQAAIgNBA3EiCUEBaw4DAwEAAgsgACgCiOEBDQBBYg8LIAJBBUkNAkEDIQYgASgAACEFAn8CQAJAIANBAnZBA3EiCEF+aiIEQQFNBEAgBEEBaw0BDAILIAVBDnZB/wdxIQQgBUEEdkH/B3EhAyAIRQwCCyAFQRJ2IQRBBCEGIAVBBHZB//8AcSEDQQAMAQsgBUEEdkH//w9xIgNBgIAISw0DIAEtAARBCnQgBUEWdnIhBEEFIQZBAAshBSAEIAZqIgogAksNAgJAIANBgQZJDQAgACgCnOIBRQ0AQQAhAgNAIAJBg4ABSw0BIAJBQGshAgwAAAsACwJ/IAlBA0YEQCABIAZqIQEgAEHw4gFqIQIgACgCDCEGIAUEQCACIAMgASAEIAYQXwwCCyACIAMgASAEIAYQXQwBCyAAQbjQAWohAiABIAZqIQEgAEHw4gFqIQYgAEGo0ABqIQggBQRAIAggBiADIAEgBCACEF4MAQsgCCAGIAMgASAEIAIQXAsQAw0CIAAgAzYCgOIBIABBATYCiOEBIAAgAEHw4gFqNgLw4QEgCUECRgRAIAAgAEGo0ABqNgIMCyAAIANqIgBBiOMBakIANwAAIABBgOMBakIANwAAIABB+OIBakIANwAAIABB8OIBakIANwAAIAoPCwJ/AkACQAJAIANBAnZBA3FBf2oiBEECSw0AIARBAWsOAgACAQtBASEEIANBA3YMAgtBAiEEIAEvAABBBHYMAQtBAyEEIAEQIUEEdgsiAyAEaiIFQSBqIAJLBEAgBSACSw0CIABB8OIBaiABIARqIAMQCyEBIAAgAzYCgOIBIAAgATYC8OEBIAEgA2oiAEIANwAYIABCADcAECAAQgA3AAggAEIANwAAIAUPCyAAIAM2AoDiASAAIAEgBGo2AvDhASAFDwsCfwJAAkACQCADQQJ2QQNxQX9qIgRBAksNACAEQQFrDgIAAgELQQEhByADQQN2DAILQQIhByABLwAAQQR2DAELIAJBBEkgARAhIgJBj4CAAUtyDQFBAyEHIAJBBHYLIQIgAEHw4gFqIAEgB2otAAAgAkEgahAQIQEgACACNgKA4gEgACABNgLw4QEgB0EBaiEHCyAHC0sAIABC+erQ0OfJoeThADcDICAAQgA3AxggAELP1tO+0ser2UI3AxAgAELW64Lu6v2J9eAANwMIIABCADcDACAAQShqQQBBKBAQGgviAgICfwV+IABBKGoiASAAKAJIaiECAn4gACkDACIDQiBaBEAgACkDECIEQgeJIAApAwgiBUIBiXwgACkDGCIGQgyJfCAAKQMgIgdCEol8IAUQGSAEEBkgBhAZIAcQGQwBCyAAKQMYQsXP2bLx5brqJ3wLIAN8IQMDQCABQQhqIgAgAk0EQEIAIAEpAAAQCSADhUIbiUKHla+vmLbem55/fkLj3MqV/M7y9YV/fCEDIAAhAQwBCwsCQCABQQRqIgAgAksEQCABIQAMAQsgASgAAK1Ch5Wvr5i23puef34gA4VCF4lCz9bTvtLHq9lCfkL5893xmfaZqxZ8IQMLA0AgACACSQRAIAAxAABCxc/ZsvHluuonfiADhUILiUKHla+vmLbem55/fiEDIABBAWohAAwBCwsgA0IhiCADhULP1tO+0ser2UJ+IgNCHYggA4VC+fPd8Zn2masWfiIDQiCIIAOFC+8CAgJ/BH4gACAAKQMAIAKtfDcDAAJAAkAgACgCSCIDIAJqIgRBH00EQCABRQ0BIAAgA2pBKGogASACECAgACgCSCACaiEEDAELIAEgAmohAgJ/IAMEQCAAQShqIgQgA2ogAUEgIANrECAgACAAKQMIIAQpAAAQCTcDCCAAIAApAxAgACkAMBAJNwMQIAAgACkDGCAAKQA4EAk3AxggACAAKQMgIABBQGspAAAQCTcDICAAKAJIIQMgAEEANgJIIAEgA2tBIGohAQsgAUEgaiACTQsEQCACQWBqIQMgACkDICEFIAApAxghBiAAKQMQIQcgACkDCCEIA0AgCCABKQAAEAkhCCAHIAEpAAgQCSEHIAYgASkAEBAJIQYgBSABKQAYEAkhBSABQSBqIgEgA00NAAsgACAFNwMgIAAgBjcDGCAAIAc3AxAgACAINwMICyABIAJPDQEgAEEoaiABIAIgAWsiBBAgCyAAIAQ2AkgLCy8BAX8gAEUEQEG2f0EAIAMbDwtBun8hBCADIAFNBH8gACACIAMQEBogAwVBun8LCy8BAX8gAEUEQEG2f0EAIAMbDwtBun8hBCADIAFNBH8gACACIAMQCxogAwVBun8LC6gCAQZ/IwBBEGsiByQAIABB2OABaikDAEKAgIAQViEIQbh/IQUCQCAEQf//B0sNACAAIAMgBBBCIgUQAyIGDQAgACgCnOIBIQkgACAHQQxqIAMgAyAFaiAGGyIKIARBACAFIAYbayIGEEAiAxADBEAgAyEFDAELIAcoAgwhBCABRQRAQbp/IQUgBEEASg0BCyAGIANrIQUgAyAKaiEDAkAgCQRAIABBADYCnOIBDAELAkACQAJAIARBBUgNACAAQdjgAWopAwBCgICACFgNAAwBCyAAQQA2ApziAQwBCyAAKAIIED8hBiAAQQA2ApziASAGQRRPDQELIAAgASACIAMgBSAEIAgQOSEFDAELIAAgASACIAMgBSAEIAgQOiEFCyAHQRBqJAAgBQtnACAAQdDgAWogASACIAAoAuzhARAuIgEQAwRAIAEPC0G4fyECAkAgAQ0AIABB7OABaigCACIBBEBBYCECIAAoApjiASABRw0BC0EAIQIgAEHw4AFqKAIARQ0AIABBkOEBahBDCyACCycBAX8QVyIERQRAQUAPCyAEIAAgASACIAMgBBBLEE8hACAEEFYgAAs/AQF/AkACQAJAIAAoAqDiAUEBaiIBQQJLDQAgAUEBaw4CAAECCyAAEDBBAA8LIABBADYCoOIBCyAAKAKU4gELvAMCB38BfiMAQRBrIgkkAEG4fyEGAkAgBCgCACIIQQVBCSAAKALs4QEiBRtJDQAgAygCACIHQQFBBSAFGyAFEC8iBRADBEAgBSEGDAELIAggBUEDakkNACAAIAcgBRBJIgYQAw0AIAEgAmohCiAAQZDhAWohCyAIIAVrIQIgBSAHaiEHIAEhBQNAIAcgAiAJECwiBhADDQEgAkF9aiICIAZJBEBBuH8hBgwCCyAJKAIAIghBAksEQEFsIQYMAgsgB0EDaiEHAn8CQAJAAkAgCEEBaw4CAgABCyAAIAUgCiAFayAHIAYQSAwCCyAFIAogBWsgByAGEEcMAQsgBSAKIAVrIActAAAgCSgCCBBGCyIIEAMEQCAIIQYMAgsgACgC8OABBEAgCyAFIAgQRQsgAiAGayECIAYgB2ohByAFIAhqIQUgCSgCBEUNAAsgACkD0OABIgxCf1IEQEFsIQYgDCAFIAFrrFINAQsgACgC8OABBEBBaiEGIAJBBEkNASALEEQhDCAHKAAAIAynRw0BIAdBBGohByACQXxqIQILIAMgBzYCACAEIAI2AgAgBSABayEGCyAJQRBqJAAgBgsuACAAECsCf0EAQQAQAw0AGiABRSACRXJFBEBBYiAAIAEgAhA9EAMNARoLQQALCzcAIAEEQCAAIAAoAsTgASABKAIEIAEoAghqRzYCnOIBCyAAECtBABADIAFFckUEQCAAIAEQWwsL0QIBB38jAEEQayIGJAAgBiAENgIIIAYgAzYCDCAFBEAgBSgCBCEKIAUoAgghCQsgASEIAkACQANAIAAoAuzhARAWIQsCQANAIAQgC0kNASADKAAAQXBxQdDUtMIBRgRAIAMgBBAiIgcQAw0EIAQgB2shBCADIAdqIQMMAQsLIAYgAzYCDCAGIAQ2AggCQCAFBEAgACAFEE5BACEHQQAQA0UNAQwFCyAAIAogCRBNIgcQAw0ECyAAIAgQUCAMQQFHQQAgACAIIAIgBkEMaiAGQQhqEEwiByIDa0EAIAMQAxtBCkdyRQRAQbh/IQcMBAsgBxADDQMgAiAHayECIAcgCGohCEEBIQwgBigCDCEDIAYoAgghBAwBCwsgBiADNgIMIAYgBDYCCEG4fyEHIAQNASAIIAFrIQcMAQsgBiADNgIMIAYgBDYCCAsgBkEQaiQAIAcLRgECfyABIAAoArjgASICRwRAIAAgAjYCxOABIAAgATYCuOABIAAoArzgASEDIAAgATYCvOABIAAgASADIAJrajYCwOABCwutAgIEfwF+IwBBQGoiBCQAAkACQCACQQhJDQAgASgAAEFwcUHQ1LTCAUcNACABIAIQIiEBIABCADcDCCAAQQA2AgQgACABNgIADAELIARBGGogASACEC0iAxADBEAgACADEBoMAQsgAwRAIABBuH8QGgwBCyACIAQoAjAiA2shAiABIANqIQMDQAJAIAAgAyACIARBCGoQLCIFEAMEfyAFBSACIAVBA2oiBU8NAUG4fwsQGgwCCyAGQQFqIQYgAiAFayECIAMgBWohAyAEKAIMRQ0ACyAEKAI4BEAgAkEDTQRAIABBuH8QGgwCCyADQQRqIQMLIAQoAighAiAEKQMYIQcgAEEANgIEIAAgAyABazYCACAAIAIgBmytIAcgB0J/URs3AwgLIARBQGskAAslAQF/IwBBEGsiAiQAIAIgACABEFEgAigCACEAIAJBEGokACAAC30BBH8jAEGQBGsiBCQAIARB/wE2AggCQCAEQRBqIARBCGogBEEMaiABIAIQFSIGEAMEQCAGIQUMAQtBVCEFIAQoAgwiB0EGSw0AIAMgBEEQaiAEKAIIIAcQQSIFEAMNACAAIAEgBmogAiAGayADEDwhBQsgBEGQBGokACAFC4cBAgJ/An5BABAWIQMCQANAIAEgA08EQAJAIAAoAABBcHFB0NS0wgFGBEAgACABECIiAhADRQ0BQn4PCyAAIAEQVSIEQn1WDQMgBCAFfCIFIARUIQJCfiEEIAINAyAAIAEQUiICEAMNAwsgASACayEBIAAgAmohAAwBCwtCfiAFIAEbIQQLIAQLPwIBfwF+IwBBMGsiAiQAAn5CfiACQQhqIAAgARAtDQAaQgAgAigCHEEBRg0AGiACKQMICyEDIAJBMGokACADC40BAQJ/IwBBMGsiASQAAkAgAEUNACAAKAKI4gENACABIABB/OEBaigCADYCKCABIAApAvThATcDICAAEDAgACgCqOIBIQIgASABKAIoNgIYIAEgASkDIDcDECACIAFBEGoQGyAAQQA2AqjiASABIAEoAig2AgggASABKQMgNwMAIAAgARAbCyABQTBqJAALKgECfyMAQRBrIgAkACAAQQA2AgggAEIANwMAIAAQWCEBIABBEGokACABC4cBAQN/IwBBEGsiAiQAAkAgACgCAEUgACgCBEVzDQAgAiAAKAIINgIIIAIgACkCADcDAAJ/IAIoAgAiAQRAIAIoAghBqOMJIAERBQAMAQtBqOMJECgLIgFFDQAgASAAKQIANwL04QEgAUH84QFqIAAoAgg2AgAgARBZIAEhAwsgAkEQaiQAIAMLywEBAn8jAEEgayIBJAAgAEGBgIDAADYCtOIBIABBADYCiOIBIABBADYC7OEBIABCADcDkOIBIABBADYCpOMJIABBADYC3OIBIABCADcCzOIBIABBADYCvOIBIABBADYCxOABIABCADcCnOIBIABBpOIBakIANwIAIABBrOIBakEANgIAIAFCADcCECABQgA3AhggASABKQMYNwMIIAEgASkDEDcDACABKAIIQQh2QQFxIQIgAEEANgLg4gEgACACNgKM4gEgAUEgaiQAC3YBA38jAEEwayIBJAAgAARAIAEgAEHE0AFqIgIoAgA2AiggASAAKQK80AE3AyAgACgCACEDIAEgAigCADYCGCABIAApArzQATcDECADIAFBEGoQGyABIAEoAig2AgggASABKQMgNwMAIAAgARAbCyABQTBqJAALzAEBAX8gACABKAK00AE2ApjiASAAIAEoAgQiAjYCwOABIAAgAjYCvOABIAAgAiABKAIIaiICNgK44AEgACACNgLE4AEgASgCuNABBEAgAEKBgICAEDcDiOEBIAAgAUGk0ABqNgIMIAAgAUGUIGo2AgggACABQZwwajYCBCAAIAFBDGo2AgAgAEGs0AFqIAFBqNABaigCADYCACAAQbDQAWogAUGs0AFqKAIANgIAIABBtNABaiABQbDQAWooAgA2AgAPCyAAQgA3A4jhAQs7ACACRQRAQbp/DwsgBEUEQEFsDwsgAiAEEGAEQCAAIAEgAiADIAQgBRBhDwsgACABIAIgAyAEIAUQZQtGAQF/IwBBEGsiBSQAIAVBCGogBBAOAn8gBS0ACQRAIAAgASACIAMgBBAyDAELIAAgASACIAMgBBA0CyEAIAVBEGokACAACzQAIAAgAyAEIAUQNiIFEAMEQCAFDwsgBSAESQR/IAEgAiADIAVqIAQgBWsgABA1BUG4fwsLRgEBfyMAQRBrIgUkACAFQQhqIAQQDgJ/IAUtAAkEQCAAIAEgAiADIAQQYgwBCyAAIAEgAiADIAQQNQshACAFQRBqJAAgAAtZAQF/QQ8hAiABIABJBEAgAUEE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\";je.spaces[lt];var Hu=new WeakMap,Vu,qu=0,$n=class s extends Bi{constructor(e){super(e),this.transcoderPath=\"\",this.transcoderBinary=null,this.transcoderPending=null,this.workerPool=new DA,this.workerSourceURL=\"\",this.workerConfig=null,typeof MSC_TRANSCODER<\"u\"&&console.warn('THREE.KTX2Loader: Please update to latest \"basis_transcoder\". \"msc_basis_transcoder\" is no longer supported in three.js r125+.')}setTranscoderPath(e){return this.transcoderPath=e,this}setWorkerLimit(e){return this.workerPool.setWorkerLimit(e),this}async detectSupportAsync(e){return console.warn('KTX2Loader: \"detectSupportAsync()\" has been deprecated. Use \"detectSupport()\" and \"await renderer.init();\" when creating the renderer.'),await e.init(),this.detectSupport(e)}detectSupport(e){return e.isWebGPURenderer===!0?this.workerConfig={astcSupported:e.hasFeature(\"texture-compression-astc\"),astcHDRSupported:!1,etc1Supported:e.hasFeature(\"texture-compression-etc1\"),etc2Supported:e.hasFeature(\"texture-compression-etc2\"),dxtSupported:e.hasFeature(\"texture-compression-s3tc\"),bptcSupported:e.hasFeature(\"texture-compression-bc\"),pvrtcSupported:e.hasFeature(\"texture-compression-pvrtc\")}:(this.workerConfig={astcSupported:e.extensions.has(\"WEBGL_compressed_texture_astc\"),astcHDRSupported:e.extensions.has(\"WEBGL_compressed_texture_astc\")&&e.extensions.get(\"WEBGL_compressed_texture_astc\").getSupportedProfiles().includes(\"hdr\"),etc1Supported:e.extensions.has(\"WEBGL_compressed_texture_etc1\"),etc2Supported:e.extensions.has(\"WEBGL_compressed_texture_etc\"),dxtSupported:e.extensions.has(\"WEBGL_compressed_texture_s3tc\"),bptcSupported:e.extensions.has(\"EXT_texture_compression_bptc\"),pvrtcSupported:e.extensions.has(\"WEBGL_compressed_texture_pvrtc\")||e.extensions.has(\"WEBKIT_WEBGL_compressed_texture_pvrtc\")},typeof navigator<\"u\"&&navigator.platform!==void 0&&navigator.userAgent!==void 0&&navigator.platform.indexOf(\"Linux\")>=0&&navigator.userAgent.indexOf(\"Firefox\")>=0&&this.workerConfig.astcSupported&&this.workerConfig.etc2Supported&&this.workerConfig.bptcSupported&&this.workerConfig.dxtSupported&&(this.workerConfig.astcSupported=!1,this.workerConfig.etc2Supported=!1)),this}init(){if(!this.transcoderPending){let e=new Hi(this.manager);e.setPath(this.transcoderPath),e.setWithCredentials(this.withCredentials);let t=e.loadAsync(\"basis_transcoder.js\"),i=new Hi(this.manager);i.setPath(this.transcoderPath),i.setResponseType(\"arraybuffer\"),i.setWithCredentials(this.withCredentials);let n=i.loadAsync(\"basis_transcoder.wasm\");this.transcoderPending=Promise.all([t,n]).then(([r,a])=>{let o=s.BasisWorker.toString(),l=[\"/* constants */\",\"let _EngineFormat = \"+JSON.stringify(s.EngineFormat),\"let _EngineType = \"+JSON.stringify(s.EngineType),\"let _TranscoderFormat = \"+JSON.stringify(s.TranscoderFormat),\"let _BasisFormat = \"+JSON.stringify(s.BasisFormat),\"/* basis_transcoder.js */\",r,\"/* worker */\",o.substring(o.indexOf(\"{\")+1,o.lastIndexOf(\"}\"))].join(`\n`);this.workerSourceURL=URL.createObjectURL(new Blob([l])),this.transcoderBinary=a,this.workerPool.setWorkerCreator(()=>{let c=new Worker(this.workerSourceURL),h=this.transcoderBinary.slice(0);return c.postMessage({type:\"init\",config:this.workerConfig,transcoderBinary:h},[h]),c})}),qu>0&&console.warn(\"THREE.KTX2Loader: Multiple active KTX2 loaders may cause performance issues. Use a single KTX2Loader instance, or call .dispose() on old instances.\"),qu++}return this.transcoderPending}load(e,t,i,n){if(this.workerConfig===null)throw new Error(\"THREE.KTX2Loader: Missing initialization with `.detectSupport( renderer )`.\");let r=new Hi(this.manager);r.setPath(this.path),r.setCrossOrigin(this.crossOrigin),r.setWithCredentials(this.withCredentials),r.setRequestHeader(this.requestHeader),r.setResponseType(\"arraybuffer\"),r.load(e,a=>{this.parse(a,t,n)},i,n)}parse(e,t,i){if(this.workerConfig===null)throw new Error(\"THREE.KTX2Loader: Missing initialization with `.detectSupport( renderer )`.\");if(Hu.has(e))return Hu.get(e).promise.then(t).catch(i);this._createTexture(e).then(n=>t?t(n):null).catch(i)}_createTextureFrom(e,t){let{type:i,error:n,data:{faces:r,width:a,height:o,format:l,type:c,dfdFlags:h}}=e;if(i===\"error\")return Promise.reject(n);let u;if(t.faceCount===6)u=new Fp(r,l,c);else{let d=r[0].mipmaps;u=t.layerCount>1?new Pp(d,a,o,t.layerCount,l,c):new Bn(d,a,o,l,c)}return u.minFilter=r[0].mipmaps.length===1?Ve:mi,u.magFilter=Ve,u.generateMipmaps=!1,u.needsUpdate=!0,u.colorSpace=r0(t),u.premultiplyAlpha=!!(1&h),u}async _createTexture(e,t={}){let i=(function(o){let l=new Uint8Array(o.buffer,o.byteOffset,li.length);if(l[0]!==li[0]||l[1]!==li[1]||l[2]!==li[2]||l[3]!==li[3]||l[4]!==li[4]||l[5]!==li[5]||l[6]!==li[6]||l[7]!==li[7]||l[8]!==li[8]||l[9]!==li[9]||l[10]!==li[10]||l[11]!==li[11])throw new Error(\"Missing KTX 2.0 identifier.\");let c={vkFormat:0,typeSize:1,pixelWidth:0,pixelHeight:0,pixelDepth:0,layerCount:0,faceCount:1,levelCount:0,supercompressionScheme:0,levels:[],dataFormatDescriptor:[{vendorId:0,descriptorType:0,versionNumber:2,colorModel:0,colorPrimaries:1,transferFunction:2,flags:0,texelBlockDimension:[0,0,0,0],bytesPlane:[0,0,0,0,0,0,0,0],samples:[]}],keyValue:{},globalData:null},h=17*Uint32Array.BYTES_PER_ELEMENT,u=new Ns(o,li.length,h,!0);c.vkFormat=u._nextUint32(),c.typeSize=u._nextUint32(),c.pixelWidth=u._nextUint32(),c.pixelHeight=u._nextUint32(),c.pixelDepth=u._nextUint32(),c.layerCount=u._nextUint32(),c.faceCount=u._nextUint32(),c.levelCount=u._nextUint32(),c.supercompressionScheme=u._nextUint32();let d=u._nextUint32(),p=u._nextUint32(),A=u._nextUint32(),g=u._nextUint32(),b=u._nextUint64(),m=u._nextUint64(),f=3*Math.max(c.levelCount,1)*8,y=new Ns(o,li.length+h,f,!0);for(let E=0,S=Math.max(c.levelCount,1);E{let A=new PA;await A.init(),p(A)})),c=await Vu);let h=[];for(let p=0;p>p),g=Math.max(1,o.pixelHeight>>p),b=o.pixelDepth?Math.max(1,o.pixelDepth>>p):0,m=o.levels[p],f,y;if(o.supercompressionScheme===0)f=m.levelData;else{if(o.supercompressionScheme!==2)throw new Error(\"THREE.KTX2Loader: Unsupported supercompressionScheme.\");f=c.decode(m.levelData,m.uncompressedByteLength)}y=Er[l]===Tt?new Float32Array(f.buffer,f.byteOffset,f.byteLength/Float32Array.BYTES_PER_ELEMENT):Er[l]===pt?new Uint16Array(f.buffer,f.byteOffset,f.byteLength/Uint16Array.BYTES_PER_ELEMENT):Er[l]===Oh||Er[l]===kh?new Uint32Array(f.buffer,f.byteOffset,f.byteLength/Uint32Array.BYTES_PER_ELEMENT):f,h.push({data:y,width:A,height:g,depth:b})}let u=o.levelCount===0||h.length>1,d;if($S.has(ju[l]))d=o.pixelDepth===0?new ss(h[0].data,o.pixelWidth,o.pixelHeight):new kc(h[0].data,o.pixelWidth,o.pixelHeight,o.pixelDepth),d.minFilter=u?qo:kt,d.magFilter=kt,d.generateMipmaps=o.levelCount===0;else{if(o.pixelDepth>0)throw new Error(\"THREE.KTX2Loader: Unsupported pixelDepth.\");d=new Bn(h,o.pixelWidth,o.pixelHeight),d.minFilter=u?mi:Ve,d.magFilter=Ve}return d.mipmaps=h,d.type=Er[l],d.format=ju[l],d.colorSpace=r0(o),d.needsUpdate=!0,Promise.resolve(d)})(i);let r=t,a=this.init().then(()=>this.workerPool.postMessage({type:\"transcode\",buffer:e,taskConfig:r},[e])).then(o=>this._createTextureFrom(o.data,i));return Hu.set(e,{promise:a}),a}dispose(){this.workerPool.dispose(),this.workerSourceURL&&URL.revokeObjectURL(this.workerSourceURL),qu--}};$n.BasisFormat={ETC1S:0,UASTC:1,UASTC_HDR:2},$n.TranscoderFormat={ETC1:0,ETC2:1,BC1:2,BC3:3,BC4:4,BC5:5,BC7_M6_OPAQUE_ONLY:6,BC7_M5:7,PVRTC1_4_RGB:8,PVRTC1_4_RGBA:9,ASTC_4x4:10,ATC_RGB:11,ATC_RGBA_INTERPOLATED_ALPHA:12,RGBA32:13,RGB565:14,BGR565:15,RGBA4444:16,BC6H:22,RGB_HALF:24,RGBA_HALF:25},$n.EngineFormat={RGBAFormat:_t,RGBA_ASTC_4x4_Format:js,RGB_BPTC_UNSIGNED_Format:Mc,RGBA_BPTC_Format:la,RGBA_ETC2_EAC_Format:Io,RGBA_PVRTC_4BPPV1_Format:oa,RGBA_S3TC_DXT5_Format:qs,RGB_ETC1_Format:_c,RGB_ETC2_Format:Mo,RGB_PVRTC_4BPPV1_Format:xc,RGBA_S3TC_DXT1_Format:Vs},$n.EngineType={UnsignedByteType:Me,HalfFloatType:pt,FloatType:Tt},$n.BasisWorker=function(){let s,e,t,i=_EngineFormat,n=_EngineType,r=_TranscoderFormat,a=_BasisFormat;self.addEventListener(\"message\",function(u){let d=u.data;switch(d.type){case\"init\":s=d.config,p=d.transcoderBinary,e=new Promise(A=>{t={wasmBinary:p,onRuntimeInitialized:A},BASIS(t)}).then(()=>{t.initializeBasis(),t.KTX2File===void 0&&console.warn(\"THREE.KTX2Loader: Please update Basis Universal transcoder.\")});break;case\"transcode\":e.then(()=>{try{let{faces:A,buffers:g,width:b,height:m,hasAlpha:f,format:y,type:v,dfdFlags:x}=(function(_){let w=new t.KTX2File(new Uint8Array(_));function B(){w.close(),w.delete()}if(!w.isValid())throw B(),new Error(\"THREE.KTX2Loader:\tInvalid or unsupported .ktx2 file\");let I;if(w.isUASTC())I=a.UASTC;else if(w.isETC1S())I=a.ETC1S;else{if(!w.isHDR())throw new Error(\"THREE.KTX2Loader: Unknown Basis encoding\");I=a.UASTC_HDR}let F=w.getWidth(),Q=w.getHeight(),R=w.getLayers()||1,U=w.getLevels(),O=w.getFaces(),k=w.getHasAlpha(),N=w.getDFDFlags(),{transcoderFormat:G,engineFormat:re,engineType:ce}=(function(se,K,J,Z){let ye=l[se];for(let Ce=0;Ce1?(ye=P.origWidth,Ce=P.origHeight):(ye=P.width,Ce=P.height);let H=new Uint8Array(w.getImageTranscodedSizeInBytes(J,S,0,G)),C=w.transcodeImage(H,J,S,se,G,0,-1,-1);if(ce===n.HalfFloatType&&(H=new Uint16Array(H.buffer,H.byteOffset,H.byteLength/Uint16Array.BYTES_PER_ELEMENT)),!C)throw B(),new Error(\"THREE.KTX2Loader: .transcodeImage failed.\");Z.push(H)}let E=h(Z);K.push({data:E,width:ye,height:Ce}),he.push(E.buffer)}fe.push({mipmaps:K,width:F,height:Q,format:re,type:ce})}return B(),{faces:fe,buffers:he,width:F,height:Q,hasAlpha:k,dfdFlags:N,format:re,type:ce}})(d.buffer);self.postMessage({type:\"transcode\",id:d.id,data:{faces:A,width:b,height:m,hasAlpha:f,format:y,type:v,dfdFlags:x}},g)}catch(A){console.error(A),self.postMessage({type:\"error\",id:d.id,error:A.message})}})}var p});let o=[{if:\"astcSupported\",basisFormat:[a.UASTC],transcoderFormat:[r.ASTC_4x4,r.ASTC_4x4],engineFormat:[i.RGBA_ASTC_4x4_Format,i.RGBA_ASTC_4x4_Format],engineType:[n.UnsignedByteType],priorityETC1S:1/0,priorityUASTC:1,needsPowerOfTwo:!1},{if:\"bptcSupported\",basisFormat:[a.ETC1S,a.UASTC],transcoderFormat:[r.BC7_M5,r.BC7_M5],engineFormat:[i.RGBA_BPTC_Format,i.RGBA_BPTC_Format],engineType:[n.UnsignedByteType],priorityETC1S:3,priorityUASTC:2,needsPowerOfTwo:!1},{if:\"dxtSupported\",basisFormat:[a.ETC1S,a.UASTC],transcoderFormat:[r.BC1,r.BC3],engineFormat:[i.RGBA_S3TC_DXT1_Format,i.RGBA_S3TC_DXT5_Format],engineType:[n.UnsignedByteType],priorityETC1S:4,priorityUASTC:5,needsPowerOfTwo:!1},{if:\"etc2Supported\",basisFormat:[a.ETC1S,a.UASTC],transcoderFormat:[r.ETC1,r.ETC2],engineFormat:[i.RGB_ETC2_Format,i.RGBA_ETC2_EAC_Format],engineType:[n.UnsignedByteType],priorityETC1S:1,priorityUASTC:3,needsPowerOfTwo:!1},{if:\"etc1Supported\",basisFormat:[a.ETC1S,a.UASTC],transcoderFormat:[r.ETC1],engineFormat:[i.RGB_ETC1_Format],engineType:[n.UnsignedByteType],priorityETC1S:2,priorityUASTC:4,needsPowerOfTwo:!1},{if:\"pvrtcSupported\",basisFormat:[a.ETC1S,a.UASTC],transcoderFormat:[r.PVRTC1_4_RGB,r.PVRTC1_4_RGBA],engineFormat:[i.RGB_PVRTC_4BPPV1_Format,i.RGBA_PVRTC_4BPPV1_Format],engineType:[n.UnsignedByteType],priorityETC1S:5,priorityUASTC:6,needsPowerOfTwo:!0},{if:\"bptcSupported\",basisFormat:[a.UASTC_HDR],transcoderFormat:[r.BC6H],engineFormat:[i.RGB_BPTC_UNSIGNED_Format],engineType:[n.HalfFloatType],priorityHDR:1,needsPowerOfTwo:!1},{basisFormat:[a.ETC1S,a.UASTC],transcoderFormat:[r.RGBA32,r.RGBA32],engineFormat:[i.RGBAFormat,i.RGBAFormat],engineType:[n.UnsignedByteType,n.UnsignedByteType],priorityETC1S:100,priorityUASTC:100,needsPowerOfTwo:!1},{basisFormat:[a.UASTC_HDR],transcoderFormat:[r.RGBA_HALF],engineFormat:[i.RGBAFormat],engineType:[n.HalfFloatType],priorityHDR:100,needsPowerOfTwo:!1}],l={[a.ETC1S]:o.filter(u=>u.basisFormat.includes(a.ETC1S)).sort((u,d)=>u.priorityETC1S-d.priorityETC1S),[a.UASTC]:o.filter(u=>u.basisFormat.includes(a.UASTC)).sort((u,d)=>u.priorityUASTC-d.priorityUASTC),[a.UASTC_HDR]:o.filter(u=>u.basisFormat.includes(a.UASTC_HDR)).sort((u,d)=>u.priorityHDR-d.priorityHDR)};function c(u){return u<=2||!(u&u-1)&&u!==0}function h(u){if(u.length===1)return u[0];let d=0;for(let g=0;g=this[Ua];--e){let t=this[gn][e];this[Ni].get(t)===0&&(this[Wu].delete(t),this[gn].splice(e,1))}}};var ZS=s=>{let e=new Map;for(let t of s.mappings)for(let i of t.variants)e.set(i,{material:null,gltfMaterialIndex:t.material});return e},UA=class{constructor(e){this.parser=e,this.name=\"KHR_materials_variants\"}afterRoot(e){let t=this.parser,i=t.json;if(i.extensions===void 0||i.extensions[this.name]===void 0)return null;let n=(r=>{let a=[],o=new Set;for(let l of r){let c=l,h=0;for(;o.has(c);)c=l+\".\"+ ++h;o.add(c),a.push(c)}return a})((i.extensions[this.name].variants||[]).map(r=>r.name));for(let r of e.scenes)r.traverse(a=>{let o=a;if(!o.material)return;let l=t.associations.get(o);if(l==null||l.meshes==null||l.primitives==null)return;let c=i.meshes[l.meshes].primitives[l.primitives].extensions;c&&c[this.name]&&(o.userData.variantMaterials=ZS(c[this.name]))});return e.userData.variants=n,Promise.resolve()}};var ex,l0;xt.DEFAULT_ANISOTROPY=4;var mn=new Map,Xu=new Map,c0,h0=new class extends Bi{constructor(s){super(s),this.decoderPath=\"\",this.decoderConfig={},this.decoderBinary=null,this.decoderPending=null,this.workerLimit=4,this.workerPool=[],this.workerNextTaskID=1,this.workerSourceURL=\"\",this.defaultAttributeIDs={position:\"POSITION\",normal:\"NORMAL\",color:\"COLOR\",uv:\"TEX_COORD\"},this.defaultAttributeTypes={position:\"Float32Array\",normal:\"Float32Array\",color:\"Float32Array\",uv:\"Float32Array\"}}setDecoderPath(s){return this.decoderPath=s,this}setDecoderConfig(s){return this.decoderConfig=s,this}setWorkerLimit(s){return this.workerLimit=s,this}load(s,e,t,i){let n=new Hi(this.manager);n.setPath(this.path),n.setResponseType(\"arraybuffer\"),n.setRequestHeader(this.requestHeader),n.setWithCredentials(this.withCredentials),n.load(s,r=>{this.parse(r,e,i)},t,i)}parse(s,e,t=()=>{}){this.decodeDracoFile(s,e,null,null,lt,t).catch(t)}decodeDracoFile(s,e,t,i,n=At,r=()=>{}){let a={attributeIDs:t||this.defaultAttributeIDs,attributeTypes:i||this.defaultAttributeTypes,useUniqueIDs:!!t,vertexColorSpace:n};return this.decodeGeometry(s,a).then(e).catch(r)}decodeGeometry(s,e){let t=JSON.stringify(e);if(Ou.has(s)){let o=Ou.get(s);if(o.key===t)return o.promise;if(s.byteLength===0)throw new Error(\"THREE.DRACOLoader: Unable to re-decode a buffer with different settings. Buffer has already been transferred.\")}let i,n=this.workerNextTaskID++,r=s.byteLength,a=this._getWorker(n,r).then(o=>(i=o,new Promise((l,c)=>{i._callbacks[n]={resolve:l,reject:c},i.postMessage({type:\"decode\",id:n,taskConfig:e,buffer:s},[s])}))).then(o=>this._createGeometry(o.geometry));return a.catch(()=>!0).then(()=>{i&&n&&this._releaseTask(i,n)}),Ou.set(s,{key:t,promise:a}),a}_createGeometry(s){let e=new ei;s.index&&e.setIndex(new gt(s.index.array,1));for(let t=0;t{t.load(s,i,void 0,n)})}preload(){return this._initDecoder(),this}_initDecoder(){if(this.decoderPending)return this.decoderPending;let s=typeof WebAssembly!=\"object\"||this.decoderConfig.type===\"js\",e=[];return s?e.push(this._loadLibrary(\"draco_decoder.js\",\"text\")):(e.push(this._loadLibrary(\"draco_wasm_wrapper.js\",\"text\")),e.push(this._loadLibrary(\"draco_decoder.wasm\",\"arraybuffer\"))),this.decoderPending=Promise.all(e).then(t=>{let i=t[0];s||(this.decoderConfig.wasmBinary=t[1]);let n=wS.toString(),r=[\"/* draco decoder */\",i,\"\",\"/* worker */\",n.substring(n.indexOf(\"{\")+1,n.lastIndexOf(\"}\"))].join(`\n`);this.workerSourceURL=URL.createObjectURL(new Blob([r]))}),this.decoderPending}_getWorker(s,e){return this._initDecoder().then(()=>{if(this.workerPool.lengthn._taskLoad?-1:1});let t=this.workerPool[this.workerPool.length-1];return t._taskCosts[s]=e,t._taskLoad+=e,t})}_releaseTask(s,e){s._taskLoad-=s._taskCosts[e],delete s._callbacks[e],delete s._taskCosts[e]}debug(){console.log(\"Task load: \",this.workerPool.map(s=>s._taskLoad))}dispose(){for(let s=0;s{let r=document.createElement(\"script\");document.body.appendChild(r),r.onload=i,r.onerror=n,r.async=!0,r.src=t})).then(()=>$f.ready).then(()=>$f))}static getMeshoptDecoderLocation(){return Ku}static initializeKTX2Loader(e){mo.detectSupport(e)}static get cache(){return mn}static clearCache(){mn.forEach((e,t)=>{this.delete(t)}),this[Wn].reset()}static has(e){return mn.has(e)}static async delete(e){if(!this.has(e))return;let t=mn.get(e);Xu.delete(e),mn.delete(e),(await t).dispose()}static hasFinishedLoading(e){return!!Xu.get(e)}constructor(e){super(),this[l0]=new vA().register(t=>new UA(t)),this[d0]=e,this[Rs].setDRACOLoader(h0),this[Rs].setKTX2Loader(mo)}get[(ex=Wn,l0=Rs,Wn)](){return this.constructor[Wn]}async preload(e,t,i=()=>{}){if(this[Rs].setWithCredentials(t.withCredentials),this.dispatchEvent({type:\"preload\",element:t,src:e}),!mn.has(e)){Ju!=null&&this[Rs].setMeshoptDecoder(await Ju);let n=((o,l,c=()=>{})=>{let h=u=>{let d=u.loaded/u.total;c(Math.max(0,Math.min(1,isFinite(d)?d:1)))};return new Promise((u,d)=>{l.load(o,u,h,d)})})(e,this[Rs],o=>{i(.8*o)}),r=this[d0],a=n.then(o=>r.prepare(o)).then(o=>(i(.9),new r(o))).catch(o=>(console.error(o),new r));mn.set(e,a)}await mn.get(e),Xu.set(e,!0),i&&i(1)}async load(e,t,i=()=>{}){await this.preload(e,t,i);let n=await mn.get(e),r=await n.clone();return this[Wn].retain(e),r.dispose=()=>{this[Wn].release(e)},r}};$t[ex]=new FA($t);var NA=class extends ct{constructor(e=document.createElement(\"div\")){super(),this.isCSS2DObject=!0,this.element=e,this.element.style.position=\"absolute\",this.element.style.userSelect=\"none\",this.element.setAttribute(\"draggable\",!1),this.center=new pe(.5,.5),this.addEventListener(\"removed\",function(){this.traverse(function(t){t.element&&t.element instanceof t.element.ownerDocument.defaultView.Element&&t.element.parentNode!==null&&t.element.remove()})})}copy(e,t){return super.copy(e,t),this.element=e.element.cloneNode(!0),this.center=e.center,this}},Sr=new T,p0=new Re,A0=new Re,g0=new T,m0=new T,OA=class{constructor(e={}){let t=this,i,n,r,a,o={objects:new WeakMap},l=e.element!==void 0?e.element:document.createElement(\"div\");function c(d){d.isCSS2DObject&&(d.element.style.display=\"none\");for(let p=0,A=d.children.length;p=-1&&Sr.z<=1&&d.layers.test(A.layers)===!0,b=d.element;b.style.display=g===!0?\"\":\"none\",g===!0&&(d.onBeforeRender(t,p,A),b.style.transform=\"translate(\"+-100*d.center.x+\"%,\"+-100*d.center.y+\"%)translate(\"+(Sr.x*r+r)+\"px,\"+(-Sr.y*a+a)+\"px)\",b.parentNode!==l&&l.appendChild(b),d.onAfterRender(t,p,A));let m={distanceToCameraSquared:u(A,d)};o.objects.set(d,m)}for(let g=0,b=d.children.length;g{let{geometry:a}=r;if(a!==void 0){let{position:o}=a.attributes;if(o!==void 0)for(let l=0,c=o.count;l\"u\"&&typeof OffscreenCanvas<\"u\"?new OffscreenCanvas(1,1):document.createElement(\"canvas\")}var kA=class{constructor(){this.plugins=[],this.options={},this.pending=[],this.buffers=[],this.byteOffset=0,this.buffers=[],this.nodeMap=new Map,this.skins=[],this.extensionsUsed={},this.extensionsRequired={},this.uids=new Map,this.uid=0,this.json={asset:{version:\"2.0\",generator:\"THREE.GLTFExporter r\"+yc}},this.cache={meshes:new Map,attributes:new Map,attributesNormalized:new Map,materials:new Map,textures:new Map,images:new Map},this.textureUtils=null}setPlugins(e){this.plugins=e}setTextureUtils(e){this.textureUtils=e}async writeAsync(e,t,i={}){this.options=Object.assign({binary:!1,trs:!1,onlyVisible:!0,maxTextureSize:1/0,animations:[],includeCustomExtensions:!1},i),this.options.animations.length>0&&(this.options.trs=!0),await this.processInputAsync(e),await Promise.all(this.pending);let n=this,r=n.buffers,a=n.json;i=n.options;let o=n.extensionsUsed,l=n.extensionsRequired,c=new Blob(r,{type:\"application/octet-stream\"}),h=Object.keys(o),u=Object.keys(l);if(h.length>0&&(a.extensionsUsed=h),u.length>0&&(a.extensionsRequired=u),a.buffers&&a.buffers.length>0&&(a.buffers[0].byteLength=c.size),i.binary===!0){let d=new FileReader;d.readAsArrayBuffer(c),d.onloadend=function(){let p=nd(d.result),A=new DataView(new ArrayBuffer(8));A.setUint32(0,p.byteLength,!0),A.setUint32(4,5130562,!0);let g=nd((b=JSON.stringify(a),new TextEncoder().encode(b).buffer),32);var b;let m=new DataView(new ArrayBuffer(8));m.setUint32(0,g.byteLength,!0),m.setUint32(4,1313821514,!0);let f=new ArrayBuffer(12),y=new DataView(f);y.setUint32(0,1179937895,!0),y.setUint32(4,2,!0);let v=12+m.byteLength+g.byteLength+A.byteLength+p.byteLength;y.setUint32(8,v,!0);let x=new Blob([f,m,g,A,p],{type:\"application/octet-stream\"}),_=new FileReader;_.readAsArrayBuffer(x),_.onloadend=function(){t(_.result)}}}else if(a.buffers&&a.buffers.length>0){let d=new FileReader;d.readAsDataURL(c),d.onloadend=function(){let p=d.result;a.buffers[0].uri=p,t(a)}}else t(a)}serializeUserData(e,t){if(Object.keys(e.userData).length===0)return;let i=this.options,n=this.extensionsUsed;try{let r=JSON.parse(JSON.stringify(e.userData));if(i.includeCustomExtensions&&r.gltfExtensions){t.extensions===void 0&&(t.extensions={});for(let a in r.gltfExtensions)t.extensions[a]=r.gltfExtensions[a],n[a]=!0;delete r.gltfExtensions}Object.keys(r).length>0&&(t.extras=r)}catch(r){console.warn(\"THREE.GLTFExporter: userData of '\"+e.name+\"' won't be serialized because of JSON.stringify error - \"+r.message)}}getUID(e,t=!1){if(this.uids.has(e)===!1){let i=new Map;i.set(!0,this.uid++),i.set(!1,this.uid++),this.uids.set(e,i)}return this.uids.get(e).get(t)}isNormalizedNormalAttribute(e){if(this.cache.attributesNormalized.has(e))return!1;let t=new T;for(let i=0,n=e.count;i5e-4)return!1;return!0}createNormalizedNormalAttribute(e){let t=this.cache;if(t.attributesNormalized.has(e))return t.attributesNormalized.get(e);let i=e.clone(),n=new T;for(let r=0,a=i.count;r4?g=e.array[p*e.itemSize+A]:(A===0?g=e.getX(p):A===1?g=e.getY(p):A===2?g=e.getZ(p):A===3&&(g=e.getW(p)),e.normalized===!0&&(g=lc.normalize(g,e.array))),t===x0?h.setFloat32(u,g,!0):t===v0?h.setInt32(u,g,!0):t===y0?h.setUint32(u,g,!0):t===Zu?h.setInt16(u,g,!0):t===ed?h.setUint16(u,g,!0):t===Yu?h.setInt8(u,g):t===$u&&h.setUint8(u,g),u+=o}u%l!==0&&(u+=l-u%l)}let d={buffer:this.processBuffer(h.buffer),byteOffset:this.byteOffset,byteLength:c};return r!==void 0&&(d.target=r),r===td&&(d.byteStride=l),this.byteOffset+=c,a.bufferViews.push(d),{id:a.bufferViews.length-1,byteLength:0}}processBufferViewImage(e){let t=this,i=t.json;return i.bufferViews||(i.bufferViews=[]),new Promise(function(n){let r=new FileReader;r.readAsArrayBuffer(e),r.onloadend=function(){let a=nd(r.result),o={buffer:t.processBuffer(a),byteOffset:t.byteOffset,byteLength:a.byteLength};t.byteOffset+=a.byteLength,n(i.bufferViews.push(o)-1)}})}processAccessor(e,t,i,n){let r=this.json,a;if(e.array.constructor===Float32Array)a=x0;else if(e.array.constructor===Int32Array)a=v0;else if(e.array.constructor===Uint32Array)a=y0;else if(e.array.constructor===Int16Array)a=Zu;else if(e.array.constructor===Uint16Array)a=ed;else if(e.array.constructor===Int8Array)a=Yu;else{if(e.array.constructor!==Uint8Array)throw new Error(\"THREE.GLTFExporter: Unsupported bufferAttribute component type: \"+e.array.constructor.name);a=$u}if(i===void 0&&(i=0),n!==void 0&&n!==1/0||(n=e.count),n===0)return null;let o=(function(u,d,p){let A={min:new Array(u.itemSize).fill(Number.POSITIVE_INFINITY),max:new Array(u.itemSize).fill(Number.NEGATIVE_INFINITY)};for(let g=d;g4?m=u.array[g*u.itemSize+b]:(b===0?m=u.getX(g):b===1?m=u.getY(g):b===2?m=u.getZ(g):b===3&&(m=u.getW(g)),u.normalized===!0&&(m=lc.normalize(m,u.array))),A.min[b]=Math.min(A.min[b],m),A.max[b]=Math.max(A.max[b],m)}return A})(e,i,n),l;t!==void 0&&(l=e===t.index?sC:td);let c=this.processBufferView(e,a,i,n,l),h={bufferView:c.id,byteOffset:c.byteOffset,componentType:a,count:n,max:o.max,min:o.min,type:{1:\"SCALAR\",2:\"VEC2\",3:\"VEC3\",4:\"VEC4\",9:\"MAT3\",16:\"MAT4\"}[e.itemSize]};return e.normalized===!0&&(h.normalized=!0),r.accessors||(r.accessors=[]),r.accessors.push(h)-1}processImage(e,t,i,n=\"image/png\"){if(e!==null){let r=this,a=r.cache,o=r.json,l=r.options,c=r.pending;a.images.has(e)||a.images.set(e,{});let h=a.images.get(e),u=n+\":flipY/\"+i.toString();if(h[u]!==void 0)return h[u];o.images||(o.images=[]);let d={mimeType:n},p=E0();p.width=Math.min(e.width,l.maxTextureSize),p.height=Math.min(e.height,l.maxTextureSize);let A=p.getContext(\"2d\",{willReadFrequently:!0});if(i===!0&&(A.translate(0,p.height),A.scale(1,-1)),e.data!==void 0){t!==_t&&console.error(\"GLTFExporter: Only RGBAFormat is supported.\",t),(e.width>l.maxTextureSize||e.height>l.maxTextureSize)&&console.warn(\"GLTFExporter: Image size is bigger than maxTextureSize\",e);let b=new Uint8ClampedArray(e.height*e.width*4);for(let m=0;mb.toBlob(f,m))})(p,n).then(b=>r.processBufferViewImage(b)).then(b=>{d.bufferView=b})):d.uri=Nc.getDataURL(p,n);let g=o.images.push(d)-1;return h[u]=g,g}throw new Error(\"THREE.GLTFExporter: No valid image data found. Unable to process texture.\")}processSampler(e){let t=this.json;t.samplers||(t.samplers=[]);let i={magFilter:gi[e.magFilter],minFilter:gi[e.minFilter],wrapS:gi[e.wrapS],wrapT:gi[e.wrapT]};return t.samplers.push(i)-1}async processTextureAsync(e){let t=this.options,i=this.cache,n=this.json;if(i.textures.has(e))return i.textures.get(e);n.textures||(n.textures=[]),e instanceof Bn&&(e=await this.decompressTextureAsync(e,t.maxTextureSize));let r=e.userData.mimeType;r===\"image/webp\"&&(r=\"image/png\");let a={sampler:this.processSampler(e),source:this.processImage(e.image,e.format,e.flipY,r)};e.name&&(a.name=e.name),await this._invokeAllAsync(async function(l){l.writeTexture&&await l.writeTexture(e,a)});let o=n.textures.push(a)-1;return i.textures.set(e,o),o}async processMaterialAsync(e){let t=this.cache,i=this.json;if(t.materials.has(e))return t.materials.get(e);if(e.isShaderMaterial)return console.warn(\"GLTFExporter: THREE.ShaderMaterial not supported.\"),null;i.materials||(i.materials=[]);let n={pbrMetallicRoughness:{}};e.isMeshStandardMaterial!==!0&&e.isMeshBasicMaterial!==!0&&console.warn(\"GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.\");let r=e.color.toArray().concat([e.opacity]);if(fn(r,[1,1,1,1])||(n.pbrMetallicRoughness.baseColorFactor=r),e.isMeshStandardMaterial?(n.pbrMetallicRoughness.metallicFactor=e.metalness,n.pbrMetallicRoughness.roughnessFactor=e.roughness):(n.pbrMetallicRoughness.metallicFactor=0,n.pbrMetallicRoughness.roughnessFactor=1),e.metalnessMap||e.roughnessMap){let o=await this.buildMetalRoughTextureAsync(e.metalnessMap,e.roughnessMap),l={index:await this.processTextureAsync(o),texCoord:o.channel};this.applyTextureTransform(l,o),n.pbrMetallicRoughness.metallicRoughnessTexture=l}if(e.map){let o={index:await this.processTextureAsync(e.map),texCoord:e.map.channel};this.applyTextureTransform(o,e.map),n.pbrMetallicRoughness.baseColorTexture=o}if(e.emissive){let o=e.emissive;if(Math.max(o.r,o.g,o.b)>0&&(n.emissiveFactor=e.emissive.toArray()),e.emissiveMap){let l={index:await this.processTextureAsync(e.emissiveMap),texCoord:e.emissiveMap.channel};this.applyTextureTransform(l,e.emissiveMap),n.emissiveTexture=l}}if(e.normalMap){let o={index:await this.processTextureAsync(e.normalMap),texCoord:e.normalMap.channel};e.normalScale&&e.normalScale.x!==1&&(o.scale=e.normalScale.x),this.applyTextureTransform(o,e.normalMap),n.normalTexture=o}if(e.aoMap){let o={index:await this.processTextureAsync(e.aoMap),texCoord:e.aoMap.channel};e.aoMapIntensity!==1&&(o.strength=e.aoMapIntensity),this.applyTextureTransform(o,e.aoMap),n.occlusionTexture=o}e.transparent?n.alphaMode=\"BLEND\":e.alphaTest>0&&(n.alphaMode=\"MASK\",n.alphaCutoff=e.alphaTest),e.side===2&&(n.doubleSided=!0),e.name!==\"\"&&(n.name=e.name),this.serializeUserData(e,n),await this._invokeAllAsync(async function(o){o.writeMaterialAsync&&await o.writeMaterialAsync(e,n)});let a=i.materials.push(n)-1;return t.materials.set(e,a),a}async processMeshAsync(e){let t=this.cache,i=this.json,n=[e.geometry.uuid];if(Array.isArray(e.material))for(let v=0,x=e.material.length;v0){let v=[],x=[],_={};if(e.morphTargetDictionary!==void 0)for(let w in e.morphTargetDictionary)_[e.morphTargetDictionary[w]]=w;for(let w=0;w0&&(l.extras={},l.extras.targetNames=x)}let g=Array.isArray(e.material);if(g&&a.groups.length===0)return null;let b=!1;if(g&&a.index===null){let v=[];for(let x=0,_=a.attributes.position.count;x<_;x++)v[x]=x;a.setIndex(v),b=!0}let m=g?e.material:[e.material],f=g?a.groups:[{materialIndex:0,start:void 0,count:void 0}];for(let v=0,x=f.length;v0&&(_.targets=u),a.index!==null){let B=this.getUID(a.index);f[v].start===void 0&&f[v].count===void 0||(B+=\":\"+f[v].start+\":\"+f[v].count),t.attributes.has(B)?_.indices=t.attributes.get(B):(_.indices=this.processAccessor(a.index,a,f[v].start,f[v].count),t.attributes.set(B,_.indices)),_.indices===null&&delete _.indices}let w=await this.processMaterialAsync(m[f[v].materialIndex]);w!==null&&(_.material=w),h.push(_)}b===!0&&a.setIndex(null),l.primitives=h,i.meshes||(i.meshes=[]),await this._invokeAllAsync(function(v){v.writeMesh&&v.writeMesh(e,l)});let y=i.meshes.push(l)-1;return t.meshes.set(r,y),y}detectMeshQuantization(e,t){if(this.extensionsUsed[id])return;let i;switch(t.array.constructor){case Int8Array:i=\"byte\";break;case Uint8Array:i=\"unsigned byte\";break;case Int16Array:i=\"short\";break;case Uint16Array:i=\"unsigned short\";break;default:return}t.normalized&&(i+=\" normalized\");let n=e.split(\"_\",1)[0];f0[n]&&f0[n].includes(i)&&(this.extensionsUsed[id]=!0,this.extensionsRequired[id]=!0)}processCamera(e){let t=this.json;t.cameras||(t.cameras=[]);let i=e.isOrthographicCamera,n={type:i?\"orthographic\":\"perspective\"};return i?n.orthographic={xmag:2*e.right,ymag:2*e.top,zfar:e.far<=0?.001:e.far,znear:e.near<0?0:e.near}:n.perspective={aspectRatio:e.aspect,yfov:lc.degToRad(e.fov),zfar:e.far<=0?.001:e.far,znear:e.near<0?0:e.near},e.name!==\"\"&&(n.name=e.type),t.cameras.push(n)-1}processAnimation(e,t){let i=this.json,n=this.nodeMap;i.animations||(i.animations=[]);let r=(e=Xs.Utils.mergeMorphTargetTracks(e.clone(),t)).tracks,a=[],o=[];for(let c=0;c0){let o=[];for(let l=0,c=e.children.length;l0&&(r.children=o)}return await this._invokeAllAsync(function(o){o.writeNode&&o.writeNode(e,r)}),a}async _processNodeWithPivotAsync(e){let t=this.json,i=this.options,n=this.nodeMap,r=e.pivot,a={},o=e.quaternion.toArray(),l=[e.position.x+r.x,e.position.y+r.y,e.position.z+r.z],c=e.scale.toArray();fn(o,[0,0,0,1])||(a.rotation=o),fn(l,[0,0,0])||(a.translation=l),fn(c,[1,1,1])||(a.scale=c),a.extras={pivot:r.toArray()},e.name!==\"\"&&(a.name=String(e.name)),this.serializeUserData(e,a);let h=t.nodes.push(a)-1;n.set(e,h);let u={},d=[-r.x,-r.y,-r.z];if(fn(d,[0,0,0])||(u.translation=d),e.isMesh||e.isLine||e.isPoints){let A=await this.processMeshAsync(e);A!==null&&(u.mesh=A)}else e.isCamera&&(u.camera=this.processCamera(e));e.isSkinnedMesh&&this.skins.push(e);let p=[t.nodes.push(u)-1];if(e.children.length>0){let A=[];for(let g=0,b=e.children.length;g0&&(u.children=A)}return a.children=p,await this._invokeAllAsync(function(A){A.writeNode&&A.writeNode(e,a)}),h}async processSceneAsync(e){let t=this.json,i=this.options;t.scenes||(t.scenes=[],t.scene=0);let n={};e.name!==\"\"&&(n.name=e.name),t.scenes.push(n);let r=[];for(let a=0,o=e.children.length;a0&&(n.nodes=r),this.serializeUserData(e,n)}async processObjectsAsync(e){let t=new rn;t.name=\"AuxScene\";for(let i=0;i0&&await this.processObjectsAsync(i);for(let n=0;n0&&(a.range=e.distance)):e.isSpotLight&&(a.type=\"spot\",e.distance>0&&(a.range=e.distance),a.spot={},a.spot.innerConeAngle=(1-e.penumbra)*e.angle,a.spot.outerConeAngle=e.angle),e.decay!==void 0&&e.decay!==2&&console.warn(\"THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, and expects light.decay=2.\"),!e.target||e.target.parent===e&&e.target.position.x===0&&e.target.position.y===0&&e.target.position.z===-1||console.warn(\"THREE.GLTFExporter: Light direction may be lost. For best results, make light.target a child of the light with position 0,0,-1.\"),r[this.name]||(n.extensions=n.extensions||{},n.extensions[this.name]={lights:[]},r[this.name]=!0);let o=n.extensions[this.name].lights;o.push(a),t.extensions=t.extensions||{},t.extensions[this.name]={light:o.length-1}}},GA=class{constructor(e){this.writer=e,this.name=\"KHR_materials_unlit\"}async writeMaterialAsync(e,t){if(!e.isMeshBasicMaterial)return;let i=this.writer.extensionsUsed;t.extensions=t.extensions||{},t.extensions[this.name]={},i[this.name]=!0,t.pbrMetallicRoughness.metallicFactor=0,t.pbrMetallicRoughness.roughnessFactor=.9}},zA=class{constructor(e){this.writer=e,this.name=\"KHR_materials_clearcoat\"}async writeMaterialAsync(e,t){if(!e.isMeshPhysicalMaterial||e.clearcoat===0)return;let i=this.writer,n=i.extensionsUsed,r={};if(r.clearcoatFactor=e.clearcoat,e.clearcoatMap){let a={index:await i.processTextureAsync(e.clearcoatMap),texCoord:e.clearcoatMap.channel};i.applyTextureTransform(a,e.clearcoatMap),r.clearcoatTexture=a}if(r.clearcoatRoughnessFactor=e.clearcoatRoughness,e.clearcoatRoughnessMap){let a={index:await i.processTextureAsync(e.clearcoatRoughnessMap),texCoord:e.clearcoatRoughnessMap.channel};i.applyTextureTransform(a,e.clearcoatRoughnessMap),r.clearcoatRoughnessTexture=a}if(e.clearcoatNormalMap){let a={index:await i.processTextureAsync(e.clearcoatNormalMap),texCoord:e.clearcoatNormalMap.channel};e.clearcoatNormalScale.x!==1&&(a.scale=e.clearcoatNormalScale.x),i.applyTextureTransform(a,e.clearcoatNormalMap),r.clearcoatNormalTexture=a}t.extensions=t.extensions||{},t.extensions[this.name]=r,n[this.name]=!0}},HA=class{constructor(e){this.writer=e,this.name=\"KHR_materials_dispersion\"}async writeMaterialAsync(e,t){if(!e.isMeshPhysicalMaterial||e.dispersion===0)return;let i=this.writer.extensionsUsed,n={};n.dispersion=e.dispersion,t.extensions=t.extensions||{},t.extensions[this.name]=n,i[this.name]=!0}},VA=class{constructor(e){this.writer=e,this.name=\"KHR_materials_iridescence\"}async writeMaterialAsync(e,t){if(!e.isMeshPhysicalMaterial||e.iridescence===0)return;let i=this.writer,n=i.extensionsUsed,r={};if(r.iridescenceFactor=e.iridescence,e.iridescenceMap){let a={index:await i.processTextureAsync(e.iridescenceMap),texCoord:e.iridescenceMap.channel};i.applyTextureTransform(a,e.iridescenceMap),r.iridescenceTexture=a}if(r.iridescenceIor=e.iridescenceIOR,r.iridescenceThicknessMinimum=e.iridescenceThicknessRange[0],r.iridescenceThicknessMaximum=e.iridescenceThicknessRange[1],e.iridescenceThicknessMap){let a={index:await i.processTextureAsync(e.iridescenceThicknessMap),texCoord:e.iridescenceThicknessMap.channel};i.applyTextureTransform(a,e.iridescenceThicknessMap),r.iridescenceThicknessTexture=a}t.extensions=t.extensions||{},t.extensions[this.name]=r,n[this.name]=!0}},qA=class{constructor(e){this.writer=e,this.name=\"KHR_materials_transmission\"}async writeMaterialAsync(e,t){if(!e.isMeshPhysicalMaterial||e.transmission===0)return;let i=this.writer,n=i.extensionsUsed,r={};if(r.transmissionFactor=e.transmission,e.transmissionMap){let a={index:await i.processTextureAsync(e.transmissionMap),texCoord:e.transmissionMap.channel};i.applyTextureTransform(a,e.transmissionMap),r.transmissionTexture=a}t.extensions=t.extensions||{},t.extensions[this.name]=r,n[this.name]=!0}},jA=class{constructor(e){this.writer=e,this.name=\"KHR_materials_volume\"}async writeMaterialAsync(e,t){if(!e.isMeshPhysicalMaterial||e.transmission===0)return;let i=this.writer,n=i.extensionsUsed,r={};if(r.thicknessFactor=e.thickness,e.thicknessMap){let a={index:await i.processTextureAsync(e.thicknessMap),texCoord:e.thicknessMap.channel};i.applyTextureTransform(a,e.thicknessMap),r.thicknessTexture=a}e.attenuationDistance!==1/0&&(r.attenuationDistance=e.attenuationDistance),r.attenuationColor=e.attenuationColor.toArray(),t.extensions=t.extensions||{},t.extensions[this.name]=r,n[this.name]=!0}},WA=class{constructor(e){this.writer=e,this.name=\"KHR_materials_ior\"}async writeMaterialAsync(e,t){if(!e.isMeshPhysicalMaterial||e.ior===1.5)return;let i=this.writer.extensionsUsed,n={};n.ior=e.ior,t.extensions=t.extensions||{},t.extensions[this.name]=n,i[this.name]=!0}},XA=class{constructor(e){this.writer=e,this.name=\"KHR_materials_specular\"}async writeMaterialAsync(e,t){if(!e.isMeshPhysicalMaterial||e.specularIntensity===1&&e.specularColor.equals(AC)&&!e.specularIntensityMap&&!e.specularColorMap)return;let i=this.writer,n=i.extensionsUsed,r={};if(e.specularIntensityMap){let a={index:await i.processTextureAsync(e.specularIntensityMap),texCoord:e.specularIntensityMap.channel};i.applyTextureTransform(a,e.specularIntensityMap),r.specularTexture=a}if(e.specularColorMap){let a={index:await i.processTextureAsync(e.specularColorMap),texCoord:e.specularColorMap.channel};i.applyTextureTransform(a,e.specularColorMap),r.specularColorTexture=a}r.specularFactor=e.specularIntensity,r.specularColorFactor=e.specularColor.toArray(),t.extensions=t.extensions||{},t.extensions[this.name]=r,n[this.name]=!0}},KA=class{constructor(e){this.writer=e,this.name=\"KHR_materials_sheen\"}async writeMaterialAsync(e,t){if(!e.isMeshPhysicalMaterial||e.sheen==0)return;let i=this.writer,n=i.extensionsUsed,r={};if(e.sheenRoughnessMap){let a={index:await i.processTextureAsync(e.sheenRoughnessMap),texCoord:e.sheenRoughnessMap.channel};i.applyTextureTransform(a,e.sheenRoughnessMap),r.sheenRoughnessTexture=a}if(e.sheenColorMap){let a={index:await i.processTextureAsync(e.sheenColorMap),texCoord:e.sheenColorMap.channel};i.applyTextureTransform(a,e.sheenColorMap),r.sheenColorTexture=a}r.sheenRoughnessFactor=e.sheenRoughness,r.sheenColorFactor=e.sheenColor.toArray(),t.extensions=t.extensions||{},t.extensions[this.name]=r,n[this.name]=!0}},JA=class{constructor(e){this.writer=e,this.name=\"KHR_materials_anisotropy\"}async writeMaterialAsync(e,t){if(!e.isMeshPhysicalMaterial||e.anisotropy==0)return;let i=this.writer,n=i.extensionsUsed,r={};if(e.anisotropyMap){let a={index:await i.processTextureAsync(e.anisotropyMap)};i.applyTextureTransform(a,e.anisotropyMap),r.anisotropyTexture=a}r.anisotropyStrength=e.anisotropy,r.anisotropyRotation=e.anisotropyRotation,t.extensions=t.extensions||{},t.extensions[this.name]=r,n[this.name]=!0}},YA=class{constructor(e){this.writer=e,this.name=\"KHR_materials_emissive_strength\"}async writeMaterialAsync(e,t){if(!e.isMeshStandardMaterial||e.emissiveIntensity===1)return;let i=this.writer.extensionsUsed,n={};n.emissiveStrength=e.emissiveIntensity,t.extensions=t.extensions||{},t.extensions[this.name]=n,i[this.name]=!0}},$A=class{constructor(e){this.writer=e,this.name=\"EXT_materials_bump\"}async writeMaterialAsync(e,t){if(!e.isMeshStandardMaterial||e.bumpScale===1&&!e.bumpMap)return;let i=this.writer,n=i.extensionsUsed,r={};if(e.bumpMap){let a={index:await i.processTextureAsync(e.bumpMap),texCoord:e.bumpMap.channel};i.applyTextureTransform(a,e.bumpMap),r.bumpTexture=a}r.bumpFactor=e.bumpScale,t.extensions=t.extensions||{},t.extensions[this.name]=r,n[this.name]=!0}},ZA=class{constructor(e){this.writer=e,this.name=\"EXT_mesh_gpu_instancing\"}writeNode(e,t){if(!e.isInstancedMesh)return;let i=this.writer,n=e,r=new Float32Array(3*n.count),a=new Float32Array(4*n.count),o=new Float32Array(3*n.count),l=new Re,c=new T,h=new Kt,u=new T;for(let p=0;ps.times[s.times.length-1]){if(Math.abs(s.times[s.times.length-1]-e)<.001)return s.times.length-1;n[n.length-1]=e,n.set(s.times,0),r.set(s.values,0),r.set(a.evaluate(e),s.values.length),o=n.length-1}else for(let l=0;le){n.set(s.times.slice(0,l+1),0),n[l+1]=e,n.set(s.times.slice(l+1),l+2),r.set(s.values.slice(0,(l+1)*i),0),r.set(a.evaluate(e),(l+1)*i),r.set(s.values.slice((l+1)*i),(l+2)*i),o=l+1;break}}return s.times=n,s.values=r,o},mergeMorphTargetTracks:function(s,e){let t=[],i={},n=s.tracks;for(let r=0;rs.material!==void 0&&s.userData&&s.userData.variantMaterials&&!!Array.from(s.userData.variantMaterials.values()).filter(e=>uc(e.material)),uc=s=>s&&s.isMaterial&&!Array.isArray(s),eg=class{constructor(e){this.writer=e,this.name=\"KHR_materials_variants\",this.variantNames=[]}beforeParse(e){let t=new Set,i=n=>{if(!S0(n))return;let r=n.userData.variantMaterials,a=n.userData.variantData;for(let[o,l]of a){let c=r.get(l.index);c&&uc(c.material)&&t.add(o)}};if(Array.isArray(e))for(let n of e)n.traverse(i);else e.traverse(i);t.forEach(n=>this.variantNames.push(n))}async writeMesh(e,t){if(!S0(e))return;let i=e.userData,n=i.variantMaterials,r=i.variantData,a=new Map,o=new Map,l=Array.from(r.values()).sort((u,d)=>u.index-d.index);for(let[u,d]of l.entries())o.set(d.index,u);for(let u of r.values()){let d=n.get(u.index);if(!d||!uc(d.material))continue;let p=await this.writer.processMaterialAsync(d.material);a.has(p)||a.set(p,{material:p,variants:[]}),a.get(p).variants.push(o.get(u.index))}let c=Array.from(a.values()).map(u=>u.variants.sort((d,p)=>d-p)&&u).sort((u,d)=>u.material-d.material);if(c.length===0)return;let h=uc(i.originalMaterial)?await this.writer.processMaterialAsync(i.originalMaterial):-1;for(let u of t.primitives)h>=0&&(u.material=h),u.extensions=u.extensions||{},u.extensions[this.name]={mappings:c}}afterParse(){if(this.variantNames.length===0)return;let e=this.writer.json;e.extensions=e.extensions||{};let t=this.variantNames.map(i=>({name:i}));e.extensions[this.name]={variants:t},this.writer.extensionsUsed[this.name]=!0}},tg=class{constructor(e,t,i,n,r){this.xrLight=e,this.renderer=t,this.lightProbe=i,this.xrWebGLBinding=null,this.estimationStartCallback=r,this.frameCallback=this.onXRFrame.bind(this);let a=t.xr.getSession();if(n&&\"XRWebGLBinding\"in window){let o=new fa(16);e.environment=o.texture;let l=t.getContext();switch(a.preferredReflectionFormat){case\"srgba8\":l.getExtension(\"EXT_sRGB\");break;case\"rgba16f\":l.getExtension(\"OES_texture_half_float\")}this.xrWebGLBinding=new XRWebGLBinding(a,l),this.lightProbe.addEventListener(\"reflectionchange\",()=>{this.updateReflection()})}a.requestAnimationFrame(this.frameCallback)}updateReflection(){let e=this.renderer.properties.get(this.xrLight.environment);if(e){let t=this.xrWebGLBinding.getReflectionCubeMap(this.lightProbe);t&&(e.__webglTexture=t,this.xrLight.environment.needsPMREMUpdate=!0)}}onXRFrame(e,t){if(!this.xrLight)return;t.session.requestAnimationFrame(this.frameCallback);let i=t.getLightEstimate(this.lightProbe);if(i){this.xrLight.lightProbe.sh.fromArray(i.sphericalHarmonicsCoefficients),this.xrLight.lightProbe.intensity=1;let n=Math.max(1,Math.max(i.primaryLightIntensity.x,Math.max(i.primaryLightIntensity.y,i.primaryLightIntensity.z)));this.xrLight.directionalLight.color.setRGB(i.primaryLightIntensity.x/n,i.primaryLightIntensity.y/n,i.primaryLightIntensity.z/n),this.xrLight.directionalLight.intensity=n,this.xrLight.directionalLight.position.copy(i.primaryLightDirection),this.estimationStartCallback&&(this.estimationStartCallback(),this.estimationStartCallback=null)}}dispose(){this.xrLight=null,this.renderer=null,this.lightProbe=null,this.xrWebGLBinding=null}},ig=class extends tn{constructor(e,t=!0){super(),this.lightProbe=new nA,this.lightProbe.intensity=0,this.add(this.lightProbe),this.directionalLight=new hh,this.directionalLight.intensity=0,this.add(this.directionalLight),this.environment=null;let i=null,n=!1;e.xr.addEventListener(\"sessionstart\",()=>{let r=e.xr.getSession();\"requestLightProbe\"in r&&r.requestLightProbe({reflectionFormat:r.preferredReflectionFormat}).then(a=>{i=new tg(this,e,a,t,()=>{n=!0,this.dispatchEvent({type:\"estimationstart\"})})})}),e.xr.addEventListener(\"sessionend\",()=>{i&&(i.dispose(),i=null),n&&this.dispatchEvent({type:\"estimationend\"})}),this.dispose=()=>{i&&(i.dispose(),i=null),this.remove(this.lightProbe),this.lightProbe=null,this.remove(this.directionalLight),this.directionalLight=null,this.environment=null}}};var Ot=class{constructor(e=50){this.velocity=0,this.naturalFrequency=0,this.setDecayTime(e)}setDecayTime(e){this.naturalFrequency=1/Math.max(.001,e)}update(e,t,i,n){let r=2e-4*this.naturalFrequency;if(e==null||n===0||e===t&&this.velocity===0)return t;if(i<0)return e;let a=e-t,o=this.velocity+this.naturalFrequency*a,l=a+i*o,c=Math.exp(-this.naturalFrequency*i),h=(o-this.naturalFrequency*l)*c,u=-this.naturalFrequency*(h+o*c);return Math.abs(h)=0?(this.velocity=0,t):(this.velocity=h,t+l*c)}};var Ze={MIN_TOUCH_AREA:.05,BASE_RADIUS:.15,LINE_WIDTH:.02,SEGMENTS:16,DELTA_PHI:Math.PI/32,COLORS:{EDGE_FALLOFF:new ve(.98,.98,.98),EDGE_CUTOFF:new ve(.8,.8,.8),FILL_FALLOFF:new ve(.4,.4,.4),FILL_CUTOFF:new ve(.4,.4,.4),ACTIVE_EDGE:new ve(1,1,1),ACTIVE_FILL:new ve(.6,.6,.6)},MAX_OPACITY:.75,FILL_OPACITY_MULTIPLIER:.5,INTERACTIVE_OPACITY_MULTIPLIER:1.2,MIN_DISTANCE:.5,MAX_DISTANCE:10,BASE_SCALE:1,DISTANCE_SCALE_FACTOR:.3,COLOR_LERP_FACTOR:.15,SCREEN_SPACE_SCALE:1.2,SIZE_UPDATE_THRESHOLD:.001,GEOMETRY_UPDATE_DEBOUNCE:100},C0=new pe,Rl=(s,e,t,i,n)=>{let r=e>0?t>0?0:-Math.PI/2:t>0?Math.PI/2:Math.PI;for(let a=0;a<=Ze.SEGMENTS;++a)s.push(e+(i-n)*Math.cos(r),t+(i-n)*Math.sin(r),0,e+i*Math.cos(r),t+i*Math.sin(r),0),r+=Ze.DELTA_PHI},ng=class extends Je{constructor(e,t){super(new ei),this.shadowHeight=0,this.isActive=!1,this.isHovered=!1,this.lastGeometryUpdateTime=0,this.needsGeometryUpdate=!1,this.scene=e,this.side=t,this.currentSize=new T,this.goalSize=new T,this.sizeDamper=new Ot,this.updateSizeFromScene(),this.edgeMaterial=new Xt({color:Ze.COLORS.EDGE_FALLOFF,transparent:!0,opacity:0,side:2,depthWrite:!1,blending:2}),this.fillMaterial=new Xt({color:Ze.COLORS.FILL_FALLOFF,transparent:!0,opacity:0,side:2,depthWrite:!1,blending:1}),this.material=this.edgeMaterial,this.goalOpacity=0,this.opacityDamper=new Ot,this.createHitMeshes(),this.updatePositionFromScene(),e.target.add(this),e.target.add(this.hitBox),this.offsetHeight=0}updateSizeFromScene(){let{size:e}=this.scene;this.goalSize.copy(e);let t=Math.sqrt(e.x*e.x+e.z*e.z),i=Math.max(Ze.MIN_TOUCH_AREA,.4*t);this.goalSize.x0}update(e,t){if(this.updateOpacity(e),!this.currentSize.equals(this.goalSize)){let n=new T;n.x=this.sizeDamper.update(this.currentSize.x,this.goalSize.x,e,1),n.y=this.sizeDamper.update(this.currentSize.y,this.goalSize.y,e,1),n.z=this.sizeDamper.update(this.currentSize.z,this.goalSize.z,e,1),n.distanceTo(this.currentSize)>Ze.SIZE_UPDATE_THRESHOLD&&(this.currentSize.copy(n),this.needsGeometryUpdate=!0)}let i=performance.now();this.needsGeometryUpdate&&i-this.lastGeometryUpdateTime>Ze.GEOMETRY_UPDATE_DEBOUNCE&&(this.updateGeometry(),this.updateHitMeshes(),this.needsGeometryUpdate=!1,this.lastGeometryUpdateTime=i),t&&this.applyDistanceScaling(t),this.updateVisualState()}getSize(){return this.goalSize.clone()}dispose(){let{geometry:e,material:t}=this.hitPlane;e.dispose(),t.dispose(),this.hitBox.geometry.dispose(),this.hitBox.material.dispose(),this.geometry.dispose(),this.edgeMaterial.dispose(),this.fillMaterial.dispose(),this.hitBox.removeFromParent(),this.removeFromParent()}};var si=(s,e)=>({type:\"number\",number:s,unit:e}),In=(()=>{let s={};return e=>{let t=e;if(t in s)return s[t];let i=[],n=0;for(;e;){if(++n>1e3){e=\"\";break}let r=ix(e),a=r.nodes[0];if(a==null||a.terms.length===0)break;i.push(a),e=r.remainingInput}return s[t]=i}})(),ix=(()=>{let s=/^(\\-\\-|[a-z\\u0240-\\uffff])/i,e=/^([\\*\\+\\/]|[\\-]\\s)/i,t=/^[\\),]/;return i=>{let n=[];for(;i.length&&(i=i.trim(),!t.test(i));)if(i[0]===\"(\"){let{nodes:r,remainingInput:a}=w0(i);i=a,n.push({type:\"function\",name:{type:\"ident\",value:\"calc\"},arguments:r})}else if(s.test(i)){let r=gC(i),a=r.nodes[0];if((i=r.remainingInput)[0]===\"(\"){let{nodes:o,remainingInput:l}=w0(i);n.push({type:\"function\",name:a,arguments:o}),i=l}else n.push(a)}else if(e.test(i))n.push({type:\"operator\",value:i[0]}),i=i.slice(1);else{let{nodes:r,remainingInput:a}=i[0]===\"#\"?fC(i):mC(i);if(r.length===0)break;n.push(r[0]),i=a}return{nodes:[{type:\"expression\",terms:n}],remainingInput:i}}})(),gC=(()=>{let s=/[^a-z0-9_\\-\\u0240-\\uffff]/i;return e=>{let t=e.match(s);return{nodes:[{type:\"ident\",value:t==null?e:e.substr(0,t.index)}],remainingInput:t==null?\"\":e.substr(t.index)}}})(),mC=(()=>{let s=/[\\+\\-]?(\\d+[\\.]\\d+|\\d+|[\\.]\\d+)([eE][\\+\\-]?\\d+)?/,e=/^[a-z%]+/i,t=/^(m|mm|cm|rad|deg|[%])$/;return i=>{let n=i.match(s),r=n==null?\"0\":n[0],a=(i=r==null?i:i.slice(r.length)).match(e),o=a!=null&&a[0]!==\"\"?a[0]:null,l=a==null?i:i.slice(o.length);return o==null||t.test(o)||(o=null),{nodes:[{type:\"number\",number:parseFloat(r)||0,unit:o}],remainingInput:l}}})(),fC=(()=>{let s=/^[a-f0-9]*/i;return e=>{let t=(e=e.slice(1).trim()).match(s);return{nodes:t==null?[]:[{type:\"hex\",value:t[0]}],remainingInput:t==null?e:e.slice(t[0].length)}}})(),w0=s=>{let e=[];for(s=s.slice(1).trim();s.length;){let t=ix(s);if(e.push(t.nodes[0]),(s=t.remainingInput.trim())[0]===\",\")s=s.slice(1).trim();else if(s[0]===\")\"){s=s.slice(1);break}}return{nodes:e,remainingInput:s}},M0=Symbol(\"visitedTypes\"),sg=class{constructor(e){this[M0]=e}walk(e,t){let i=e.slice();for(;i.length;){let n=i.shift();switch(this[M0].indexOf(n.type)>-1&&t(n),n.type){case\"expression\":i.unshift(...n.terms);break;case\"function\":i.unshift(n.name,...n.arguments)}}}},cs=Object.freeze({type:\"number\",number:0,unit:null}),Gh=(s,e=0)=>{let{number:t,unit:i}=s;if(isFinite(t)){if(s.unit===\"rad\"||s.unit==null)return s}else t=e,i=\"rad\";return{type:\"number\",number:(i===\"deg\"&&t!=null?t:0)*Math.PI/180,unit:\"rad\"}},I0=(s,e=0)=>{let t,{number:i,unit:n}=s;if(isFinite(i)){if(s.unit===\"m\")return s}else i=e,n=\"m\";switch(n){default:t=1;break;case\"cm\":t=.01;break;case\"mm\":t=.001}return{type:\"number\",number:t*i,unit:\"m\"}},nn=(()=>{let s=t=>t,e={rad:s,deg:Gh,m:s,mm:I0,cm:I0};return(t,i=cs)=>{isFinite(t.number)||(t.number=i.number,t.unit=i.unit);let{unit:n}=t;if(n==null)return t;let r=e[n];return r==null?i:r(t)}})();var nx,T0,B0,va=Symbol(\"evaluate\"),dc=Symbol(\"lastValue\"),It=class s{constructor(){this[nx]=null}static evaluatableFor(e,t=cs){if(e instanceof s)return e;if(e.type===\"number\")return e.unit===\"%\"?new rg(e,t):e;switch(e.name.value){case\"calc\":return new og(e,t);case\"env\":return new ag(e)}return cs}static evaluate(e){return e instanceof s?e.evaluate():e}static isConstant(e){return!(e instanceof s)||e.isConstant}static applyIntrinsics(e,t){let{basis:i,keywords:n}=t,{auto:r}=n;return i.map((a,o)=>{let l=r[o]==null?a:r[o],c=e[o]?e[o]:l;if(c.type===\"ident\"){let h=c.value;h in n&&(c=n[h][o])}return c!=null&&c.type!==\"ident\"||(c=l),c.unit===\"%\"?si(c.number/100*a.number,a.unit):(c=nn(c,a),c.unit!==a.unit?a:c)})}get isConstant(){return!1}evaluate(){return this.isConstant&&this[dc]!=null||(this[dc]=this[va]()),this[dc]}};nx=dc;var R0=Symbol(\"percentage\"),rd=Symbol(\"basis\"),rg=class extends It{constructor(e,t){super(),this[R0]=e,this[rd]=t}get isConstant(){return!0}[va](){return si(this[R0].number/100*this[rd].number,this[rd].unit)}},Dl=Symbol(\"identNode\"),ag=class extends It{constructor(e){super(),this[T0]=null;let t=e.arguments.length?e.arguments[0].terms[0]:null;t!=null&&t.type===\"ident\"&&(this[Dl]=t)}get isConstant(){return!1}[(T0=Dl,va)](){return this[Dl]!=null&&this[Dl].value===\"window-scroll-y\"?{type:\"number\",number:window.pageYOffset/(Math.max(document.body.scrollHeight,document.body.offsetHeight,document.documentElement.clientHeight,document.documentElement.scrollHeight,document.documentElement.offsetHeight)-window.innerHeight)||0,unit:null}:cs}},bC=/[\\*\\/]/,wr=Symbol(\"evaluator\"),og=class extends It{constructor(e,t=cs){if(super(),this[B0]=null,e.arguments.length!==1)return;let i=e.arguments[0].terms.slice(),n=[];for(;i.length;){let r=i.shift();if(n.length>0){let a=n[n.length-1];if(a.type===\"operator\"&&bC.test(a.value)){let o=n.pop(),l=n.pop();if(l==null)return;n.push(new fh(o,It.evaluatableFor(l,t),It.evaluatableFor(r,t)));continue}}n.push(r.type===\"operator\"?r:It.evaluatableFor(r,t))}for(;n.length>2;){let[r,a,o]=n.splice(0,3);if(a.type!==\"operator\")return;n.unshift(new fh(a,It.evaluatableFor(r,t),It.evaluatableFor(o,t)))}n.length===1&&(this[wr]=n[0])}get isConstant(){return this[wr]==null||It.isConstant(this[wr])}[(B0=wr,va)](){return this[wr]!=null?It.evaluate(this[wr]):cs}},D0=Symbol(\"operator\"),ad=Symbol(\"left\"),od=Symbol(\"right\"),fh=class extends It{constructor(e,t,i){super(),this[D0]=e,this[ad]=t,this[od]=i}get isConstant(){return It.isConstant(this[ad])&&It.isConstant(this[od])}[va](){let e=nn(It.evaluate(this[ad])),t=nn(It.evaluate(this[od])),{number:i,unit:n}=e,{number:r,unit:a}=t;if(a!=null&&n!=null&&a!=n)return cs;let o=n||a,l;switch(this[D0].value){case\"+\":l=i+r;break;case\"-\":l=i-r;break;case\"/\":l=i/r;break;case\"*\":l=i*r;break;default:return cs}return{type:\"number\",number:l,unit:o}}},ld=Symbol(\"evaluatables\"),L0=Symbol(\"intrinsics\"),bh=class extends It{constructor(e,t){super(),this[L0]=t;let i=e[0],n=i!=null?i.terms:[];this[ld]=t.basis.map((r,a)=>{let o=n[a];return o==null?{type:\"ident\",value:\"auto\"}:o.type===\"ident\"?o:It.evaluatableFor(o,r)})}get isConstant(){for(let e of this[ld])if(!It.isConstant(e))return!1;return!0}[va](){let e=this[ld].map(t=>It.evaluate(t));return It.applyIntrinsics(e,this[L0]).map(t=>t.number)}};var sx,rx,ax,ox,Mr=Symbol(\"instances\"),P0=Symbol(\"activateListener\"),F0=Symbol(\"deactivateListener\"),cd=Symbol(\"notifyInstances\"),U0=Symbol(\"notify\"),N0=Symbol(\"callback\"),vh=class s{static[cd](){for(let e of s[Mr])e[U0]()}static[(sx=Mr,P0)](){window.addEventListener(\"scroll\",this[cd],{passive:!0})}static[F0](){window.removeEventListener(\"scroll\",this[cd])}constructor(e){this[N0]=e}observe(){s[Mr].size===0&&s[P0](),s[Mr].add(this)}disconnect(){s[Mr].delete(this),s[Mr].size===0&&s[F0]()}[U0](){this[N0]()}};vh[sx]=new Set;var O0=Symbol(\"computeStyleCallback\"),lx=Symbol(\"astWalker\"),so=Symbol(\"dependencies\"),cx=Symbol(\"onScroll\"),lg=class{constructor(e){this[rx]={},this[ax]=new sg([\"function\"]),this[ox]=()=>{this[O0]({relatedState:\"window-scroll\"})},this[O0]=e}observeEffectsFor(e){let t={},i=this[so];this[lx].walk(e,n=>{let{name:r}=n,a=n.arguments[0].terms[0];if(r.value===\"env\"&&a!=null&&a.type===\"ident\"&&a.value===\"window-scroll-y\"&&t[\"window-scroll\"]==null){let o=\"window-scroll\"in i?i[\"window-scroll\"]:new vh(this[cx]);o.observe(),delete i[\"window-scroll\"],t[\"window-scroll\"]=o}});for(let n in i)i[n].disconnect();this[so]=t}dispose(){for(let e in this[so])this[so][e].disconnect()}};rx=so,ax=lx,ox=cx;var zn=s=>{let e=s.observeEffects||!1,t=s.intrinsics instanceof Function?s.intrinsics:()=>s.intrinsics;return(i,n)=>{let r=i.updated,a=i.connectedCallback,o=i.disconnectedCallback,l=Symbol(`${n}StyleEffector`),c=Symbol(`${n}StyleEvaluator`),h=Symbol(`${n}UpdateEvaluator`),u=Symbol(`${n}EvaluateAndSync`);Object.defineProperties(i,{[l]:{value:null,writable:!0},[c]:{value:null,writable:!0},[h]:{value:function(){let d=In(this[n]);this[c]=new bh(d,t(this)),this[l]==null&&e&&(this[l]=new lg(()=>this[u]())),this[l]!=null&&this[l].observeEffectsFor(d)}},[u]:{value:function(){if(this[c]==null)return;let d=this[c].evaluate();this[s.updateHandler](d)}},updated:{value:function(d){d.has(n)&&(this[h](),this[u]()),r.call(this,d)}},connectedCallback:{value:function(){a.call(this),this.requestUpdate(n,this[n])}},disconnectedCallback:{value:function(){o.call(this),this[l]!=null&&(this[l].dispose(),this[l]=null)}}})}},hx=s=>s<.5?2*s*s:(4-2*s)*s-1,vC=(s,e,t=hx)=>i=>s+(e-s)*t(i),Hr=s=>{let e=[],t=[],i=s.initialValue;for(let n=0;n{let a=r.map((o=0,l=>o+=l));var o;return l=>{l=en(l,0,1),l*=a[a.length-1];let c=a.findIndex(d=>d>=l),h=c<1?0:a[c-1],u=a[c];return n[c]((l-h)/(u-h))}})(e,t)};var Lt=function(s,e,t,i){for(var n,r=arguments.length,a=r<3?e:i===null?i=Object.getOwnPropertyDescriptor(e,t):i,o=s.length-1;o>=0;o--)(n=s[o])&&(a=(r<3?n(a):r>3?n(e,t,a):n(e,t))||a);return r>3&&a&&Object.defineProperty(e,t,a),a},yC=Hr({initialValue:0,keyframes:[{frames:5,value:-1},{frames:1,value:-1},{frames:8,value:1},{frames:1,value:1},{frames:5,value:0},{frames:18,value:0}]}),xC=Hr({initialValue:0,keyframes:[{frames:1,value:1},{frames:5,value:1},{frames:1,value:0},{frames:6,value:0}]}),ux=\"0deg 75deg 105%\",_C=[\"front\",\"right\",\"back\",\"left\"],EC=[\"upper-\",\"\",\"lower-\"],Ll=\"auto\",SC=\"basic\",k0=\"wiggle\",CC=\"none\",Q0=()=>({basis:[Gh(si(30,\"deg\"))],keywords:{auto:[null]}}),wC=()=>({basis:[Gh(si(12,\"deg\"))],keywords:{auto:[null]}}),dx=(()=>{let s=In(ux)[0].terms,e=nn(s[0]),t=nn(s[1]);return i=>{let n=i[Y].idealCameraDistance();return{basis:[e,t,si(n,\"m\")],keywords:{auto:[null,null,si(105,\"%\")]}}}})(),MC=s=>{let e=2.2*s[Y].boundingSphere.radius;return{basis:[si(-1/0,\"rad\"),si(0,\"rad\"),si(e,\"m\")],keywords:{auto:[null,null,null]}}},IC=s=>{let e=dx(s),t=new bh([],e).evaluate()[2];return{basis:[si(1/0,\"rad\"),si(Math.PI,\"rad\"),si(t,\"m\")],keywords:{auto:[null,null,null]}}},TC=s=>{s[Y].updateBoundingBoxAndShadowIfDirty();let e=s[Y].boundingBox.getCenter(new T);return{basis:[si(e.x,\"m\"),si(e.y,\"m\"),si(e.z,\"m\")],keywords:{auto:[null,null,null]}}},px=Math.PI/2,BC=Math.PI/3,RC=px/2,DC=2*Math.PI,st=Symbol(\"controls\"),pc=Symbol(\"panElement\"),hd=Symbol(\"promptElement\"),Pl=Symbol(\"promptAnimatedContainer\"),ud=Symbol(\"fingerAnimatedContainers\"),Fl=Symbol(\"deferInteractionPrompt\"),G0=Symbol(\"updateAria\"),bn=Symbol(\"a11y\"),z0=Symbol(\"updateA11y\"),H0=Symbol(\"updateCameraForRadius\"),Ir=Symbol(\"cancelPrompts\"),dd=Symbol(\"onChange\"),Oa=Symbol(\"onPointerChange\"),Ss=Symbol(\"waitingToPromptUser\"),Ul=Symbol(\"userHasInteracted\"),Tr=Symbol(\"promptElementVisibleTime\"),ka=Symbol(\"lastPromptOffset\"),Nl=Symbol(\"cancellationSource\"),pd=Symbol(\"lastSpherical\"),Qa=Symbol(\"jumpCamera\"),Ad=Symbol(\"initialized\"),Ga=Symbol(\"maintainThetaPhi\"),V0=Symbol(\"syncCameraOrbit\"),q0=Symbol(\"syncFieldOfView\"),j0=Symbol(\"syncCameraTarget\"),W0=Symbol(\"syncMinCameraOrbit\"),X0=Symbol(\"syncMaxCameraOrbit\"),K0=Symbol(\"syncMinFieldOfView\"),J0=Symbol(\"syncMaxFieldOfView\"),LC=new pe,Y0=new T,$0=Object.freeze({minimumRadius:0,maximumRadius:1/0,minimumPolarAngle:0,maximumPolarAngle:Math.PI,minimumAzimuthalAngle:-1/0,maximumAzimuthalAngle:1/0,minimumFieldOfView:10,maximumFieldOfView:45,touchAction:\"none\"}),Ol=Math.PI/8,gd=.04,St={USER_INTERACTION:\"user-interaction\",NONE:\"none\",AUTOMATIC:\"automatic\"},cg=class extends vi{constructor(e,t,i){super(),this.camera=e,this.element=t,this.scene=i,this.orbitSensitivity=1,this.zoomSensitivity=1,this.panSensitivity=1,this.inputSensitivity=1,this.changeSource=St.NONE,this._interactionEnabled=!1,this._disableZoom=!1,this.isUserPointing=!1,this.enablePan=!0,this.enableTap=!0,this.panProjection=new Pe,this.panPerPixel=0,this.spherical=new Jr,this.goalSpherical=new Jr,this.thetaDamper=new Ot,this.phiDamper=new Ot,this.radiusDamper=new Ot,this.logFov=Math.log($0.maximumFieldOfView),this.goalLogFov=this.logFov,this.fovDamper=new Ot,this.touchMode=null,this.pointers=[],this.startTime=0,this.startPointerPosition={clientX:0,clientY:0},this.lastSeparation=0,this.touchDecided=!1,this.onContext=n=>{if(this.enablePan)n.preventDefault();else for(let r of this.pointers)this.onPointerUp(new PointerEvent(\"pointercancel\",Object.assign(Object.assign({},this.startPointerPosition),{pointerId:r.id})))},this.touchModeZoom=(n,r)=>{if(!this._disableZoom){let a=this.twoTouchDistance(this.pointers[0],this.pointers[1]),o=gd*this.zoomSensitivity*(this.lastSeparation-a)*50/this.scene.height;this.lastSeparation=a,this.userAdjustOrbit(0,0,o)}this.panPerPixel>0&&this.movePan(n,r)},this.disableScroll=n=>{n.preventDefault()},this.touchModeRotate=(n,r)=>{let{touchAction:a}=this._options;if(!this.touchDecided&&a!==\"none\"){this.touchDecided=!0;let o=Math.abs(n),l=Math.abs(r);if(this.changeSource===St.USER_INTERACTION&&(a===\"pan-y\"&&l>o||a===\"pan-x\"&&o>l))return void(this.touchMode=null);this.element.addEventListener(\"touchmove\",this.disableScroll,{passive:!1})}this.handleSinglePointerMove(n,r)},this.onPointerDown=n=>{if(this.pointers.length>2)return;let{element:r}=this;this.pointers.length===0&&(r.addEventListener(\"pointermove\",this.onPointerMove),r.addEventListener(\"pointerup\",this.onPointerUp),this.touchMode=null,this.touchDecided=!1,this.startPointerPosition.clientX=n.clientX,this.startPointerPosition.clientY=n.clientY,this.startTime=performance.now());try{r.setPointerCapture(n.pointerId)}catch{}this.pointers.push({clientX:n.clientX,clientY:n.clientY,id:n.pointerId}),this.isUserPointing=!1,n.pointerType===\"touch\"?(this.changeSource=n.altKey?St.AUTOMATIC:St.USER_INTERACTION,this.onTouchChange(n)):(this.changeSource=St.USER_INTERACTION,this.onMouseDown(n)),this.changeSource===St.USER_INTERACTION&&this.dispatchEvent({type:\"user-interaction\"})},this.onPointerMove=n=>{let r=this.pointers.find(c=>c.id===n.pointerId);if(r==null)return;if(n.pointerType===\"mouse\"&&n.buttons===0)return void this.onPointerUp(n);let a=this.pointers.length,o=(n.clientX-r.clientX)/a,l=(n.clientY-r.clientY)/a;o===0&&l===0||(r.clientX=n.clientX,r.clientY=n.clientY,n.pointerType===\"touch\"?(this.changeSource=n.altKey?St.AUTOMATIC:St.USER_INTERACTION,this.touchMode!==null&&this.touchMode(o,l)):(this.changeSource=St.USER_INTERACTION,this.panPerPixel>0?this.movePan(o,l):this.handleSinglePointerMove(o,l)))},this.onPointerUp=n=>{let{element:r}=this,a=this.pointers.findIndex(o=>o.id===n.pointerId);a!==-1&&this.pointers.splice(a,1),this.panPerPixel>0&&!n.altKey&&this.resetRadius(),this.pointers.length===0?(r.removeEventListener(\"pointermove\",this.onPointerMove),r.removeEventListener(\"pointerup\",this.onPointerUp),r.removeEventListener(\"touchmove\",this.disableScroll),this.enablePan&&this.enableTap&&this.recenter(n)):this.touchMode!==null&&this.onTouchChange(n),this.scene.element[pc].style.opacity=0,r.style.cursor=\"grab\",this.panPerPixel=0,this.isUserPointing&&this.dispatchEvent({type:\"pointer-change-end\"})},this.onWheel=n=>{this.changeSource=St.USER_INTERACTION;let r=n.deltaY*(n.deltaMode==1?18:1)*gd*this.zoomSensitivity/30;this.userAdjustOrbit(0,0,r),n.preventDefault(),this.dispatchEvent({type:\"user-interaction\"})},this.onKeyDown=n=>{let{changeSource:r}=this;this.changeSource=St.USER_INTERACTION,(n.shiftKey&&this.enablePan?this.panKeyCodeHandler(n):this.orbitZoomKeyCodeHandler(n))?(n.preventDefault(),this.dispatchEvent({type:\"user-interaction\"})):this.changeSource=r},this._options=Object.assign({},$0),this.setOrbit(0,Math.PI/2,1),this.setFieldOfView(100),this.jumpToGoal()}get interactionEnabled(){return this._interactionEnabled}enableInteraction(){if(this._interactionEnabled===!1){let{element:e}=this;e.addEventListener(\"pointerdown\",this.onPointerDown),e.addEventListener(\"pointercancel\",this.onPointerUp),this._disableZoom||e.addEventListener(\"wheel\",this.onWheel),e.addEventListener(\"keydown\",this.onKeyDown),e.addEventListener(\"touchmove\",()=>{},{passive:!1}),e.addEventListener(\"contextmenu\",this.onContext),this.element.style.cursor=\"grab\",this._interactionEnabled=!0,this.updateTouchActionStyle()}}disableInteraction(){if(this._interactionEnabled===!0){let{element:e}=this;e.removeEventListener(\"pointerdown\",this.onPointerDown),e.removeEventListener(\"pointermove\",this.onPointerMove),e.removeEventListener(\"pointerup\",this.onPointerUp),e.removeEventListener(\"pointercancel\",this.onPointerUp),e.removeEventListener(\"wheel\",this.onWheel),e.removeEventListener(\"keydown\",this.onKeyDown),e.removeEventListener(\"contextmenu\",this.onContext),e.style.cursor=\"\",this.touchMode=null,this._interactionEnabled=!1,this.updateTouchActionStyle()}}get options(){return this._options}set disableZoom(e){this._disableZoom!=e&&(this._disableZoom=e,e===!0?this.element.removeEventListener(\"wheel\",this.onWheel):this.element.addEventListener(\"wheel\",this.onWheel),this.updateTouchActionStyle())}getCameraSpherical(e=new Jr){return e.copy(this.spherical)}getFieldOfView(){return this.camera.fov}applyOptions(e){Object.assign(this._options,e),this.setOrbit(),this.setFieldOfView(Math.exp(this.goalLogFov))}updateNearFar(e,t){this.camera.far=t===0?2:t,this.camera.near=Math.max(e,this.camera.far/1e3),this.camera.updateProjectionMatrix()}updateAspect(e){this.camera.aspect=e,this.camera.updateProjectionMatrix()}setOrbit(e=this.goalSpherical.theta,t=this.goalSpherical.phi,i=this.goalSpherical.radius){let{minimumAzimuthalAngle:n,maximumAzimuthalAngle:r,minimumPolarAngle:a,maximumPolarAngle:o,minimumRadius:l,maximumRadius:c}=this._options,{theta:h,phi:u,radius:d}=this.goalSpherical,p=en(e,n,r);isFinite(n)||isFinite(r)||(this.spherical.theta=this.wrapAngle(this.spherical.theta-p)+p);let A=en(t,a,o),g=en(i,l,c);return(p!==h||A!==u||g!==d)&&!!(isFinite(p)&&isFinite(A)&&isFinite(g))&&(this.goalSpherical.theta=p,this.goalSpherical.phi=A,this.goalSpherical.radius=g,this.goalSpherical.makeSafe(),!0)}setRadius(e){this.goalSpherical.radius=e,this.setOrbit()}setFieldOfView(e){let{minimumFieldOfView:t,maximumFieldOfView:i}=this._options;e=en(e,t,i),this.goalLogFov=Math.log(e)}setDamperDecayTime(e){this.thetaDamper.setDecayTime(e),this.phiDamper.setDecayTime(e),this.radiusDamper.setDecayTime(e),this.fovDamper.setDecayTime(e)}adjustOrbit(e,t,i){let{theta:n,phi:r,radius:a}=this.goalSpherical,{minimumRadius:o,maximumRadius:l,minimumFieldOfView:c,maximumFieldOfView:h}=this._options,u=this.spherical.theta-n,d=Math.PI-.001,p=n-en(e,-d-u,d-u),A=r-t,g=i===0?0:((i>0?l:o)-a)/(Math.log(i>0?h:c)-this.goalLogFov),b=a+i*(isFinite(g)?g:2*(l-o));if(this.setOrbit(p,A,b),i!==0){let m=this.goalLogFov+i;this.setFieldOfView(Math.exp(m))}}jumpToGoal(){this.update(0,1e4)}update(e,t){if(this.isStationary())return!1;let{maximumPolarAngle:i,maximumRadius:n}=this._options,r=this.spherical.theta-this.goalSpherical.theta;return Math.abs(r)>Math.PI&&!isFinite(this._options.minimumAzimuthalAngle)&&!isFinite(this._options.maximumAzimuthalAngle)&&(this.spherical.theta-=2*Math.sign(r)*Math.PI),this.spherical.theta=this.thetaDamper.update(this.spherical.theta,this.goalSpherical.theta,t,Math.PI),this.spherical.phi=this.phiDamper.update(this.spherical.phi,this.goalSpherical.phi,t,i),this.spherical.radius=this.radiusDamper.update(this.spherical.radius,this.goalSpherical.radius,t,n),this.logFov=this.fovDamper.update(this.logFov,this.goalLogFov,t,1),this.moveCamera(),!0}updateTouchActionStyle(){let{style:e}=this.element;if(this._interactionEnabled){let{touchAction:t}=this._options;this._disableZoom&&t!==\"none\"?e.touchAction=\"manipulation\":e.touchAction=t}else e.touchAction=\"\"}isStationary(){return this.goalSpherical.theta===this.spherical.theta&&this.goalSpherical.phi===this.spherical.phi&&this.goalSpherical.radius===this.spherical.radius&&this.goalLogFov===this.logFov}moveCamera(){this.spherical.makeSafe(),this.camera.position.setFromSpherical(this.spherical),this.camera.setRotationFromEuler(new Mi(this.spherical.phi-Math.PI/2,this.spherical.theta,0,\"YXZ\")),this.camera.fov!==Math.exp(this.logFov)&&(this.camera.fov=Math.exp(this.logFov),this.camera.updateProjectionMatrix())}userAdjustOrbit(e,t,i){this.adjustOrbit(e*this.orbitSensitivity*this.inputSensitivity,t*this.orbitSensitivity*this.inputSensitivity,i*this.inputSensitivity)}wrapAngle(e){let t=(e+Math.PI)/(2*Math.PI);return 2*(t-Math.floor(t))*Math.PI-Math.PI}pixelLengthToSphericalAngle(e){return 2*Math.PI*e/this.scene.height}twoTouchDistance(e,t){let{clientX:i,clientY:n}=e,{clientX:r,clientY:a}=t,o=r-i,l=a-n;return Math.sqrt(o*o+l*l)}handleSinglePointerMove(e,t){let i=this.pixelLengthToSphericalAngle(e),n=this.pixelLengthToSphericalAngle(t);this.isUserPointing===!1&&(this.isUserPointing=!0,this.dispatchEvent({type:\"pointer-change-start\"})),this.userAdjustOrbit(i,n,0)}initializePan(){let{theta:e,phi:t}=this.spherical,i=e-this.scene.yaw;this.panPerPixel=.018*this.panSensitivity/this.scene.height,this.panProjection.set(-Math.cos(i),-Math.cos(t)*Math.sin(i),0,0,Math.sin(t),0,Math.sin(i),-Math.cos(t)*Math.cos(i),0)}movePan(e,t){let{scene:i}=this,n=Y0.set(e,t,0).multiplyScalar(this.inputSensitivity),r=this.spherical.radius*Math.exp(this.logFov)*this.panPerPixel;n.multiplyScalar(r);let a=i.getTarget();a.add(n.applyMatrix3(this.panProjection)),i.boundingSphere.clampPoint(a,a),i.setTarget(a.x,a.y,a.z)}recenter(e){if(performance.now()>this.startTime+300||Math.abs(e.clientX-this.startPointerPosition.clientX)>2||Math.abs(e.clientY-this.startPointerPosition.clientY)>2)return;let{scene:t}=this,i=t.positionAndNormalFromPoint(t.getNDC(e.clientX,e.clientY));if(i==null){let{cameraTarget:n}=t.element;t.element.cameraTarget=\"\",t.element.cameraTarget=n,this.userAdjustOrbit(0,0,1)}else t.target.worldToLocal(i.position),t.setTarget(i.position.x,i.position.y,i.position.z)}resetRadius(){let{scene:e}=this,t=e.positionAndNormalFromPoint(LC.set(0,0));if(t==null)return;e.target.worldToLocal(t.position);let i=e.getTarget(),{theta:n,phi:r}=this.spherical,a=n-e.yaw,o=Y0.set(Math.sin(r)*Math.sin(a),Math.cos(r),Math.sin(r)*Math.cos(a)),l=o.dot(t.position.sub(i));i.add(o.multiplyScalar(l)),e.setTarget(i.x,i.y,i.z),this.setOrbit(void 0,void 0,this.goalSpherical.radius-l)}onTouchChange(e){if(this.pointers.length===1)this.touchMode=this.touchModeRotate;else{if(this._disableZoom)return this.touchMode=null,void this.element.removeEventListener(\"touchmove\",this.disableScroll);this.touchMode=this.touchDecided&&this.touchMode===null?null:this.touchModeZoom,this.touchDecided=!0,this.element.addEventListener(\"touchmove\",this.disableScroll,{passive:!1}),this.lastSeparation=this.twoTouchDistance(this.pointers[0],this.pointers[1]),this.enablePan&&this.touchMode!=null&&(this.initializePan(),e.altKey||(this.scene.element[pc].style.opacity=1))}}onMouseDown(e){this.panPerPixel=0,this.enablePan&&(e.button===2||e.ctrlKey||e.metaKey||e.shiftKey)&&(this.initializePan(),this.scene.element[pc].style.opacity=1),this.element.style.cursor=\"grabbing\"}orbitZoomKeyCodeHandler(e){let t=!0;switch(e.key){case\"PageUp\":this.userAdjustOrbit(0,0,gd*this.zoomSensitivity);break;case\"PageDown\":this.userAdjustOrbit(0,0,-.04*this.zoomSensitivity);break;case\"ArrowUp\":this.userAdjustOrbit(0,-Ol,0);break;case\"ArrowDown\":this.userAdjustOrbit(0,Ol,0);break;case\"ArrowLeft\":this.userAdjustOrbit(-Ol,0,0);break;case\"ArrowRight\":this.userAdjustOrbit(Ol,0,0);break;default:t=!1}return t}panKeyCodeHandler(e){this.initializePan();let t=!0;switch(e.key){case\"ArrowUp\":this.movePan(0,-10);break;case\"ArrowDown\":this.movePan(0,10);break;case\"ArrowLeft\":this.movePan(-10,0);break;case\"ArrowRight\":this.movePan(10,0);break;default:t=!1}return t}},Z0=`\n \n`,eb=`\n \n`,tb=`\n \n \n \n \n \n \n \n \n`,Xi={width:.16,height:.07,cornerRadius:.03,opacity:1,color:0,minDistance:.5,maxDistance:10,baseScale:1,distanceScaleFactor:.3},ib=.05,nb=.01,PC=.07,kl=128,FC=Ve,Ho=class s extends ct{constructor(){super(),this.isActualSize=!1,this.preRenderIcons(),this.createPanel(),this.createButtons(),this.opacityDamper=new Ot,this.goalOpacity=Xi.opacity}createPanel(){let e=this.createPanelShape(),t=new Gp(e),i=new Xt({color:Xi.color,opacity:Xi.opacity,transparent:!0});this.panelMesh=new Je(t,i),this.panelMesh.name=\"MenuPanel\",this.add(this.panelMesh)}createButtons(){this.exitButton=this.createButton(\"close\"),this.exitButton.name=\"ExitButton\",this.exitButton.position.set(PC/2,0,nb),this.add(this.exitButton),this.toggleButton=this.createButton(\"view-real-size\"),this.toggleButton.name=\"ToggleButton\",this.toggleButton.position.set(-.035,0,nb),this.add(this.toggleButton)}createPanelShape(){let e=new ih,{width:t,height:i,cornerRadius:n}=Xi;return e.moveTo(-t/2+n,-i/2),e.lineTo(t/2-n,-i/2),e.quadraticCurveTo(t/2,-i/2,t/2,-i/2+n),e.lineTo(t/2,i/2-n),e.quadraticCurveTo(t/2,i/2,t/2-n,i/2),e.lineTo(-t/2+n,i/2),e.quadraticCurveTo(-t/2,i/2,-t/2,i/2-n),e.lineTo(-t/2,-i/2+n),e.quadraticCurveTo(-t/2,-i/2,-t/2+n,-i/2),e}preRenderIcons(){[{key:\"close\",svg:Z0},{key:\"view-real-size\",svg:eb},{key:\"replay\",svg:tb}].forEach(({key:e,svg:t})=>{s.iconTextures.has(e)||this.createTextureFromSvg(t,e)})}createTextureFromSvg(e,t){let i=document.createElement(\"canvas\");i.width=kl,i.height=kl;let n=i.getContext(\"2d\"),r=new Image,a=new Blob([e],{type:\"image/svg+xml\"}),o=URL.createObjectURL(a);r.onload=()=>{n.drawImage(r,0,0,kl,kl);let l=new pa(i);l.needsUpdate=!0,l.minFilter=FC,s.iconTextures.set(t,l),URL.revokeObjectURL(o)},r.src=o}createButton(e){let t=new Xt({transparent:!0}),i=new Ii(ib,ib),n=new Je(i,t),r=s.iconTextures.get(e);if(r)n.material.map=r,n.material.needsUpdate=!0;else{this.createTextureFromSvg(e===\"close\"?Z0:e===\"view-real-size\"?eb:tb,e);let a=()=>{let o=s.iconTextures.get(e);o?(n.material.map=o,n.material.needsUpdate=!0):setTimeout(a,10)};a()}return n}exitButtonControllerIntersection(e,t){return e.hitFromController(t,this.exitButton)}scaleModeButtonControllerIntersection(e,t){return e.hitFromController(t,this.toggleButton)}handleScaleToggle(e,t,i,n){if(!e)return null;this.isActualSize=!this.isActualSize;let r=this.isActualSize?\"replay\":\"view-real-size\";this.updateScaleModeButtonLabel(r);let a=this.isActualSize?1:t;return Math.max(i,Math.min(n,a))}updateScaleModeButtonLabel(e){let t=s.iconTextures.get(e);t&&(this.toggleButton.material.map=t,this.toggleButton.material.needsUpdate=!0)}updatePosition(e,t){if(!t)return;let i=new T;t.getWorldPosition(i);let n=t.getSize(),r=Math.min(n.x,n.z),a=Math.max(.5,Math.min(2,r/1)),o=-.2*a,l=.9*a,c=new T().copy(e.position).sub(i);c.y=0,c.normalize();let h=new T().copy(i).add(new T(0,o,0)).add(c.multiplyScalar(l));this.position.copy(h);let u=e.position.distanceTo(h),d=Math.max(Xi.minDistance,Math.min(Xi.maxDistance,u)),p=Xi.baseScale+(d-Xi.minDistance)*Xi.distanceScaleFactor;this.scale.set(p,p,p),this.lookAt(e.position)}set show(e){this.goalOpacity=e?Xi.opacity:0}updateOpacity(e){let t=this.panelMesh.material.opacity,i=this.opacityDamper.update(t,this.goalOpacity,e,1);this.traverse(n=>{if(n instanceof Je){let r=n.material;r.transparent&&(r.opacity=i)}}),this.visible=i>0}dispose(){var e;this.children.forEach(t=>{t instanceof Je&&(t.geometry&&t.geometry.dispose(),(Array.isArray(t.material)?t.material:[t.material]).forEach(i=>{i&&(\"map\"in i&&i.map instanceof pa&&i.map.dispose(),i.dispose())}))}),(e=this.parent)===null||e===void 0||e.remove(this)}};Ho.iconTextures=new Map;var Ql=.8,Cs=150,UC=new T(0,1,0),md=.01,fd=.001,Ac=\"not-presenting\",Ax=\"session-started\",sb=\"object-placed\",NC=\"failed\",gx=\"tracking\",OC=\"not-tracking\",za=new T,bd=new Kt,kC=new Re,QC=new T,GC=new Pt(45,1,.1,100),rb=new ei().setFromPoints([new T(0,0,0),new T(0,0,-1)]),_i=\"screen-space\",Br=\"world-space\",hg=class extends vi{isWorldSpaceReady(){return this.xrMode===Br&&this.worldSpaceInitialPlacementDone}constructor(e){super(),this.renderer=e,this.currentSession=null,this.placeOnWall=!1,this.placementBox=null,this.menuPanel=null,this.lastTick=null,this.turntableRotation=null,this.oldShadowIntensity=null,this.frame=null,this.initialHitSource=null,this.transientHitTestSource=null,this.inputSource=null,this._presentedScene=null,this.resolveCleanup=null,this.exitWebXRButtonContainer=null,this.overlay=null,this.xrLight=null,this.xrMode=null,this.xrController1=null,this.xrController2=null,this.selectedXRController=null,this.tracking=!0,this.frames=0,this.initialized=!1,this.oldTarget=new T,this.placementComplete=!1,this.isTranslating=!1,this.isRotating=!1,this.isTwoHandInteraction=!1,this.lastDragPosition=new T,this.deltaRotation=new Kt,this.scaleLine=new Zs(rb),this.firstRatio=0,this.lastAngle=0,this.goalPosition=new T,this.goalYaw=0,this.goalScale=1,this.xDamper=new Ot,this.yDamper=new Ot,this.zDamper=new Ot,this.yawDamper=new Ot,this.pitchDamper=new Ot,this.rollDamper=new Ot,this.scaleDamper=new Ot,this.wasTwoFingering=!1,this.worldSpaceInitialPlacementDone=!1,this.initialModelScale=1,this.minScale=1,this.maxScale=1,this.onExitWebXRButtonContainerClick=()=>this.stopPresenting(),this.onControllerSelectStart=t=>{var i,n,r;let a=this.presentedScene,o=t.target,l=this.menuPanel;if(this.menuPanel.exitButtonControllerIntersection(a,o)!=null)return(i=this.menuPanel)===null||i===void 0||i.dispose(),void this.stopPresenting();if(this.menuPanel.scaleModeButtonControllerIntersection(a,o)!=null){let h=this.menuPanel.handleScaleToggle(this.worldSpaceInitialPlacementDone,this.initialModelScale,this.minScale,this.maxScale);return void(h!==null&&(this.goalScale=h))}l&&(l.show=!1);let c=this.placementBox.controllerIntersection(a,o);if(c!=null){let h=new Wt().setFromObject(a.scenePivot).min.y+.2;if(c.point.y<=h)this.selectedXRController!=null?(this.selectedXRController.userData.line.visible=!1,a.canScale&&this.isWorldSpaceReady()&&(this.isTwoHandInteraction=!0,this.firstRatio=this.controllerSeparation()/a.scenePivot.scale.x,this.scaleLine.visible=!0)):o.attach(a.scenePivot),this.selectedXRController=o,a.setShadowIntensity(.01);else if(o==this.xrController1?this.xrController1.userData.isSelected=!0:o==this.xrController2&&(this.xrController2.userData.isSelected=!0),((n=this.xrController1)===null||n===void 0?void 0:n.userData.isSelected)&&((r=this.xrController2)===null||r===void 0?void 0:r.userData.isSelected))a.canScale&&this.isWorldSpaceReady()&&(this.isTwoHandInteraction=!0,this.firstRatio=this.controllerSeparation()/a.scenePivot.scale.x,this.scaleLine.visible=!0);else{let u=o===this.xrController1?this.xrController2:this.xrController1;o.userData.initialX=o.position.x,u.userData.turning=!1,o.userData.turning=!0,o.userData.line.visible=!1}}},this.onControllerSelectEnd=t=>{let i=t.target;if(i.userData.turning=!1,i.userData.line.visible=!0,this.isTwoHandInteraction=!1,this.scaleLine.visible=!1,i==this.xrController1?this.xrController1.userData.isSelected=!1:i==this.xrController2&&(this.xrController2.userData.isSelected=!1),this.selectedXRController!=null&&this.selectedXRController!=i)return;let n=this.presentedScene;n.attach(n.scenePivot),this.selectedXRController=null,this.goalYaw=Math.atan2(n.scenePivot.matrix.elements[8],n.scenePivot.matrix.elements[10]),this.goalPosition.x=n.scenePivot.position.x,this.goalPosition.z=n.scenePivot.position.z,this.isWorldSpaceReady()&&(this.goalPosition.y=n.scenePivot.position.y),this.menuPanel.show=!0},this.onUpdateScene=()=>{this.placementBox!=null&&this.isPresenting&&(this.placementBox.updateFromModelChanges(),this.placementBox.setScreenSpaceMode(this.xrMode===_i)),this.xrMode!==_i&&(this.menuPanel&&(this.menuPanel.dispose(),this.menuPanel=null),this.menuPanel=new Ho,this.presentedScene.add(this.menuPanel),this.menuPanel.updatePosition(this.presentedScene.getCamera(),this.placementBox))},this.onSelectStart=t=>{let i=this.transientHitTestSource;if(i==null)return;let n=this.frame.getHitTestResultsForTransientInput(i),r=this.presentedScene,a=this.placementBox;if(n.length===1){this.inputSource=t.inputSource;let{axes:o}=this.inputSource.gamepad,l=a.getHit(this.presentedScene,o[0],o[1]);a.show=!0,l!=null?(this.isTranslating=!0,this.lastDragPosition.copy(l)):this.placeOnWall===!1&&(this.isRotating=!0,this.lastAngle=1.5*o[0])}else if(n.length===2){a.show=!0,this.isTwoHandInteraction=!0;let{separation:o,angle:l}=this.fingerPolar(n);return this.lastAngle=l,this.firstRatio===0&&(this.firstRatio=o/r.scenePivot.scale.x),void(r.canScale&&this.setScale(o))}},this.onSelectEnd=()=>{this.isTranslating=!1,this.isRotating=!1,this.isTwoHandInteraction=!1,this.inputSource=null,this.goalPosition.y+=this.placementBox.offsetHeight*this.presentedScene.scale.x,this.placementBox.show=!1},this.onBeforeXRSelect=t=>{let i=t.composedPath();for(let n of i)if(n instanceof HTMLElement){let r=n.tagName.toLowerCase();if(r===\"input\"||r===\"button\"||r===\"select\"||r===\"textarea\"||r===\"a\"||n.hasAttribute(\"data-pointer-coalesce\")||n.classList.contains(\"interactive\")){t.preventDefault();break}}},this.threeRenderer=e.threeRenderer,this.threeRenderer.xr.enabled=!0}async resolveARSession(){Kf();let e=await navigator.xr.requestSession(\"immersive-ar\",{requiredFeatures:[],optionalFeatures:[\"hit-test\",\"dom-overlay\",\"light-estimation\"],domOverlay:this.overlay?{root:this.overlay}:void 0});return this.threeRenderer.xr.setReferenceSpaceType(\"local\"),await this.threeRenderer.xr.setSession(e),this.threeRenderer.xr.cameraAutoUpdate=!1,e}get presentedScene(){return this._presentedScene}async supportsPresentation(){try{return Kf(),await navigator.xr.isSessionSupported(\"immersive-ar\")}catch(e){return console.warn(\"Request to present in WebXR denied:\"),console.warn(e),console.warn(\"Falling back to next ar-mode\"),!1}}async present(e,t=!1){this.isPresenting&&console.warn(\"Cannot present while a model is already presenting\");let i=new Promise((c,h)=>{requestAnimationFrame(()=>c())});e.setHotspotsVisibility(!1),e.queueRender(),await i,this._presentedScene=e,this.overlay=e.element.shadowRoot.querySelector(\"div.default\"),this.overlay!=null&&this.overlay.addEventListener(\"beforexrselect\",this.onBeforeXRSelect),t===!0&&(this.xrLight=new ig(this.threeRenderer),this.xrLight.addEventListener(\"estimationstart\",()=>{if(!this.isPresenting||this.xrLight==null)return;let c=this.presentedScene;c.add(this.xrLight),c.environment=this.xrLight.environment}));let n=await this.resolveARSession();n.addEventListener(\"end\",()=>{this.postSessionCleanup()},{once:!0});let r=e.element.shadowRoot.querySelector(\".slot.exit-webxr-ar-button\");r.classList.add(\"enabled\"),r.addEventListener(\"click\",this.onExitWebXRButtonContainerClick),this.exitWebXRButtonContainer=r;let a=await n.requestReferenceSpace(\"viewer\");this.xrMode=n.interactionMode,this.tracking=!0,this.frames=0,this.initialized=!1,this.turntableRotation=e.yaw,this.goalYaw=e.yaw,this.goalScale=1,e.setBackground(null),this.oldShadowIntensity=e.shadowIntensity,e.setShadowIntensity(.01),this.oldTarget.copy(e.getTarget()),e.element.addEventListener(\"load\",this.onUpdateScene);let o=20*Math.PI/180,l=this.placeOnWall===!0?void 0:new XRRay(new DOMPoint(0,0,0),{x:0,y:-Math.sin(o),z:-Math.cos(o)});n.requestHitTestSource({space:a,offsetRay:l}).then(c=>{this.initialHitSource=c}),this.xrMode!==_i&&(this.setupXRControllers(),this.xDamper.setDecayTime(Cs),this.yDamper.setDecayTime(Cs),this.zDamper.setDecayTime(Cs),this.yawDamper.setDecayTime(Cs),this.pitchDamper.setDecayTime(Cs),this.rollDamper.setDecayTime(Cs),this.scaleDamper.setDecayTime(Cs)),this.currentSession=n,this.placementBox=new ng(e,this.placeOnWall?\"back\":\"bottom\"),this.placementBox&&this.placementBox.setScreenSpaceMode(this.xrMode===_i),this.placementComplete=!1,this.xrMode!==_i&&(this.menuPanel=new Ho,e.add(this.menuPanel),this.menuPanel.updatePosition(e.getCamera(),this.placementBox)),this.lastTick=performance.now(),this.dispatchEvent({type:\"status\",status:Ax})}setupXRControllerLine(e){if(!e.userData.line){let t=new Zs(rb);t.name=\"line\",t.scale.z=5,e.userData.turning=!1,e.userData.line=t,e.add(t)}}setupController(e){this.setupXRControllerLine(e),e.addEventListener(\"selectstart\",this.onControllerSelectStart),e.addEventListener(\"selectend\",this.onControllerSelectEnd)}setupXRControllers(){this.xrController1=this.threeRenderer.xr.getController(0),this.xrController2=this.threeRenderer.xr.getController(1),this.setupController(this.xrController1),this.setupController(this.xrController2),this.scaleLine.name=\"scale line\",this.scaleLine.visible=!1,this.xrController1.add(this.scaleLine);let e=this.presentedScene;e.add(this.xrController1),e.add(this.xrController2)}hover(e){if(this.xrMode===_i||this.selectedXRController==e)return!1;let t=this.presentedScene,i=this.placementBox.controllerIntersection(t,e);return e.userData.line.scale.z=i==null?5:i.distance,i!=null}controllerSeparation(){return this.xrController1.position.distanceTo(this.xrController2.position)}async stopPresenting(){if(!this.isPresenting)return;let e=new Promise(t=>{this.resolveCleanup=t});try{await this.currentSession.end(),await e}catch(t){console.warn(\"Error while trying to end WebXR AR session\"),console.warn(t),this.postSessionCleanup()}}get isPresenting(){return this.presentedScene!=null}get target(){return this.oldTarget}updateTarget(){let e=this.presentedScene;if(e!=null){let t=e.getTarget();this.oldTarget.copy(t),this.placeOnWall?t.z=e.boundingBox.min.z:t.y=e.boundingBox.min.y,e.setTarget(t.x,t.y,t.z)}}cleanupXRController(e){e.userData.turning=!1,e.userData.line.visible=!0,e.removeEventListener(\"selectstart\",this.onControllerSelectStart),e.removeEventListener(\"selectend\",this.onControllerSelectEnd),e.removeFromParent()}postSessionCleanup(){let e=this.currentSession;e!=null&&(e.removeEventListener(\"selectstart\",this.onSelectStart),e.removeEventListener(\"selectend\",this.onSelectEnd),this.currentSession=null);let t=this.presentedScene;if(this._presentedScene=null,t!=null){let{element:a}=t;this.xrLight!=null&&(t.remove(this.xrLight),this.xrLight.dispose(),this.xrLight=null),this.menuPanel!=null&&(this.menuPanel.dispose(),this.menuPanel=null),t.add(t.scenePivot),t.scenePivot.quaternion.set(0,0,0,1),t.scenePivot.position.set(0,0,0),t.scenePivot.scale.set(1,1,1),t.setShadowOffset(0);let o=this.turntableRotation;o!=null&&(t.yaw=o);let l=this.oldShadowIntensity;l!=null&&t.setShadowIntensity(l),t.setEnvironmentAndSkybox(a[no],a[hc]);let c=this.oldTarget;t.setTarget(c.x,c.y,c.z),t.xrCamera=null,t.element.removeEventListener(\"load\",this.onUpdateScene),t.orientHotspots(0);let{width:h,height:u}=a.getBoundingClientRect();t.setSize(h,u),requestAnimationFrame(()=>{t.element.dispatchEvent(new CustomEvent(\"camera-change\",{detail:{source:St.NONE}}))})}this.renderer.height=0;let i=this.exitWebXRButtonContainer;i!=null&&(i.classList.remove(\"enabled\"),i.removeEventListener(\"click\",this.onExitWebXRButtonContainerClick),this.exitWebXRButtonContainer=null);let n=this.transientHitTestSource;n!=null&&(n.cancel(),this.transientHitTestSource=null);let r=this.initialHitSource;r!=null&&(r.cancel(),this.initialHitSource=null),this.placementBox!=null&&(this.placementBox.dispose(),this.placementBox=null),this.xrMode!==_i&&(this.xrController1!=null&&(this.cleanupXRController(this.xrController1),this.xrController1=null),this.xrController2!=null&&(this.cleanupXRController(this.xrController2),this.xrController2=null),this.selectedXRController=null,this.scaleLine.visible=!1),this.isTranslating=!1,this.isRotating=!1,this.isTwoHandInteraction=!1,this.lastTick=null,this.turntableRotation=null,this.oldShadowIntensity=null,this.frame=null,this.inputSource=null,this.overlay!=null&&(this.overlay.removeEventListener(\"beforexrselect\",this.onBeforeXRSelect),this.overlay=null),this.worldSpaceInitialPlacementDone=!1,this.resolveCleanup!=null&&this.resolveCleanup(),this.dispatchEvent({type:\"status\",status:Ac})}updateView(e){let t=this.presentedScene,i=this.threeRenderer.xr;i.updateCamera(GC),t.xrCamera=i.getCamera();let{elements:n}=t.getCamera().matrixWorld;if(t.orientHotspots(Math.atan2(n[1],n[5])),this.initialized||(this.placeInitially(),this.initialized=!0),e.requestViewportScale&&e.recommendedViewportScale){let a=e.recommendedViewportScale;e.requestViewportScale(Math.max(a,.25))}let r=i.getBaseLayer();if(r!=null){let a=r instanceof XRWebGLLayer?r.getViewport(e):i.getBinding().getViewSubImage(r,e).viewport;this.threeRenderer.setViewport(a.x,a.y,a.width,a.height)}}placeInitially(){let e=this.presentedScene,{scenePivot:t,element:i}=e,{position:n}=t,r=e.getCamera(),{width:a,height:o}=this.overlay.getBoundingClientRect();e.setSize(a,o),r.projectionMatrixInverse.copy(r.projectionMatrix).invert();let{theta:l}=i.getCameraOrbit(),c=r.getWorldDirection(za);if(e.yaw=Math.atan2(-c.x,-c.z)-l,this.goalYaw=e.yaw,this.xrMode!==Br||this.worldSpaceInitialPlacementDone){if(this.xrMode===_i){let h=Math.max(1,2*e.boundingSphere.radius);n.copy(r.position).add(c.multiplyScalar(h)),this.updateTarget();let u=e.getTarget();n.add(u).sub(this.oldTarget),this.goalPosition.copy(n)}}else{let{position:h,scale:u}=this.calculateWorldSpaceOptimalPlacement(e,r);this.goalPosition.copy(h),this.goalScale=u,this.initialModelScale=u,n.copy(h),t.scale.set(u,u,u),this.worldSpaceInitialPlacementDone=!0,this.calculateWorldSpaceScaleLimits(e),this.enableWorldSpaceUserInteraction()}if(e.setHotspotsVisibility(!0),this.xrMode===_i){let{session:h}=this.frame;h.addEventListener(\"selectstart\",this.onSelectStart),h.addEventListener(\"selectend\",this.onSelectEnd),h.requestHitTestSourceForTransientInput({profile:\"generic-touchscreen\"}).then(u=>{this.transientHitTestSource=u})}}getTouchLocation(){let{axes:e}=this.inputSource.gamepad,t=this.placementBox.getExpandedHit(this.presentedScene,e[0],e[1]);return t!=null&&(za.copy(t).sub(this.presentedScene.getCamera().position),za.length()>10)?null:t}getHitPoint(e){let t=this.threeRenderer.xr.getReferenceSpace(),i=e.getPose(t);if(i==null)return null;let n=kC.fromArray(i.transform.matrix);return this.placeOnWall===!0&&(this.goalYaw=Math.atan2(n.elements[4],n.elements[6])),n.elements[5]>.75!==this.placeOnWall?QC.setFromMatrixPosition(n):null}moveToFloor(e){if(this.xrMode===Br&&!this.worldSpaceInitialPlacementDone)return this.placementBox.show=!1,void this.dispatchEvent({type:\"status\",status:sb});let t=this.initialHitSource;if(t==null)return;let i=e.getHitTestResults(t);if(i.length==0)return;let n=i[0],r=this.getHitPoint(n);r!=null&&(this.placementBox.show=!0,this.isTranslating||(this.placeOnWall?this.goalPosition.copy(r):this.goalPosition.y=r.y),t.cancel(),this.initialHitSource=null,this.dispatchEvent({type:\"status\",status:sb}))}fingerPolar(e){let t=e[0].inputSource.gamepad.axes,i=e[1].inputSource.gamepad.axes,n=i[0]-t[0],r=i[1]-t[1],a=Math.atan2(r,n),o=this.lastAngle-a;return o>Math.PI?o-=2*Math.PI:o<-Math.PI&&(o+=2*Math.PI),{separation:Math.sqrt(n*n+r*r),deltaYaw:o,angle:a}}setScale(e){let t=e/this.firstRatio;t=Math.abs(t-1)<.2?1:t,this.isWorldSpaceReady()&&(t=Math.max(this.minScale,Math.min(this.maxScale,t))),this.goalScale=t}processInput(e){let t=this.transientHitTestSource;if(t==null)return;let i=e.getHitTestResultsForTransientInput(t),n=this.presentedScene,r=n.scenePivot.scale.x;if(i.length===2){if(!this.wasTwoFingering){let{separation:c,angle:h}=this.fingerPolar(i);return this.firstRatio=c/r,this.lastAngle=h,this.wasTwoFingering=!0,void(this.isTwoHandInteraction=!0)}let{separation:a,deltaYaw:o,angle:l}=this.fingerPolar(i);return this.goalYaw+=o,this.lastAngle=l,n.canScale&&this.setScale(a),void(this.isTwoHandInteraction=!0)}if(this.wasTwoFingering&&(this.wasTwoFingering=!1,this.isTwoHandInteraction=!1,this.firstRatio=0),this.isTranslating||this.isTwoHandInteraction||this.isRotating)if(this.isRotating){let a=1.5*this.inputSource.gamepad.axes[0];this.goalYaw+=a-this.lastAngle,this.lastAngle=a}else this.isTranslating&&i.forEach(a=>{if(a.inputSource!==this.inputSource)return;let o=null;if(a.results.length>0&&(o=this.getHitPoint(a.results[0])),o==null&&(o=this.getTouchLocation()),o!=null){if(this.goalPosition.sub(this.lastDragPosition),this.isWorldSpaceReady())console.log(\"[processInput] Setting goalPosition.y to hit.y:\",o.y),this.goalPosition.add(o);else if(this.placeOnWall===!1){let l=o.y-this.lastDragPosition.y;if(l<0){this.placementBox.offsetHeight=l/r,this.presentedScene.setShadowOffset(l);let c=za.copy(n.getCamera().position),h=-l/(c.y-o.y);c.multiplyScalar(h),o.multiplyScalar(1-h).add(c)}this.goalPosition.add(o)}else this.goalPosition.add(o);this.lastDragPosition.copy(o)}})}applyXRControllerRotation(e,t){if(!e.userData.turning)return;let i=.3*(e.position.x-e.userData.initialX);this.deltaRotation.setFromAxisAngle(UC,i),t.quaternion.multiplyQuaternions(this.deltaRotation,t.quaternion)}handleScalingInXR(e,t){if(this.xrMode===Br&&!this.worldSpaceInitialPlacementDone)return;if(this.xrController1&&this.xrController2&&this.isTwoHandInteraction){let n=this.controllerSeparation();this.setScale(n),this.scaleLine.scale.z=-n,this.scaleLine.lookAt(this.xrController2.position)}let i=e.scenePivot.scale.x;if(this.goalScale!==i){let n=this.scaleDamper.update(i,this.goalScale,t,1);e.scenePivot.scale.set(n,n,n)}}updatePivotPosition(e,t){let{scenePivot:i}=e,n=this.placementBox,r=e.boundingSphere.radius,a=this.goalPosition,o=i.position,l=St.NONE;if(!a.equals(o)){l=St.USER_INTERACTION;let{x:c,y:h,z:u}=o;if(c=this.xDamper.update(c,a.x,t,r),h=this.yDamper.update(h,a.y,t,r),u=this.zDamper.update(u,a.z,t,r),o.set(c,h,u),this.xrMode===_i&&!this.isTranslating){let d=a.y-h;this.placementComplete&&this.placeOnWall===!1?(n.offsetHeight=d/e.scenePivot.scale.x,e.setShadowOffset(d)):d===0&&(this.placementComplete=!0,n.show=!1,e.setShadowIntensity(Ql))}this.xrMode!==_i&&a.equals(o)&&e.setShadowIntensity(Ql),this.isWorldSpaceReady()&&e.setShadowIntensity(Ql)}if(this.xrMode===Br&&!this.worldSpaceInitialPlacementDone&&this.goalScale!==i.scale.x){let c=this.scaleDamper.update(i.scale.x,this.goalScale,t,1);i.scale.set(c,c,c)}return e.updateTarget(t),l}updateYaw(e,t){bd.setFromAxisAngle(za.set(0,1,0),this.goalYaw);let i=e.scenePivot.quaternion.angleTo(bd),n=i-this.yawDamper.update(i,0,t,Math.PI);e.scenePivot.quaternion.rotateTowards(bd,n)}updateMenuPanel(e,t,i){this.menuPanel&&(this.menuPanel.updateOpacity(i),this.menuPanel.updatePosition(e.getCamera(),t))}applyXRInputToScene(e){let t=this.presentedScene,i=t.scenePivot,n=this.placementBox;if(this.updatePlacementBoxOpacity(n,e),this.updateTwoHandInteractionState(),this.applyXRControllerRotations(i),this.handleScalingInXR(t,e),i.parent!==t)return void this.updateMenuPanel(t,n,e);let r=this.updatePivotPosition(t,e);this.updateYaw(t,e),this.dispatchCameraChangeEvent(t,r),this.updateMenuPanel(t,n,e)}updatePlacementBoxOpacity(e,t){let i=this.presentedScene.getCamera();e.update(t,i.position);let n=this.hover(this.xrController1),r=this.hover(this.xrController2),a=(n||r)&&!this.isTwoHandInteraction;e.setInteractionState(this.isTranslating||this.isRotating,a)}updateTwoHandInteractionState(){var e,t;let i=((e=this.xrController1)===null||e===void 0?void 0:e.userData.isSelected)&&((t=this.xrController2)===null||t===void 0?void 0:t.userData.isSelected);this.isTwoHandInteraction=!!i}applyXRControllerRotations(e){this.isTwoHandInteraction||(this.xrController1&&this.applyXRControllerRotation(this.xrController1,e),this.xrController2&&this.applyXRControllerRotation(this.xrController2,e))}dispatchCameraChangeEvent(e,t){e.element.dispatchEvent(new CustomEvent(\"camera-change\",{detail:{source:t}}))}updateXRControllerHover(){let e=this.hover(this.xrController1),t=this.hover(this.xrController2),i=(e||t)&&!this.isTwoHandInteraction;this.placementBox&&(this.placementBox.setInteractionState(this.isTranslating||this.isRotating,i),this.placementBox.show=i)}enableWorldSpaceUserInteraction(){this.placementBox&&(this.placementBox.show=!0),this.presentedScene&&this.presentedScene.setShadowIntensity(Ql)}handleFirstView(e,t){this.xrMode===Br&&this.worldSpaceInitialPlacementDone||this.moveToFloor(e),this.processInput(e);let i=t-this.lastTick;this.applyXRInputToScene(i),this.renderer.preRender(this.presentedScene,t,i),this.lastTick=t,this.presentedScene.renderShadow(this.threeRenderer)}onWebXRFrame(e,t){this.xrMode!==_i&&this.updateXRControllerHover(),this.frame=t,++this.frames;let i=this.threeRenderer.xr.getReferenceSpace(),n=t.getViewerPose(i);n==null&&this.tracking===!0&&this.frames>30&&(this.tracking=!1,this.dispatchEvent({type:\"tracking\",status:OC}));let r=this.presentedScene;if(n==null||r==null||!r.element.loaded)return void this.threeRenderer.clear();this.tracking===!1&&(this.tracking=!0,this.dispatchEvent({type:\"tracking\",status:gx}));let a=!0;for(let o of n.views)this.updateView(o),a&&(this.handleFirstView(t,e),a=!1),this.threeRenderer.render(r,r.getCamera())}calculateWorldSpaceOptimalPlacement(e,t){let i=e.boundingBox,n={x:(i.max.x-i.min.x)/2,y:(i.max.y-i.min.y)/2,z:(i.max.z-i.min.z)/2},r=.15,a=Math.max(1.65,2*Math.max(n.x,n.z)+r),o=15*Math.PI/180,l=new T(0,-a*Math.sin(o),-a*Math.cos(o)),c=t.matrixWorld,h=l.clone().applyMatrix4(c),u=Math.max(n.x,n.z)+r,d=n.y,p=.3*a,A=.3*a/Math.max(d,md),g=p/Math.max(u,md),b=Math.min(A,g);return{position:h.clone().sub(new T(0,b*n.y,0)),scale:b}}calculateWorldSpaceScaleLimits(e){let t=e.size,i=Math.max(t.x,t.y,t.z),n=Math.max(Math.min(t.x,t.y,t.z),fd),r=md/Math.max(i,fd),a=70/Math.max(n,fd);this.minScale=Math.min(r,a,this.goalScale),this.maxScale=Math.max(r,a,this.goalScale)}};var ab=Symbol(\"prepared\"),yh=Symbol(\"prepare\"),Ki=Symbol(\"preparedGLTF\"),xh=Symbol(\"clone\"),ug=class{static prepare(e){if(e.scene==null)throw new Error(\"Model does not have a scene\");if(e[ab])return e;let t=this[yh](e);return t[ab]=!0,t}static[yh](e){let{scene:t}=e,i=[t];return Object.assign(Object.assign({},e),{scene:t,scenes:i})}get parser(){return this[Ki].parser}get animations(){return this[Ki].animations}get scene(){return this[Ki].scene}get scenes(){return this[Ki].scenes}get cameras(){return this[Ki].cameras}get asset(){return this[Ki].asset}get userData(){return this[Ki].userData}constructor(e){this[Ki]=e}clone(){return new this.constructor(this[xh]())}dispose(){this.scenes.forEach(e=>{e.traverse(t=>{let i=t;i.material&&((Array.isArray(i.material)?i.material:[i.material]).forEach(n=>{for(let r in n){let a=n[r];if(a instanceof xt){let o=a.source.data;o.close!=null&&o.close(),a.dispose()}}n.dispose()}),i.geometry.dispose())})})}[xh](){let e=this[Ki],t=hy(this.scene);zC(t,this.scene);let i=[t],n=e.userData?Object.assign({},e.userData):{};return Object.assign(Object.assign({},e),{scene:t,scenes:i,userData:n})}},zC=(s,e)=>{mx(s,e,(t,i)=>{i.userData.variantMaterials!==void 0&&(t.userData.variantMaterials=new Map(i.userData.variantMaterials)),i.userData.variantData!==void 0&&(t.userData.variantData=i.userData.variantData),i.userData.originalMaterial!==void 0&&(t.userData.originalMaterial=i.userData.originalMaterial)})},mx=(s,e,t)=>{t(s,e);for(let i=0;i{let u=t.threeObjectMap.get(c);if(u!=null){for(let d in u)if(d!=null&&d!==\"primitives\"){let p=d,A=u[p],g=r[p][A],b=a.get(h)||{};b[p]=A,a.set(h,b);let m=o.get(g)||new Set;m.add(h),o.set(g,m)}}});return new s(e,r,a,o)}static[ub](e,t,i){let n=(r,a)=>{if(i(r,a),r.isObject3D){let o=r,l=a;if(o.material)if(Array.isArray(o.material))for(let c=0;c{r.renderOrder=1e3,r.frustumCulled=!1,r.name||(r.name=r.uuid);let a=r;if(a.material){let{geometry:o}=a;a.castShadow=!0,a.isSkinnedMesh&&(o.boundingSphere=n,o.boundingBox=null);let l=a.material;if(l.isMeshBasicMaterial===!0&&(l.toneMapped=!1),l.shadowSide=0,l.aoMap){let{gltf:c,threeObjectMap:h}=t[Ha],u=h.get(l);if(c.materials!=null&&u!=null&&u.materials!=null){let d=c.materials[u.materials];d.occlusionTexture&&d.occlusionTexture.texCoord===0&&o.attributes.uv!=null&&o.setAttribute(\"uv2\",o.attributes.uv)}}}}),t}get correlatedSceneGraph(){return this[Ki][Ha]}[xh](){let e=super[xh](),t=new Map;return e.scene.traverse(i=>{let n=i;if(n.material){let a=n.material;if(a!=null){if(t.has(a.uuid))return void(n.material=t.get(a.uuid));n.material=a.clone(),t.set(a.uuid,n.material)}}let r=i;r.target!==void 0&&r.add(r.target)}),e[Ha]=_h.from(e,this.correlatedSceneGraph),e}},Ab=(s,e,t)=>{let i;switch(s){case Me:i=new Uint8ClampedArray(e*t*4);break;case pt:i=new Uint16Array(e*t*4);break;case Rn:i=new Uint32Array(e*t*4);break;case qg:i=new Int8Array(e*t*4);break;case jg:i=new Int16Array(e*t*4);break;case Nh:i=new Int32Array(e*t*4);break;case Tt:i=new Float32Array(e*t*4);break;default:throw new Error(\"Unsupported data type\")}return i},Gl,pg=class s{_renderer;_rendererIsDisposable=!1;_material;_scene;_camera;_quad;_renderTarget;_width;_height;_type;_colorSpace;_supportsReadPixels=!0;constructor(e){this._width=e.width,this._height=e.height,this._type=e.type,this._colorSpace=e.colorSpace;let t={format:_t,depthBuffer:!1,stencilBuffer:!1,type:this._type,colorSpace:this._colorSpace,anisotropy:e.renderTargetOptions?.anisotropy!==void 0?e.renderTargetOptions?.anisotropy:1,generateMipmaps:e.renderTargetOptions?.generateMipmaps!==void 0&&e.renderTargetOptions?.generateMipmaps,magFilter:e.renderTargetOptions?.magFilter!==void 0?e.renderTargetOptions?.magFilter:Ve,minFilter:e.renderTargetOptions?.minFilter!==void 0?e.renderTargetOptions?.minFilter:Ve,samples:e.renderTargetOptions?.samples!==void 0?e.renderTargetOptions?.samples:void 0,wrapS:e.renderTargetOptions?.wrapS!==void 0?e.renderTargetOptions?.wrapS:Bt,wrapT:e.renderTargetOptions?.wrapT!==void 0?e.renderTargetOptions?.wrapT:Bt};if(this._material=e.material,e.renderer?this._renderer=e.renderer:(this._renderer=s.instantiateRenderer(),this._rendererIsDisposable=!0),this._scene=new rn,this._camera=new Ln,this._camera.position.set(0,0,10),this._camera.left=-.5,this._camera.right=.5,this._camera.top=.5,this._camera.bottom=-.5,this._camera.updateProjectionMatrix(),!((i,n,r,a)=>{if(Gl!==void 0)return Gl;let o=new Zt(1,1,a);n.setRenderTarget(o);let l=new Je(new Ii,new Xt({color:16777215}));n.render(l,r),n.setRenderTarget(null);let c=Ab(i,o.width,o.height);return n.readRenderTargetPixels(o,0,0,o.width,o.height,c),o.dispose(),l.geometry.dispose(),l.material.dispose(),Gl=c[0]!==0,Gl})(this._type,this._renderer,this._camera,t)){let i;this._type===pt&&(i=this._renderer.extensions.has(\"EXT_color_buffer_float\")?Tt:void 0),i!==void 0?(console.warn(`This browser does not support reading pixels from ${this._type} RenderTargets, switching to 1015`),this._type=i):(this._supportsReadPixels=!1,console.warn(\"This browser dos not support toArray or toDataTexture, calls to those methods will result in an error thrown\"))}this._quad=new Je(new Ii,this._material),this._quad.geometry.computeBoundingBox(),this._scene.add(this._quad),this._renderTarget=new Zt(this.width,this.height,t),this._renderTarget.texture.mapping=e.renderTargetOptions?.mapping!==void 0?e.renderTargetOptions?.mapping:na}static instantiateRenderer(){let e=new zo;return e.setSize(128,128),e}render=()=>{this._renderer.setRenderTarget(this._renderTarget);try{this._renderer.render(this._scene,this._camera)}catch(e){throw this._renderer.setRenderTarget(null),e}this._renderer.setRenderTarget(null)};toArray(){if(!this._supportsReadPixels)throw new Error(\"Can't read pixels in this browser\");let e=Ab(this._type,this._width,this._height);return this._renderer.readRenderTargetPixels(this._renderTarget,0,0,this._width,this._height,e),e}toDataTexture(e){let t=new ss(this.toArray(),this.width,this.height,_t,this._type,e?.mapping||na,e?.wrapS||Bt,e?.wrapT||Bt,e?.magFilter||Ve,e?.minFilter||Ve,e?.anisotropy||1,At);return t.generateMipmaps=e?.generateMipmaps!==void 0&&e?.generateMipmaps,t}disposeOnDemandRenderer(){this._renderer.setRenderTarget(null),this._rendererIsDisposable&&(this._renderer.dispose(),this._renderer.forceContextLoss())}dispose(e){this.disposeOnDemandRenderer(),e&&this.renderTarget.dispose(),this.material instanceof Jt&&Object.values(this.material.uniforms).forEach(t=>{t.value instanceof xt&&t.value.dispose()}),Object.values(this.material).forEach(t=>{t instanceof xt&&t.dispose()}),this.material.dispose(),this._quad.geometry.dispose()}get width(){return this._width}set width(e){this._width=e,this._renderTarget.setSize(this._width,this._height)}get height(){return this._height}set height(e){this._height=e,this._renderTarget.setSize(this._width,this._height)}get renderer(){return this._renderer}get renderTarget(){return this._renderTarget}set renderTarget(e){this._renderTarget=e,this._width=e.width,this._height=e.height}get material(){return this._material}get type(){return this._type}get colorSpace(){return this._colorSpace}},Eh=class extends Error{},Sh=class extends Error{},Va=(s,e,t)=>{let i=new RegExp(`${e}=\"([^\"]*)\"`,\"i\").exec(s);if(i)return i[1];let n=new RegExp(`<${e}[^>]*>([\\\\s\\\\S]*?)`,\"i\").exec(s);if(n){let r=n[1].match(/([^<]*)<\\/rdf:li>/g);return r&&r.length===3?r.map(a=>a.replace(/<\\/?rdf:li>/g,\"\")):n[1].trim()}if(t!==void 0)return t;throw new Error(`Can't find ${e} in gainmap metadata`)},Ag=class{options;constructor(e){this.options={debug:!(!e||e.debug===void 0)&&e.debug,extractFII:!e||e.extractFII===void 0||e.extractFII,extractNonFII:!e||e.extractNonFII===void 0||e.extractNonFII}}extract(e){return new Promise((t,i)=>{let n=this.options.debug,r=new DataView(e.buffer);if(r.getUint16(0)!==65496)return void i(new Error(\"Not a valid jpeg\"));let a=r.byteLength,o,l=2,c=0;for(;l250)return void i(new Error(`Found no marker after ${c} loops \\u{1F635}`));if(r.getUint8(l)!==255)return void i(new Error(`Not a valid marker at offset 0x${l.toString(16)}, found: 0x${r.getUint8(l).toString(16)}`));if(o=r.getUint8(l+1),n&&console.log(`Marker: ${o.toString(16)}`),o===226){n&&console.log(\"Found APP2 marker (0xffe2)\");let h=l+4;if(r.getUint32(h)===1297106432){let u=h+4,d;if(r.getUint16(u)===18761)d=!1;else{if(r.getUint16(u)!==19789)return void i(new Error(\"No valid endianness marker found in TIFF header\"));d=!0}if(r.getUint16(u+2,!d)!==42)return void i(new Error(\"Not valid TIFF data! (no 0x002A marker)\"));let p=r.getUint32(u+4,!d);if(p<8)return void i(new Error(\"Not valid TIFF data! (First offset less than 8)\"));let A=u+p,g=r.getUint16(A,!d),b=A+2,m=0;for(let v=b;v{let e=(n=>{let r;r=typeof TextDecoder<\"u\"?new TextDecoder().decode(n):n.toString();let a=r.indexOf(\"\",a),l=r.slice(a,o+10);try{let c=Va(l,\"hdrgm:GainMapMin\",\"0\"),h=Va(l,\"hdrgm:GainMapMax\"),u=Va(l,\"hdrgm:Gamma\",\"1\"),d=Va(l,\"hdrgm:OffsetSDR\",\"0.015625\"),p=Va(l,\"hdrgm:OffsetHDR\",\"0.015625\"),A=/hdrgm:HDRCapacityMin=\"([^\"]*)\"/.exec(l),g=A?A[1]:\"0\",b=/hdrgm:HDRCapacityMax=\"([^\"]*)\"/.exec(l);if(!b)throw new Error(\"Incomplete gainmap metadata\");let m=b[1];return{gainMapMin:Array.isArray(c)?c.map(f=>parseFloat(f)):[parseFloat(c),parseFloat(c),parseFloat(c)],gainMapMax:Array.isArray(h)?h.map(f=>parseFloat(f)):[parseFloat(h),parseFloat(h),parseFloat(h)],gamma:Array.isArray(u)?u.map(f=>parseFloat(f)):[parseFloat(u),parseFloat(u),parseFloat(u)],offsetSdr:Array.isArray(d)?d.map(f=>parseFloat(f)):[parseFloat(d),parseFloat(d),parseFloat(d)],offsetHdr:Array.isArray(p)?p.map(f=>parseFloat(f)):[parseFloat(p),parseFloat(p),parseFloat(p)],hdrCapacityMin:parseFloat(g),hdrCapacityMax:parseFloat(m)}}catch{}a=r.indexOf(\"new Promise((e,t)=>{let i=document.createElement(\"img\");i.onload=()=>{e(i)},i.onerror=n=>{t(n)},i.src=URL.createObjectURL(s)}),gg=class extends Bi{_renderer;_renderTargetOptions;_internalLoadingManager;_config;constructor(e,t){super(t),this._config=e,e.renderer&&(this._renderer=e.renderer),this._internalLoadingManager=new ah}setRenderer(e){return this._renderer=e,this}setRenderTargetOptions(e){return this._renderTargetOptions=e,this}prepareQuadRenderer(){this._renderer||console.warn(\"WARNING: A Renderer was not passed to this Loader constructor or in setRenderer, the result of this Loader will need to be converted to a Data Texture with toDataTexture() before you can use it in your renderer.\");let e=this._config.createMaterial({gainMapMax:[1,1,1],gainMapMin:[0,0,0],gamma:[1,1,1],offsetHdr:[1,1,1],offsetSdr:[1,1,1],hdrCapacityMax:1,hdrCapacityMin:0,maxDisplayBoost:1,gainMap:new xt,sdr:new xt});return this._config.createQuadRenderer({width:16,height:16,type:pt,colorSpace:At,material:e,renderer:this._renderer,renderTargetOptions:this._renderTargetOptions})}async processImages(e,t,i){let n=t?new Blob([t],{type:\"image/jpeg\"}):void 0,r=new Blob([e],{type:\"image/jpeg\"}),a,o,l=!1;if(typeof createImageBitmap>\"u\"){let c=await Promise.all([n?gb(n):Promise.resolve(void 0),gb(r)]);o=c[0],a=c[1],l=i===\"flipY\"}else{let c=await Promise.all([n?createImageBitmap(n,{imageOrientation:i||\"flipY\"}):Promise.resolve(void 0),createImageBitmap(r,{imageOrientation:i||\"flipY\"})]);o=c[0],a=c[1]}return{sdrImage:a,gainMapImage:o,needsFlip:l}}createTextures(e,t,i){let n=new xt(t||new ImageData(2,2),na,Bt,Bt,Ve,1008,_t,Me,1,At);n.flipY=i,n.needsUpdate=!0;let r=new xt(e,na,Bt,Bt,Ve,1008,_t,Me,1,lt);return r.flipY=i,r.needsUpdate=!0,{gainMap:n,sdr:r}}updateQuadRenderer(e,t,i,n,r){e.width=t.width,e.height=t.height,e.material.gainMap=i,e.material.sdr=n,e.material.gainMapMin=r.gainMapMin,e.material.gainMapMax=r.gainMapMax,e.material.offsetHdr=r.offsetHdr,e.material.offsetSdr=r.offsetSdr,e.material.gamma=r.gamma,e.material.hdrCapacityMin=r.hdrCapacityMin,e.material.hdrCapacityMax=r.hdrCapacityMax,e.material.maxDisplayBoost=Math.pow(2,r.hdrCapacityMax),e.material.needsUpdate=!0}},mg=class extends Jt{_maxDisplayBoost;_hdrCapacityMin;_hdrCapacityMax;constructor({gamma:e,offsetHdr:t,offsetSdr:i,gainMapMin:n,gainMapMax:r,maxDisplayBoost:a,hdrCapacityMin:o,hdrCapacityMax:l,sdr:c,gainMap:h}){super({name:\"GainMapDecoderMaterial\",vertexShader:`\nvarying vec2 vUv;\n\nvoid main() {\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);\n}\n`,fragmentShader:`\n// min half float value\n#define HALF_FLOAT_MIN vec3( -65504, -65504, -65504 )\n// max half float value\n#define HALF_FLOAT_MAX vec3( 65504, 65504, 65504 )\n\nuniform sampler2D sdr;\nuniform sampler2D gainMap;\nuniform vec3 gamma;\nuniform vec3 offsetHdr;\nuniform vec3 offsetSdr;\nuniform vec3 gainMapMin;\nuniform vec3 gainMapMax;\nuniform float weightFactor;\n\nvarying vec2 vUv;\n\nvoid main() {\n vec3 rgb = texture2D( sdr, vUv ).rgb;\n vec3 recovery = texture2D( gainMap, vUv ).rgb;\n vec3 logRecovery = pow( recovery, gamma );\n vec3 logBoost = gainMapMin * ( 1.0 - logRecovery ) + gainMapMax * logRecovery;\n vec3 hdrColor = (rgb + offsetSdr) * exp2( logBoost * weightFactor ) - offsetHdr;\n vec3 clampedHdrColor = max( HALF_FLOAT_MIN, min( HALF_FLOAT_MAX, hdrColor ));\n gl_FragColor = vec4( clampedHdrColor , 1.0 );\n}\n`,uniforms:{sdr:{value:c},gainMap:{value:h},gamma:{value:new T(1/e[0],1/e[1],1/e[2])},offsetHdr:{value:new T().fromArray(t)},offsetSdr:{value:new T().fromArray(i)},gainMapMin:{value:new T().fromArray(n)},gainMapMax:{value:new T().fromArray(r)},weightFactor:{value:(Math.log2(a)-o)/(l-o)}},blending:0,depthTest:!1,depthWrite:!1}),this._maxDisplayBoost=a,this._hdrCapacityMin=o,this._hdrCapacityMax=l,this.needsUpdate=!0,this.uniformsNeedUpdate=!0}get sdr(){return this.uniforms.sdr.value}set sdr(e){this.uniforms.sdr.value=e}get gainMap(){return this.uniforms.gainMap.value}set gainMap(e){this.uniforms.gainMap.value=e}get offsetHdr(){return this.uniforms.offsetHdr.value.toArray()}set offsetHdr(e){this.uniforms.offsetHdr.value.fromArray(e)}get offsetSdr(){return this.uniforms.offsetSdr.value.toArray()}set offsetSdr(e){this.uniforms.offsetSdr.value.fromArray(e)}get gainMapMin(){return this.uniforms.gainMapMin.value.toArray()}set gainMapMin(e){this.uniforms.gainMapMin.value.fromArray(e)}get gainMapMax(){return this.uniforms.gainMapMax.value.toArray()}set gainMapMax(e){this.uniforms.gainMapMax.value.fromArray(e)}get gamma(){let e=this.uniforms.gamma.value;return[1/e.x,1/e.y,1/e.z]}set gamma(e){let t=this.uniforms.gamma.value;t.x=1/e[0],t.y=1/e[1],t.z=1/e[2]}get hdrCapacityMin(){return this._hdrCapacityMin}set hdrCapacityMin(e){this._hdrCapacityMin=e,this.calculateWeight()}get hdrCapacityMax(){return this._hdrCapacityMax}set hdrCapacityMax(e){this._hdrCapacityMax=e,this.calculateWeight()}get maxDisplayBoost(){return this._maxDisplayBoost}set maxDisplayBoost(e){this._maxDisplayBoost=Math.max(1,Math.min(65504,e)),this.calculateWeight()}calculateWeight(){let e=(Math.log2(this._maxDisplayBoost)-this._hdrCapacityMin)/(this._hdrCapacityMax-this._hdrCapacityMin);this.uniforms.weightFactor.value=Math.max(0,Math.min(1,e))}},fg=class extends gg{constructor(e,t){super({renderer:e,createMaterial:i=>new mg(i),createQuadRenderer:i=>new pg(i)},t)}async render(e,t,i,n){let{sdrImage:r,gainMapImage:a,needsFlip:o}=await this.processImages(i,n,\"flipY\"),{gainMap:l,sdr:c}=this.createTextures(r,a,o);this.updateQuadRenderer(e,r,l,c,t),e.render()}},bg=class extends fg{load(e,t,i,n){let r=this.prepareQuadRenderer(),a=new Hi(this._internalLoadingManager);return a.setResponseType(\"arraybuffer\"),a.setRequestHeader(this.requestHeader),a.setPath(this.path),a.setWithCredentials(this.withCredentials),this.manager.itemStart(e),a.load(e,async o=>{if(typeof o==\"string\")throw new Error(\"Invalid buffer, received [string], was expecting [ArrayBuffer]\");let l=new Uint8Array(o),c,h,u;try{let d=await HC(l);c=d.sdr,h=d.gainMap,u=d.metadata}catch(d){if(!(d instanceof Sh||d instanceof Eh))throw d;console.warn(`Failure to reconstruct an HDR image from ${e}: Gain map metadata not found in the file, HDRJPGLoader will render the SDR jpeg`),u={gainMapMin:[0,0,0],gainMapMax:[1,1,1],gamma:[1,1,1],hdrCapacityMin:0,hdrCapacityMax:1,offsetHdr:[0,0,0],offsetSdr:[0,0,0]},c=l}try{await this.render(r,u,c.buffer,h?.buffer)}catch(d){return this.manager.itemError(e),typeof n==\"function\"&&n(d),void r.disposeOnDemandRenderer()}typeof t==\"function\"&&t(r),this.manager.itemEnd(e),r.disposeOnDemandRenderer()},i,o=>{this.manager.itemError(e),typeof n==\"function\"&&n(o)}),r}},vg=class extends Yp{constructor(e){super(e),this.type=pt}parse(e){let t=function(A,g){switch(A){case 1:throw new Error(\"THREE.HDRLoader: Read Error: \"+(g||\"\"));case 2:throw new Error(\"THREE.HDRLoader: Write Error: \"+(g||\"\"));case 3:throw new Error(\"THREE.HDRLoader: Bad File Format: \"+(g||\"\"));default:throw new Error(\"THREE.HDRLoader: Memory Error: \"+(g||\"\"))}},i=function(A,g,b){g=g||1024;let m=A.pos,f=-1,y=0,v=\"\",x=String.fromCharCode.apply(null,new Uint16Array(A.subarray(m,m+128)));for(;0>(f=x.indexOf(`\n`))&&y=A.byteLength||!(v=i(A)))&&t(1,\"no header found\"),(x=v.match(/^#\\?(\\S+)/))||t(3,\"bad initial token\"),y.valid|=1,y.programtype=x[1],y.string+=v+`\n`;v=i(A),v!==!1;)if(y.string+=v+`\n`,v.charAt(0)!==\"#\"){if((x=v.match(g))&&(y.gamma=parseFloat(x[1])),(x=v.match(b))&&(y.exposure=parseFloat(x[1])),(x=v.match(m))&&(y.valid|=2,y.format=x[1]),(x=v.match(f))&&(y.valid|=4,y.height=parseInt(x[1],10),y.width=parseInt(x[2],10)),2&y.valid&&4&y.valid)break}else y.comments+=v+`\n`;return 2&y.valid||t(3,\"missing format specifier\"),4&y.valid||t(3,\"missing image size specifier\"),y})(a),l=o.width,c=o.height,h=(function(A,g,b){let m=g;if(m<8||m>32767||A[0]!==2||A[1]!==2||128&A[2])return new Uint8Array(A);m!==(A[2]<<8|A[3])&&t(3,\"wrong scanline width\");let f=new Uint8Array(4*g*b);f.length||t(4,\"unable to allocate buffer space\");let y=0,v=0,x=4*m,_=new Uint8Array(4),w=new Uint8Array(x),B=b;for(;B>0&&vA.byteLength&&t(1),_[0]=A[v++],_[1]=A[v++],_[2]=A[v++],_[3]=A[v++],_[0]==2&&_[1]==2&&(_[2]<<8|_[3])==m||t(3,\"bad rgbe scanline format\");let I,F=0;for(;F128;if(R&&(I-=128),(I===0||F+I>x)&&t(3,\"bad scanline data\"),R){let U=A[v++];for(let O=0;Othis.ldrLoader(t).load(e,r,()=>{},a));return n.name=e,n.flipY=!1,n}async loadKTX2(e,t){mo.setWithCredentials(t);let i=await new Promise((n,r)=>mo.load(e,n,()=>{},r));return i.name=e,i.flipY=!1,i}async loadLottie(e,t,i){let n=await this.getLottieLoader(i);n.setQuality(t);let r=await new Promise((a,o)=>n.load(e,a,()=>{},o));return r.name=e,r}async loadEquirect(e,t=!1,i=()=>{}){try{let n=jC.test(e),r=n?this.hdrLoader(t):this.imageLoader(t),a=await new Promise((o,l)=>r.load(e,c=>{let{renderTarget:h}=c;if(h!=null){let u;try{let d=c.toDataTexture();d.needsUpdate=!0;let p=d.image.data,A=!1,g=p.length;for(let b=0;b{i(c.loaded/c.total*.9)},l));return i(1),a.name=e,a.mapping=ac,n||a.type===pt||(a.colorSpace=lt),a}finally{i&&i(1)}}async generateEnvironmentMapAndSkybox(e=null,t=null,i=()=>{},n=!1){let r=t!==\"legacy\";t!==\"legacy\"&&t!==\"neutral\"||(t=null),t=cy(t);let a,o=Promise.resolve(null);e&&(o=this.loadEquirectFromUrl(e,n,i)),a=t?this.loadEquirectFromUrl(t,n,i):e?this.loadEquirectFromUrl(e,n,i):r?this.loadGeneratedEnvironmentMapAlt():this.loadGeneratedEnvironmentMap();let[l,c]=await Promise.all([a,o]);if(l==null)throw new Error(\"Failed to load environment map.\");return{environmentMap:l,skybox:c}}async loadEquirectFromUrl(e,t,i){if(!this.skyboxCache.has(e)){let n=this.loadEquirect(e,t,i);this.skyboxCache.set(e,n)}return this.skyboxCache.get(e)}async GenerateEnvironmentMap(e,t){await((c=0)=>new Promise(h=>setTimeout(h,c)))();let i=this.threeRenderer,n=new fa(256,{generateMipmaps:!1,type:pt,format:_t,colorSpace:At,depthBuffer:!0}),r=new Go(.1,100,n),a=r.renderTarget.texture;a.name=t;let o=i.outputColorSpace,l=i.toneMapping;return i.toneMapping=0,i.outputColorSpace=At,r.update(i,e),this.blurCubemap(n,.04),i.toneMapping=l,i.outputColorSpace=o,a}async loadGeneratedEnvironmentMap(){return this.generatedEnvironmentMap==null&&(this.generatedEnvironmentMap=this.GenerateEnvironmentMap(new Ch(\"legacy\"),\"legacy\")),this.generatedEnvironmentMap}async loadGeneratedEnvironmentMapAlt(){return this.generatedEnvironmentMapAlt==null&&(this.generatedEnvironmentMapAlt=this.GenerateEnvironmentMap(new Ch(\"neutral\"),\"neutral\")),this.generatedEnvironmentMapAlt}blurCubemap(e,t){if(this.blurMaterial==null){this.blurMaterial=this.getBlurShader(zl);let n=new Dn,r=new Je(n,this.blurMaterial);this.blurScene=new rn,this.blurScene.add(r)}let i=e.clone();this.halfblur(e,i,t,\"latitudinal\"),this.halfblur(i,e,t,\"longitudinal\")}halfblur(e,t,i,n){let r=e.width,a=isFinite(i)?Math.PI/(2*r):2*Math.PI/39,o=i/a,l=isFinite(i)?1+Math.floor(3*o):zl;l>zl&&console.warn(`sigmaRadians, ${i}, is too large and will clip, as it requested ${l} samples when the maximum is set to 20`);let c=[],h=0;for(let d=0;d= samples ) {\n \n break;\n \n }\n \n float theta = dTheta * float( i );\n gl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis );\n gl_FragColor.rgb += weights[ i ] * getSample( theta, axis );\n \n }\n }\n `,blending:0,depthTest:!1,depthWrite:!1,side:1})}async dispose(){for(let[,e]of this.skyboxCache)(await e).dispose();this.generatedEnvironmentMap!=null&&((await this.generatedEnvironmentMap).dispose(),this.generatedEnvironmentMap=null),this.generatedEnvironmentMapAlt!=null&&((await this.generatedEnvironmentMapAlt).dispose(),this.generatedEnvironmentMapAlt=null),this.blurMaterial!=null&&this.blurMaterial.dispose()}};var Rr=[1,.79,.62,.5,.4,.31,.25],mb=\"high-performance\",Qi=class s extends vi{static get singleton(){return this._singleton||(this._singleton=new s({powerPreference:(self.ModelViewerElement||{}).powerPreference||mb,debug:Yf()})),this._singleton}static resetSingleton(){let e=this._singleton.dispose();for(let t of e)t.disconnectedCallback();this._singleton=new s({powerPreference:(self.ModelViewerElement||{}).powerPreference||mb,debug:Yf()});for(let t of e)t.connectedCallback()}get canRender(){return this.threeRenderer!=null}get scaleFactor(){return Rr[this.scaleStep]}set minScale(e){let t=1;for(;t{this.dispatchEvent({type:\"contextlost\",sourceEvent:t})},this.onWebGLContextRestored=()=>{var t;(t=this.textureUtils)===null||t===void 0||t.dispose(),this.textureUtils=this.canRender?new wh(this.threeRenderer):null;for(let i of this.scenes)i.element[Ah]()},this.dpr=window.devicePixelRatio,this.canvas3D=document.createElement(\"canvas\"),this.canvas3D.id=\"webgl-canvas\",this.canvas3D.classList.add(\"show\");try{this.threeRenderer=new zo({canvas:this.canvas3D,alpha:!0,antialias:!0,powerPreference:e.powerPreference,preserveDrawingBuffer:!0}),this.threeRenderer.autoClear=!0,this.threeRenderer.setPixelRatio(1),this.threeRenderer.debug={checkShaderErrors:!!e.debug,onShaderError:null},this.threeRenderer.toneMapping=7}catch(t){console.warn(t)}this.arRenderer=this.canRender?new hg(this):null,this.textureUtils=this.canRender?new wh(this.threeRenderer):null,this.canRender&&$t.initializeKTX2Loader(this.threeRenderer),this.canvas3D.addEventListener(\"webglcontextlost\",this.onWebGLContextLost),this.canvas3D.addEventListener(\"webglcontextrestored\",this.onWebGLContextRestored),this.updateRendererSize()}registerScene(e){if(this.scenes.add(e),e.forceRescale(),this.canRender){let t=new pe;this.threeRenderer.getSize(t),e.canvas.width=t.x,e.canvas.height=t.y,this.scenes.size>0&&this.threeRenderer.setAnimationLoop((i,n)=>this.render(i,n))}}unregisterScene(e){this.scenes.delete(e),this.canvas3D.parentElement===e.canvas.parentElement&&e.canvas.parentElement.removeChild(this.canvas3D),this.canRender&&this.scenes.size===0&&this.threeRenderer.setAnimationLoop(null)}displayCanvas(e){return e.element.modelIsVisible&&!this.multipleScenesVisible?this.canvas3D:e.element[Pg]}countVisibleScenes(){let{canvas3D:e}=this,t=0,i=null;for(let r of this.scenes){let{element:a}=r;a.modelIsVisible&&r.externalRenderer==null&&++t,e.parentElement===r.canvas.parentElement&&(i=r)}let n=t>1;if(i!=null){let r=n&&!this.multipleScenesVisible,a=!i.element.modelIsVisible;if(r||a){let{width:o,height:l}=this.sceneSize(i);this.copyPixels(i,o,l),e.parentElement.removeChild(e)}}this.multipleScenesVisible=n}updateRendererSize(){var e;let t=window.devicePixelRatio;if(t!==this.dpr)for(let r of this.scenes){let{element:a}=r;a[kr](a.getBoundingClientRect())}let i=0,n=0;for(let r of this.scenes)i=Math.max(i,r.width),n=Math.max(n,r.height);if(i!==this.width||n!==this.height||t!==this.dpr){this.width=i,this.height=n,this.dpr=t,i=Math.ceil(i*t),n=Math.ceil(n*t),this.canRender&&this.threeRenderer.setSize(i,n,!1);for(let r of this.scenes){let{canvas:a}=r;a.width=i,a.height=n,r.forceRescale(),(e=r.effectRenderer)===null||e===void 0||e.setSize(i,n)}}}updateRendererScale(e){let t=this.scaleStep;this.avgFrameDuration+=en(.2*(e-this.avgFrameDuration),-5,5),this.avgFrameDuration>60?++this.scaleStep:this.avgFrameDuration<40&&this.scaleStep>0&&--this.scaleStep,this.scaleStep=Math.min(this.scaleStep,this.lastStep),t!==this.scaleStep&&(this.avgFrameDuration=50)}shouldRender(e){if(e.shouldRender())e.scaleStep!=this.scaleStep&&(e.scaleStep=this.scaleStep,this.rescaleCanvas(e));else{if(e.scaleStep==0)return!1;e.scaleStep=0,this.rescaleCanvas(e)}return!0}rescaleCanvas(e){let t=Rr[e.scaleStep],i=Math.ceil(this.width/t),n=Math.ceil(this.height/t),{style:r}=e.canvas;r.width=`${i}px`,r.height=`${n}px`,this.canvas3D.style.width=`${i}px`,this.canvas3D.style.height=`${n}px`;let a=this.dpr*t,o=t<1?\"GPU throttling\":this.dpr!==window.devicePixelRatio?\"No meta viewport tag\":\"\";e.element.dispatchEvent(new CustomEvent(\"render-scale\",{detail:{reportedDpr:window.devicePixelRatio,renderedDpr:a,minimumDpr:this.dpr*Rr[this.lastStep],pixelWidth:Math.ceil(e.width*a),pixelHeight:Math.ceil(e.height*a),reason:o}}))}sceneSize(e){let{dpr:t}=this,i=Rr[e.scaleStep];return{width:Math.min(Math.ceil(e.width*i*t),this.canvas3D.width),height:Math.min(Math.ceil(e.height*i*t),this.canvas3D.height)}}copyPixels(e,t,i){let n=e.context;n!=null?(n.clearRect(0,0,t,i),n.drawImage(this.canvas3D,0,0,t,i,0,0,t,i),e.canvas.classList.add(\"show\")):console.log(\"could not acquire 2d context\")}orderedScenes(){let e=[];for(let t of[!1,!0])for(let i of this.scenes)i.element.modelIsVisible===t&&e.push(i);return e}get isPresenting(){var e;return((e=this.arRenderer)===null||e===void 0?void 0:e.isPresenting)===!0}preRender(e,t,i){let{element:n,exposure:r,toneMapping:a}=e;n[Yi](t,i);let o=typeof r==\"number\"&&!Number.isNaN(r),l=n.environmentImage,c=n.skyboxImage,h=a===7&&(l===\"neutral\"||l===\"legacy\"||!l&&!c);this.threeRenderer.toneMappingExposure=(o?r:1)*(h?1.3:1)}render(e,t){if(t!=null)return void(this.arRenderer!=null&&this.arRenderer.onWebXRFrame(e,t));let i=e-this.lastTick;if(this.lastTick=e,!this.canRender||this.isPresenting)return;this.countVisibleScenes(),this.updateRendererSize(),this.renderedLastFrame&&(this.updateRendererScale(i),this.renderedLastFrame=!1);let{canvas3D:n}=this;for(let r of this.orderedScenes()){let{element:a}=r;if(!a.loaded||!a.modelIsVisible&&r.renderCount>0||(this.preRender(r,e,i),!this.shouldRender(r)))continue;if(r.externalRenderer!=null){let c=r.getCamera();c.updateMatrix();let{matrix:h,projectionMatrix:u}=c,d=h.elements.slice(),p=r.getTarget();d[12]+=p.x,d[13]+=p.y,d[14]+=p.z,r.externalRenderer.render({viewMatrix:d,projectionMatrix:u.elements});continue}if(!a.modelIsVisible&&!this.multipleScenesVisible)for(let c of this.scenes)c.element.modelIsVisible&&c.queueRender();let{width:o,height:l}=this.sceneSize(r);r.renderShadow(this.threeRenderer),this.threeRenderer.setRenderTarget(null),this.threeRenderer.setViewport(0,Math.ceil(this.height*this.dpr)-l,o,l),r.effectRenderer!=null?r.effectRenderer.render(i):(this.threeRenderer.autoClear=!0,this.threeRenderer.toneMapping=r.toneMapping,this.threeRenderer.render(r,r.camera)),this.multipleScenesVisible||!r.element.modelIsVisible&&r.renderCount===0?this.copyPixels(r,o,l):n.parentElement!==r.canvas.parentElement&&(r.canvas.parentElement.appendChild(n),r.canvas.classList.remove(\"show\")),r.hasRendered(),++r.renderCount,this.renderedLastFrame=!0}}dispose(){this.textureUtils!=null&&this.textureUtils.dispose(),this.threeRenderer!=null&&this.threeRenderer.dispose(),this.textureUtils=null,this.threeRenderer=null;let e=[];for(let t of this.scenes)e.push(t.element);return this.canvas3D.removeEventListener(\"webglcontextlost\",this.onWebGLContextLost),this.canvas3D.removeEventListener(\"webglcontextrestored\",this.onWebGLContextRestored),e}};var Qe=Symbol(\"correlatedObjects\"),tt=Symbol(\"onUpdate\"),nr=class{constructor(e,t){this[tt]=e,this[Qe]=t}};var fb=new Xt,XC=new Ii(2,2),KC=0,Ei=Symbol(\"threeTexture\"),bb=Symbol(\"threeTextures\"),JC=class extends nr{get[Ei](){var s;return(s=this[Qe])===null||s===void 0?void 0:s.values().next().value}get[bb](){return this[Qe]}constructor(s,e){super(s,new Set(e?[e]:[]));let t=this[Ei].image;t.src||(t.src=e.name?e.name:\"adhoc_image\"+KC++),t.name||(t.name=e&&t&&t.src?t.src.split(\"/\").pop():\"adhoc_image\")}get name(){return this[Ei].image.name||\"\"}get uri(){return this[Ei].image.src}get bufferView(){return this[Ei].image.bufferView}get element(){let s=this[Ei];if(s&&(s.isCanvasTexture||s.isVideoTexture))return s.image}get animation(){let s=this[Ei];if(s&&s.isCanvasTexture&&s.animation)return s.animation}get type(){return this.uri!=null?\"external\":\"embedded\"}set name(s){for(let e of this[bb])e.image.name=s}update(){let s=this[Ei];s&&s.isCanvasTexture&&!s.animation&&(this[Ei].needsUpdate=!0,this[tt]())}async createThumbnail(s,e){let t=new rn;fb.map=this[Ei];let i=new Je(XC,fb);t.add(i);let n=new Ln(-1,1,1,-1,0,1),{threeRenderer:r}=Qi.singleton,a=new Zt(s,e);r.setRenderTarget(a),r.render(t,n),r.setRenderTarget(null);let o=new Uint8Array(s*e*4);r.readRenderTargetPixels(a,0,0,s,e,o),is.width=s,is.height=e;let l=is.getContext(\"2d\"),c=l.createImageData(s,e);return c.data.set(o),l.putImageData(c,0,0),new Promise(async(h,u)=>{is.toBlob(d=>{if(!d)return u(\"Failed to capture thumbnail.\");h(URL.createObjectURL(d))},\"image/png\")})}},jt,ns;(function(s){s[s.Nearest=9728]=\"Nearest\",s[s.Linear=9729]=\"Linear\",s[s.NearestMipmapNearest=9984]=\"NearestMipmapNearest\",s[s.LinearMipmapNearest=9985]=\"LinearMipmapNearest\",s[s.NearestMipmapLinear=9986]=\"NearestMipmapLinear\",s[s.LinearMipmapLinear=9987]=\"LinearMipmapLinear\"})(jt||(jt={})),(function(s){s[s.ClampToEdge=33071]=\"ClampToEdge\",s[s.MirroredRepeat=33648]=\"MirroredRepeat\",s[s.Repeat=10497]=\"Repeat\"})(ns||(ns={}));var vb=new Map([[ns.Repeat,sn],[ns.ClampToEdge,Bt],[ns.MirroredRepeat,aa]]),xg=new Map([[sn,ns.Repeat],[Bt,ns.ClampToEdge],[aa,ns.MirroredRepeat]]),YC=new Map([[jt.Nearest,kt],[jt.Linear,Ve],[jt.NearestMipmapNearest,qo],[jt.LinearMipmapNearest,jr],[jt.NearestMipmapLinear,Gs],[jt.LinearMipmapLinear,mi]]),fx=new Map([[kt,jt.Nearest],[Ve,jt.Linear],[qo,jt.NearestMipmapNearest],[jr,jt.LinearMipmapNearest],[Gs,jt.NearestMipmapLinear],[mi,jt.LinearMipmapLinear]]),$C=new Map([[jt.Nearest,kt],[jt.Linear,Ve]]),bx=new Map([[kt,jt.Nearest],[Ve,jt.Linear]]),ZC=s=>fx.has(s),ew=s=>bx.has(s),tw=s=>xg.has(s),vn=Symbol(\"threeTexture\"),yb=Symbol(\"threeTextures\"),Hn=Symbol(\"setProperty\"),_g=class extends nr{get[vn](){var e;return(e=this[Qe])===null||e===void 0?void 0:e.values().next().value}get[yb](){return this[Qe]}constructor(e,t){super(e,new Set(t?[t]:[]))}get name(){return this[vn].name||\"\"}get minFilter(){return fx.get(this[vn].minFilter)}get magFilter(){return bx.get(this[vn].magFilter)}get wrapS(){return xg.get(this[vn].wrapS)}get wrapT(){return xg.get(this[vn].wrapT)}get rotation(){return this[vn].rotation}get scale(){return Fg(this[vn].repeat)}get offset(){return Fg(this[vn].offset)}setMinFilter(e){this[Hn](\"minFilter\",YC.get(e))}setMagFilter(e){this[Hn](\"magFilter\",$C.get(e))}setWrapS(e){this[Hn](\"wrapS\",vb.get(e))}setWrapT(e){this[Hn](\"wrapT\",vb.get(e))}setRotation(e){e==null&&(e=0),this[Hn](\"rotation\",e)}setScale(e){e==null&&(e={u:1,v:1}),this[Hn](\"repeat\",new pe(e.u,e.v))}setOffset(e){e==null&&(e={u:0,v:0}),this[Hn](\"offset\",new pe(e.u,e.v))}[Hn](e,t){if(((i,n)=>{switch(i){case\"minFilter\":return ZC(n);case\"magFilter\":return ew(n);case\"wrapS\":case\"wrapT\":return tw(n);case\"rotation\":case\"repeat\":case\"offset\":return!0;default:throw new Error(`Cannot configure property \"${i}\" on Sampler`)}})(e,t))for(let i of this[yb])i[e]=t,i.needsUpdate=!0;this[tt]()}};var xb=Symbol(\"image\"),_b=Symbol(\"sampler\"),Eb=Symbol(\"threeTexture\"),Mh=class extends nr{get[Eb](){var e;return(e=this[Qe])===null||e===void 0?void 0:e.values().next().value}constructor(e,t){super(e,new Set(t?[t]:[])),this[_b]=new _g(e,t),this[xb]=new JC(e,t)}get name(){return this[Eb].name||\"\"}set name(e){for(let t of this[Qe])t.name=e}get sampler(){return this[_b]}get source(){return this[xb]}};var vx,yx,xx,Ds=Symbol(\"texture\"),Ls=Symbol(\"transform\"),vd=Symbol(\"materials\"),Sb=Symbol(\"usage\"),Dr=Symbol(\"onUpdate\"),ro=Symbol(\"activeVideo\"),Te;(function(s){s[s.Base=0]=\"Base\",s[s.MetallicRoughness=1]=\"MetallicRoughness\",s[s.Normal=2]=\"Normal\",s[s.Occlusion=3]=\"Occlusion\",s[s.Emissive=4]=\"Emissive\",s[s.Clearcoat=5]=\"Clearcoat\",s[s.ClearcoatRoughness=6]=\"ClearcoatRoughness\",s[s.ClearcoatNormal=7]=\"ClearcoatNormal\",s[s.SheenColor=8]=\"SheenColor\",s[s.SheenRoughness=9]=\"SheenRoughness\",s[s.Transmission=10]=\"Transmission\",s[s.Thickness=11]=\"Thickness\",s[s.Specular=12]=\"Specular\",s[s.SpecularColor=13]=\"SpecularColor\",s[s.Iridescence=14]=\"Iridescence\",s[s.IridescenceThickness=15]=\"IridescenceThickness\",s[s.Anisotropy=16]=\"Anisotropy\"})(Te||(Te={}));var ts=class{constructor(e,t,i,n){this[vx]=null,this[yx]={rotation:0,scale:new pe(1,1),offset:new pe(0,0)},this[xx]=!1,i&&(this[Ls].rotation=i.rotation,this[Ls].scale.copy(i.repeat),this[Ls].offset.copy(i.offset),this[Ds]=new Mh(e,i)),this[Dr]=e,this[vd]=n,this[Sb]=t}get texture(){return this[Ds]}setTexture(e){var t,i;let n=e!=null?e.source[Ei]:null,r=(t=this[Ds])===null||t===void 0?void 0:t.source[Ei];if(r!=null&&r.isVideoTexture?this[ro]=!1:!((i=this[Ds])===null||i===void 0)&&i.source.animation&&this[Ds].source.animation.removeEventListener(\"enterFrame\",this[Dr]),this[Ds]=e,n!=null&&n.isVideoTexture){let o=n.image;if(this[ro]=!0,o.requestVideoFrameCallback!=null){let l=()=>{this[ro]&&(this[Dr](),o.requestVideoFrameCallback(l))};o.requestVideoFrameCallback(l)}else{let l=()=>{this[ro]&&(this[Dr](),requestAnimationFrame(l))};requestAnimationFrame(l)}}else e?.source.animation!=null&&e.source.animation.addEventListener(\"enterFrame\",this[Dr]);let a=lt;if(this[vd])for(let o of this[vd]){switch(this[Sb]){case Te.Base:o.map=n;break;case Te.MetallicRoughness:a=At,o.metalnessMap=n,o.roughnessMap=n;break;case Te.Normal:a=At,o.normalMap=n;break;case Te.Occlusion:a=At,o.aoMap=n;break;case Te.Emissive:o.emissiveMap=n;break;case Te.Clearcoat:o.clearcoatMap=n;break;case Te.ClearcoatRoughness:o.clearcoatRoughnessMap=n;break;case Te.ClearcoatNormal:o.clearcoatNormalMap=n;break;case Te.SheenColor:o.sheenColorMap=n;break;case Te.SheenRoughness:o.sheenRoughnessMap=n;break;case Te.Transmission:o.transmissionMap=n;break;case Te.Thickness:o.thicknessMap=n;break;case Te.Specular:o.specularIntensityMap=n;break;case Te.SpecularColor:o.specularColorMap=n;break;case Te.Iridescence:o.iridescenceMap=n;break;case Te.IridescenceThickness:o.iridescenceThicknessMap=n;break;case Te.Anisotropy:o.anisotropyMap=n}o.needsUpdate=!0}n&&(n.colorSpace=a,n.rotation=this[Ls].rotation,n.repeat=this[Ls].scale,n.offset=this[Ls].offset),this[Dr]()}};vx=Ds,yx=Ls,xx=ro;var qa=Symbol(\"threeMaterial\"),Hl=Symbol(\"threeMaterials\"),Cb=Symbol(\"baseColorTexture\"),wb=Symbol(\"metallicRoughnessTexture\"),Eg=class extends nr{get[Hl](){return this[Qe]}get[qa](){var e;return(e=this[Qe])===null||e===void 0?void 0:e.values().next().value}constructor(e,t){super(e,t);let{map:i,metalnessMap:n}=t.values().next().value;this[Cb]=new ts(e,Te.Base,i,t),this[wb]=new ts(e,Te.MetallicRoughness,n,t)}get baseColorFactor(){let e=[0,0,0,this[qa].opacity];return this[qa].color.toArray(e),e}get metallicFactor(){return this[qa].metalness}get roughnessFactor(){return this[qa].roughness}get baseColorTexture(){return this[Cb]}get metallicRoughnessTexture(){return this[wb]}setBaseColorFactor(e){let t=new ve;e instanceof Array?t.fromArray(e):t.set(e);for(let i of this[Hl])i.color.set(t),e instanceof Array&&e.length>3?i.opacity=e[3]:(e=[0,0,0,i.opacity],t.toArray(e));this[tt]()}setMetallicFactor(e){for(let t of this[Hl])t.metalness=e;this[tt]()}setRoughnessFactor(e){for(let t of this[Hl])t.roughness=e;this[tt]()}};var Mb,Ib,Tb=Symbol(\"pbrMetallicRoughness\"),Bb=Symbol(\"normalTexture\"),Rb=Symbol(\"occlusionTexture\"),Db=Symbol(\"emissiveTexture\"),vt=Symbol(\"backingThreeMaterial\"),Lb=Symbol(\"applyAlphaCutoff\"),yd=Symbol(\"getAlphaMode\"),Lr=Symbol(\"lazyLoadGLTFInfo\"),xd=Symbol(\"initialize\"),Sg=Symbol(\"getLoadedMaterial\"),me=Symbol(\"ensureMaterialIsLoaded\"),Pb=Symbol(\"gltfIndex\"),ao=Symbol(\"setActive\"),Zr=Symbol(\"variantIndices\"),_d=Symbol(\"isActive\"),Fb=Symbol(\"modelVariants\"),Ed=Symbol(\"name\"),ui=Symbol(\"pbrTextures\"),fo=class extends nr{get[(Mb=Zr,Ib=ui,vt)](){return this[Qe].values().next().value}constructor(e,t,i,n,r,a,o=void 0){super(e,r),this[Mb]=new Set,this[Ib]=new Map,this[Pb]=t,this[_d]=i,this[Fb]=n,this[Ed]=a,o==null?this[xd]():this[Lr]=o}[xd](){let e=this[tt],t=this[Qe];this[Tb]=new Eg(e,t);let{normalMap:i,aoMap:n,emissiveMap:r}=t.values().next().value;this[Bb]=new ts(e,Te.Normal,i,t),this[Rb]=new ts(e,Te.Occlusion,n,t),this[Db]=new ts(e,Te.Emissive,r,t);let a=o=>{this[ui].set(o,new ts(e,o,null,t))};a(Te.Clearcoat),a(Te.ClearcoatRoughness),a(Te.ClearcoatNormal),a(Te.SheenColor),a(Te.SheenRoughness),a(Te.Transmission),a(Te.Thickness),a(Te.Specular),a(Te.SpecularColor),a(Te.Iridescence),a(Te.IridescenceThickness),a(Te.Anisotropy)}async[Sg](){if(this[Lr]!=null){let e=await this[Lr].doLazyLoad();return this[xd](),this[Lr]=void 0,this.ensureLoaded=async()=>{},e}return null}colorFromRgb(e){let t=new ve;return e instanceof Array?t.fromArray(e):t.set(e),t}[me](){if(this[Lr]!=null)throw new Error(`Material \"${this.name}\" has not been loaded, call 'await\n myMaterial.ensureLoaded()' before using an unloaded material.`)}async ensureLoaded(){await this[Sg]()}get isLoaded(){return this[Lr]==null}get isActive(){return this[_d]}[ao](e){this[_d]=e}get name(){return this[Ed]||\"\"}set name(e){if(this[Ed]=e,this[Qe]!=null)for(let t of this[Qe])t.name=e}get pbrMetallicRoughness(){return this[me](),this[Tb]}get normalTexture(){return this[me](),this[Bb]}get occlusionTexture(){return this[me](),this[Rb]}get emissiveTexture(){return this[me](),this[Db]}get emissiveFactor(){return this[me](),this[vt].emissive.toArray()}get index(){return this[Pb]}hasVariant(e){let t=this[Fb].get(e);return t!=null&&this[Zr].has(t.index)}setEmissiveFactor(e){this[me]();let t=this.colorFromRgb(e);for(let i of this[Qe])i.emissive.set(t);this[tt]()}[yd](){return this[vt].transparent?\"BLEND\":this[vt].alphaTest>0?\"MASK\":\"OPAQUE\"}[Lb](){this[me]();for(let e of this[Qe])this[yd]()===\"MASK\"?e.alphaTest==null&&(e.alphaTest=.5):e.alphaTest=void 0,e.needsUpdate=!0}setAlphaCutoff(e){this[me]();for(let t of this[Qe])t.alphaTest=e,t.needsUpdate=!0;this[Lb](),this[tt]()}getAlphaCutoff(){return this[me](),this[vt].alphaTest}setDoubleSided(e){this[me]();for(let t of this[Qe])t.side=e?2:0,t.needsUpdate=!0;this[tt]()}getDoubleSided(){return this[me](),this[vt].side==2}setAlphaMode(e){this[me]();let t=(i,n)=>{i.transparent=n,i.depthWrite=!n};for(let i of this[Qe])t(i,e===\"BLEND\"),i.alphaTest=e===\"MASK\"?.5:void 0,i.needsUpdate=!0;this[tt]()}getAlphaMode(){return this[me](),this[yd]()}get emissiveStrength(){return this[me](),this[vt].emissiveIntensity}setEmissiveStrength(e){this[me]();for(let t of this[Qe])t.emissiveIntensity=e;this[tt]()}get clearcoatFactor(){return this[me](),this[vt].clearcoat}get clearcoatRoughnessFactor(){return this[me](),this[vt].clearcoatRoughness}get clearcoatTexture(){return this[me](),this[ui].get(Te.Clearcoat)}get clearcoatRoughnessTexture(){return this[me](),this[ui].get(Te.ClearcoatRoughness)}get clearcoatNormalTexture(){return this[me](),this[ui].get(Te.ClearcoatNormal)}get clearcoatNormalScale(){return this[me](),this[vt].clearcoatNormalScale.x}setClearcoatFactor(e){this[me]();for(let t of this[Qe])t.clearcoat=e;this[tt]()}setClearcoatRoughnessFactor(e){this[me]();for(let t of this[Qe])t.clearcoatRoughness=e;this[tt]()}setClearcoatNormalScale(e){this[me]();for(let t of this[Qe])t.clearcoatNormalScale=new pe(e,e);this[tt]()}get ior(){return this[me](),this[vt].ior}setIor(e){this[me]();for(let t of this[Qe])t.ior=e;this[tt]()}get sheenColorFactor(){return this[me](),this[vt].sheenColor.toArray()}get sheenColorTexture(){return this[me](),this[ui].get(Te.SheenColor)}get sheenRoughnessFactor(){return this[me](),this[vt].sheenRoughness}get sheenRoughnessTexture(){return this[me](),this[ui].get(Te.SheenRoughness)}setSheenColorFactor(e){this[me]();let t=this.colorFromRgb(e);for(let i of this[Qe])i.sheenColor.set(t),i.sheen=1;this[tt]()}setSheenRoughnessFactor(e){this[me]();for(let t of this[Qe])t.sheenRoughness=e,t.sheen=1;this[tt]()}get transmissionFactor(){return this[me](),this[vt].transmission}get transmissionTexture(){return this[me](),this[ui].get(Te.Transmission)}setTransmissionFactor(e){this[me]();for(let t of this[Qe])t.transmission=e;this[tt]()}get thicknessFactor(){return this[me](),this[vt].thickness}get thicknessTexture(){return this[me](),this[ui].get(Te.Thickness)}get attenuationDistance(){return this[me](),this[vt].attenuationDistance}get attenuationColor(){return this[me](),this[vt].attenuationColor.toArray()}setThicknessFactor(e){this[me]();for(let t of this[Qe])t.thickness=e;this[tt]()}setAttenuationDistance(e){this[me]();for(let t of this[Qe])t.attenuationDistance=e;this[tt]()}setAttenuationColor(e){this[me]();let t=this.colorFromRgb(e);for(let i of this[Qe])i.attenuationColor.set(t);this[tt]()}get specularFactor(){return this[me](),this[vt].specularIntensity}get specularTexture(){return this[me](),this[ui].get(Te.Specular)}get specularColorFactor(){return this[me](),this[vt].specularColor.toArray()}get specularColorTexture(){return this[me](),this[ui].get(Te.SheenColor)}setSpecularFactor(e){this[me]();for(let t of this[Qe])t.specularIntensity=e;this[tt]()}setSpecularColorFactor(e){this[me]();let t=this.colorFromRgb(e);for(let i of this[Qe])i.specularColor.set(t);this[tt]()}get iridescenceFactor(){return this[me](),this[vt].iridescence}get iridescenceTexture(){return this[me](),this[ui].get(Te.Iridescence)}get iridescenceIor(){return this[me](),this[vt].iridescenceIOR}get iridescenceThicknessMinimum(){return this[me](),this[vt].iridescenceThicknessRange[0]}get iridescenceThicknessMaximum(){return this[me](),this[vt].iridescenceThicknessRange[1]}get iridescenceThicknessTexture(){return this[me](),this[ui].get(Te.IridescenceThickness)}setIridescenceFactor(e){this[me]();for(let t of this[Qe])t.iridescence=e;this[tt]()}setIridescenceIor(e){this[me]();for(let t of this[Qe])t.iridescenceIOR=e;this[tt]()}setIridescenceThicknessMinimum(e){this[me]();for(let t of this[Qe])t.iridescenceThicknessRange[0]=e;this[tt]()}setIridescenceThicknessMaximum(e){this[me]();for(let t of this[Qe])t.iridescenceThicknessRange[1]=e;this[tt]()}get anisotropyStrength(){return this[me](),this[vt].anisotropy}get anisotropyRotation(){return this[me](),this[vt].anisotropyRotation}get anisotropyTexture(){return this[me](),this[ui].get(Te.Anisotropy)}setAnisotropyStrength(e){this[me]();for(let t of this[Qe])t.anisotropy=e;this[tt]()}setAnisotropyRotation(e){this[me]();for(let t of this[Qe])t.anisotropyRotation=e;this[tt]()}},_x=class{constructor(s){this.name=\"\",this.children=new Array,this.name=s}},bo=class extends _x{constructor(e,t,i,n){super(e.name),this.materials=new Map,this.variantToMaterialMap=new Map,this.initialMaterialIdx=0,this.activeMaterialIdx=0,this.mesh=e;let{gltf:r,threeGLTF:a,threeObjectMap:o}=n;this.parser=a.parser,this.modelVariants=i,this.mesh.userData.variantData=i;let l=o.get(e.material);l.materials!=null?this.initialMaterialIdx=this.activeMaterialIdx=l.materials:console.error(`Primitive (${e.name}) missing initial material reference.`);let c=e.userData.associations||{};if(c.meshes==null)return void console.error(\"Mesh is missing primitive index association\");let h=((r.meshes||[])[c.meshes].primitives||[])[c.primitives];if(h!=null){if(h.material!=null)this.materials.set(h.material,t[h.material]);else{let u=t.findIndex(d=>d.name===\"Default\");u>=0?this.materials.set(u,t[u]):console.warn(\"gltfPrimitive has no material!\")}if(h.extensions&&h.extensions.KHR_materials_variants){let u=h.extensions.KHR_materials_variants,d=a.parser.json.extensions.KHR_materials_variants.variants;for(let p of u.mappings){let A=t[p.material];this.materials.set(p.material,A);for(let g of p.variants){let{name:b}=d[g];this.variantToMaterialMap.set(g,A),A[Zr].add(g),i.has(b)||i.set(b,{name:b,index:g})}}}}else console.error(\"Mesh primitive definition is missing.\")}async setActiveMaterial(e){let t=this.materials.get(e);if(e!==this.activeMaterialIdx){let i=t[Qe],n=await t[Sg]();this.mesh.material=n??i.values().next().value,this.parser.assignFinalMaterial(this.mesh),i.add(this.mesh.material),this.activeMaterialIdx=e}return this.mesh.material}getActiveMaterial(){return this.materials.get(this.activeMaterialIdx)}getMaterial(e){return this.materials.get(e)}async enableVariant(e){if(e==null)return this.setActiveMaterial(this.initialMaterialIdx);if(this.variantToMaterialMap!=null&&this.modelVariants.has(e)){let t=this.modelVariants.get(e);return this.enableVariantHelper(t.index)}return null}async enableVariantHelper(e){if(this.variantToMaterialMap!=null&&e!=null){let t=this.variantToMaterialMap.get(e);if(t!=null)return this.setActiveMaterial(t.index)}return null}async instantiateVariants(){if(this.variantToMaterialMap!=null)for(let e of this.variantToMaterialMap.keys()){let t=this.mesh.userData.variantMaterials.get(e);if(t.material!=null)continue;let i=await this.enableVariantHelper(e);i!=null&&(t.material=i)}}get variantInfo(){return this.variantToMaterialMap}addVariant(e,t){if(!this.ensureVariantIsUnused(t))return!1;this.modelVariants.has(t)||this.modelVariants.set(t,{name:t,index:this.modelVariants.size});let i=this.modelVariants.get(t).index;return e[Zr].add(i),this.variantToMaterialMap.set(i,e),this.materials.set(e.index,e),this.updateVariantUserData(i,e),!0}deleteVariant(e){if(this.variantInfo.has(e)){this.variantInfo.delete(e);let t=this.mesh.userData.variantMaterials;t?.delete(e)}}updateVariantUserData(e,t){t[Zr].add(e),this.mesh.userData.variantData=this.modelVariants,this.mesh.userData.variantMaterials=this.mesh.userData.variantMaterials||new Map,this.mesh.userData.variantMaterials.set(e,{material:t[Qe].values().next().value,gltfMaterialIndex:t.index})}ensureVariantIsUnused(e){let t=this.modelVariants.get(e);return t==null||!this.variantInfo.has(t.index)||(console.warn(`Primitive cannot add variant '${e}' for this material, it already exists.`),!1)}};var Ub,Nb,Ob,kb,Qb,Gb,ws=Symbol(\"materials\"),Vl=Symbol(\"hierarchy\"),zb=Symbol(\"roots\"),yn=Symbol(\"primitives\"),Ex=Symbol(\"prepareVariantsForExport\"),Sx=Symbol(\"switchVariant\"),Cg=Symbol(\"materialFromPoint\"),wg=Symbol(\"nodeFromPoint\"),Cx=Symbol(\"nodeFromIndex\"),ni=Symbol(\"variantData\"),Mg=Symbol(\"availableVariants\"),Sd=Symbol(\"modelOnUpdate\"),Hb=Symbol(\"cloneMaterial\"),Ig=class{constructor(e,t,i,n){this.gltf=e,this.gltfElementMap=t,this.mapKey=i,this.doLazyLoad=n}},Ih=class{constructor(e,t=()=>{}){this[Ub]=new Array,this[Nb]=new Array,this[Ob]=new Array,this[kb]=new Array,this[Qb]=()=>{},this[Gb]=new Map,this[Sd]=t;let{gltf:i,threeGLTF:n,gltfElementMap:r}=e;for(let[l,c]of i.materials.entries()){let h=r.get(c);if(h!=null)this[ws].push(new fo(t,l,!0,this[ni],h,c.name));else{let u=(i.materials||[])[l],d=new Set;r.set(u,d);let p=async()=>{let A=await n.parser.getDependency(\"material\",l);return d.add(A),A};this[ws].push(new fo(t,l,!1,this[ni],d,c.name,new Ig(i,r,u,p)))}}let a=new Map,o=new Array;for(let l of n.scene.children)o.push(l);for(;o.length>0;){let l=o.pop(),c=null;l instanceof Je?(c=new bo(l,this.materials,this[ni],e),this[yn].push(c)):c=new _x(l.name);let h=a.get(l);h!=null?h.children.push(c):this[zb].push(c),this[Vl].push(c);for(let u of l.children)o.push(u),a.set(l,c)}}get materials(){return this[ws]}[(Ub=ws,Nb=Vl,Ob=zb,kb=yn,Qb=Sd,Gb=ni,Mg)](){let e=Array.from(this[ni].values());return e.sort((t,i)=>t.index-i.index),e.map(t=>t.name)}getMaterialByName(e){let t=this[ws].filter(i=>i.name===e);return t.length>0?t[0]:null}[Cx](e,t){let i=this[Vl].find(n=>{if(n instanceof bo){let{meshes:r,primitives:a}=n.mesh.userData.associations;if(r==e&&a==t)return!0}return!1});return i??null}[wg](e){return this[Vl].find(i=>i instanceof bo&&i.mesh===e.object)||null}[Cg](e){let t=this[wg](e);return t?t.getActiveMaterial():null}async[Sx](e){for(let t of this[yn])await t.enableVariant(e);for(let t of this.materials)t[ao](!1);for(let t of this[yn])this.materials[t.getActiveMaterial().index][ao](!0)}async[Ex](){let e=new Array;for(let t of this[yn])e.push(t.instantiateVariants());await Promise.all(e)}[Hb](e,t){let i=this.materials[e];i.isLoaded||console.error(`Cloning an unloaded material,\n call 'material.ensureLoaded() before cloning the material.`);let n=i[Qe],r=new Set;for(let[o,l]of n.entries()){let c=l.clone();c.name=t+(n.size>1?\"_inst\"+o:\"\"),r.add(c)}let a=new fo(this[Sd],this[ws].length,!1,this[ni],r,t);return this[ws].push(a),a}createMaterialInstanceForVariant(e,t,i,n=!0){let r=null;for(let a of this[yn]){let o=this[ni].get(i);o!=null&&a.variantInfo.has(o.index)||a.getMaterial(e)!=null&&(this.hasVariant(i)||this.createVariant(i),r==null&&(r=this[Hb](e,t)),a.addVariant(r,i))}if(n&&r!=null){r[ao](!0),this.materials[e][ao](!1);for(let a of this[yn])a.enableVariant(i)}return r}createVariant(e){this[ni].has(e)?console.warn(`Variant '${e}'' already exists`):this[ni].set(e,{name:e,index:this[ni].size})}hasVariant(e){return this[ni].has(e)}setMaterialToVariant(e,t){if(this[Mg]().find(i=>i===t)!=null)if(e<0||e>=this.materials.length)console.error(\"setMaterialToVariant(): materialIndex is out of bounds.\");else for(let i of this[yn]){let n=i.getMaterial(e);n!=null&&i.addVariant(n,t)}else console.warn(`Can't add material to '${t}', the variant does not exist.'`)}updateVariantName(e,t){let i=this[ni].get(e);i!=null&&(i.name=t,this[ni].set(t,i),this[ni].delete(e))}deleteVariant(e){let t=this[ni].get(e);if(t!=null){for(let i of this.materials)i.hasVariant(e)&&i[Zr].delete(t.index);for(let i of this[yn])i.deleteVariant(t.index);this[ni].delete(e)}}};var Cd=function(s,e,t,i){for(var n,r=arguments.length,a=r<3?e:i===null?i=Object.getOwnPropertyDescriptor(e,t):i,o=s.length-1;o>=0;o--)(n=s[o])&&(a=(r<3?n(a):r>3?n(e,t,a):n(e,t))||a);return r>3&&a&&Object.defineProperty(e,t,a),a},gc=Symbol(\"currentGLTF\"),mc=Symbol(\"originalGltfJson\"),Ps=Symbol(\"model\"),ja=Symbol(\"extraModels\"),ql=Symbol(\"getOnUpdateMethod\"),Wa=Symbol(\"buildTexture\");var Tg=class extends Je{constructor(){super(void 0,new Xt({depthWrite:!1})),this.height=0,this.radius=0,this.resolution=0,this.userData.noHit=!0}get map(){return this.material.map}set map(e){this.material.map=e}isUsable(){return this.height>0&&this.radius>0&&this.geometry!=null&&this.map!=null}updateGeometry(e=this.height,t=this.radius,i=128){e==this.height&&t==this.radius&&i==this.resolution||(this.height=e,this.radius=t,this.resolution=i,e>0&&t>0&&(this.geometry.dispose(),this.geometry=(function(n,r,a){let o=new zp(r,2*a,a);o.scale(1,1,-1);let l=o.getAttribute(\"position\"),c=new T;for(let h=0;h0&&--this.referenceCount,this.referenceCount===0}updatePosition(e){if(e==null)return;let t=In(e)[0].terms;for(let i=0;i<3;++i)this.position.setComponent(i,nn(t[i]).number);this.updateMatrixWorld()}updateNormal(e){if(e==null)return;let t=In(e)[0].terms;for(let i=0;i<3;++i)this.normal.setComponent(i,t[i].number)}updateSurface(){let{mesh:e,tri:t,bary:i}=this;if(e==null||t==null||i==null)return;e.getVertexPosition(t.x,jl),e.getVertexPosition(t.y,wd),e.getVertexPosition(t.z,Md),jl.toArray(Wl.elements,0),wd.toArray(Wl.elements,3),Md.toArray(Wl.elements,6),this.position.copy(i).applyMatrix3(Wl);let n=this.parent;n.worldToLocal(e.localToWorld(this.position)),Vb.set(jl,wd,Md),Vb.getNormal(this.normal).transformDirection(e.matrixWorld);let r=n.parent;qb.setFromAxisAngle(jl.set(0,1,0),-r.rotation.y),this.normal.applyQuaternion(qb)}orient(e){this.pivotEl.style.transform=`rotate(${e}rad)`}updateVisibility(e){this.element.classList.toggle(\"hide\",!e),this.slot.assignedNodes().forEach(t=>{if(t.nodeType!==Node.ELEMENT_NODE)return;let i=t,n=i.dataset.visibilityAttribute;if(n!=null){let r=`data-${n}`;i.toggleAttribute(r,e)}i.dispatchEvent(new CustomEvent(\"hotspot-visibility\",{detail:{visible:e}}))}),this.initialized=!0}},iw={name:\"HorizontalBlurShader\",uniforms:{tDiffuse:{value:null},h:{value:1/512}},vertexShader:`\n\n\t\tvarying vec2 vUv;\n\n\t\tvoid main() {\n\n\t\t\tvUv = uv;\n\t\t\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\n\t\t}`,fragmentShader:`\n\n\t\tuniform sampler2D tDiffuse;\n\t\tuniform float h;\n\n\t\tvarying vec2 vUv;\n\n\t\tvoid main() {\n\n\t\t\tvec4 sum = vec4( 0.0 );\n\n\t\t\tsum += texture2D( tDiffuse, vec2( vUv.x - 4.0 * h, vUv.y ) ) * 0.051;\n\t\t\tsum += texture2D( tDiffuse, vec2( vUv.x - 3.0 * h, vUv.y ) ) * 0.0918;\n\t\t\tsum += texture2D( tDiffuse, vec2( vUv.x - 2.0 * h, vUv.y ) ) * 0.12245;\n\t\t\tsum += texture2D( tDiffuse, vec2( vUv.x - 1.0 * h, vUv.y ) ) * 0.1531;\n\t\t\tsum += texture2D( tDiffuse, vec2( vUv.x, vUv.y ) ) * 0.1633;\n\t\t\tsum += texture2D( tDiffuse, vec2( vUv.x + 1.0 * h, vUv.y ) ) * 0.1531;\n\t\t\tsum += texture2D( tDiffuse, vec2( vUv.x + 2.0 * h, vUv.y ) ) * 0.12245;\n\t\t\tsum += texture2D( tDiffuse, vec2( vUv.x + 3.0 * h, vUv.y ) ) * 0.0918;\n\t\t\tsum += texture2D( tDiffuse, vec2( vUv.x + 4.0 * h, vUv.y ) ) * 0.051;\n\n\t\t\tgl_FragColor = sum;\n\n\t\t}`},nw={name:\"VerticalBlurShader\",uniforms:{tDiffuse:{value:null},v:{value:1/512}},vertexShader:`\n\n\t\tvarying vec2 vUv;\n\n\t\tvoid main() {\n\n\t\t\tvUv = uv;\n\t\t\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\n\t\t}`,fragmentShader:`\n\n\t\tuniform sampler2D tDiffuse;\n\t\tuniform float v;\n\n\t\tvarying vec2 vUv;\n\n\t\tvoid main() {\n\n\t\t\tvec4 sum = vec4( 0.0 );\n\n\t\t\tsum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 4.0 * v ) ) * 0.051;\n\t\t\tsum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 3.0 * v ) ) * 0.0918;\n\t\t\tsum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 2.0 * v ) ) * 0.12245;\n\t\t\tsum += texture2D( tDiffuse, vec2( vUv.x, vUv.y - 1.0 * v ) ) * 0.1531;\n\t\t\tsum += texture2D( tDiffuse, vec2( vUv.x, vUv.y ) ) * 0.1633;\n\t\t\tsum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 1.0 * v ) ) * 0.1531;\n\t\t\tsum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 2.0 * v ) ) * 0.12245;\n\t\t\tsum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 3.0 * v ) ) * 0.0918;\n\t\t\tsum += texture2D( tDiffuse, vec2( vUv.x, vUv.y + 4.0 * v ) ) * 0.051;\n\n\t\t\tgl_FragColor = sum;\n\n\t\t}`};function jb(s,e,t){return(1-t)*s+t*e}var Bg=class extends ct{constructor(e,t,i){super(),this.camera=new Ln,this.renderTarget=null,this.renderTargetBlur=null,this.depthMaterial=new nh,this.horizontalBlurMaterial=new Jt(iw),this.verticalBlurMaterial=new Jt(nw),this.intensity=0,this.softness=1,this.boundingBox=new Wt,this.size=new T,this.maxDimension=0,this.isAnimated=!1,this.needsUpdate=!1;let{camera:n}=this;n.rotation.x=Math.PI/2,n.left=-.5,n.right=.5,n.bottom=-.5,n.top=.5,this.add(n);let r=new Ii,a=new Xt({opacity:1,transparent:!0,side:1});this.floor=new Je(r,a),this.floor.userData.noHit=!0,n.add(this.floor),this.blurPlane=new Je(r),this.blurPlane.visible=!1,n.add(this.blurPlane),e.target.add(this),this.depthMaterial.onBeforeCompile=function(o){o.fragmentShader=o.fragmentShader.replace(/gl_FragColor\\s*=\\s*vec4\\(\\s*vec3\\(\\s*1\\.0\\s*-\\s*fragCoordZ\\s*\\),\\s*opacity\\s*\\);/,\"gl_FragColor = vec4( vec3( 0.0 ), ( 1.0 - fragCoordZ ) * opacity );\")},this.depthMaterial.side=2,this.horizontalBlurMaterial.depthTest=!1,this.verticalBlurMaterial.depthTest=!1,this.setScene(e,t,i)}setScene(e,t,i){let{boundingBox:n,size:r,rotation:a,position:o}=this;if(this.isAnimated=e.animationNames.length>0,this.boundingBox.copy(e.boundingBox),this.size.copy(e.size),this.maxDimension=Math.max(r.x,r.y,r.z)*(this.isAnimated?2:1),this.boundingBox.getCenter(o),i===\"back\"){let{min:l,max:c}=n;[l.y,l.z]=[l.z,l.y],[c.y,c.z]=[c.z,c.y],[r.y,r.z]=[r.z,r.y],a.x=Math.PI/2,a.y=Math.PI}else a.x=0,a.y=0;if(this.isAnimated){let l=n.min.y,c=n.max.y;r.y=this.maxDimension,n.expandByVector(r.subScalar(this.maxDimension).multiplyScalar(-.5)),n.min.y=l,n.max.y=c,r.set(this.maxDimension,c-l,this.maxDimension)}i===\"bottom\"?o.y=n.min.y:o.z=n.min.y,this.setSoftness(t)}setSoftness(e){this.softness=e;let{size:t,camera:i}=this,n=this.isAnimated?2:1,r=n*Math.pow(2,9-3*e);this.setMapSize(r);let a=t.y/2,o=t.y*n;i.near=0,i.far=jb(o,a,e),this.depthMaterial.opacity=1/e,i.updateProjectionMatrix(),this.setIntensity(this.intensity),this.setOffset(0)}setMapSize(e){let{size:t}=this;this.isAnimated&&(e*=2);let i=Math.floor(t.x>t.z?e:e*t.x/t.z),n=Math.floor(t.x>t.z?e*t.z/t.x:e),r=10+i,a=10+n;if(this.renderTarget==null||this.renderTarget.width===r&&this.renderTarget.height===a||(this.renderTarget.dispose(),this.renderTarget=null,this.renderTargetBlur.dispose(),this.renderTargetBlur=null),this.renderTarget==null){let c={format:_t};this.renderTarget=new Zt(r,a,c),this.renderTargetBlur=new Zt(r,a,c),this.floor.material.map=this.renderTarget.texture}let o=t.x*(1+10/i),l=t.z*(1+10/n);this.camera.left=-o/2,this.camera.right=o/2,this.camera.bottom=-l/2,this.camera.top=l/2,this.camera.scale.set(1,1,1),this.floor.scale.set(o,l,1),this.blurPlane.scale.set(o,l,1),this.needsUpdate=!0}setIntensity(e){this.intensity=e,e>0?(this.visible=!0,this.floor.visible=!0,this.floor.material.opacity=e*jb(.3,1,this.softness*this.softness)):(this.visible=!1,this.floor.visible=!1)}getIntensity(){return this.intensity}setOffset(e){this.floor.position.z=-e+this.gap()}gap(){return .001*this.maxDimension}render(e,t){t.overrideMaterial=this.depthMaterial,this.floor.visible=!1;let i=e.xr.enabled;e.xr.enabled=!1;let n=e.getRenderTarget();e.setRenderTarget(this.renderTarget);let r=e.getClearAlpha();e.setClearAlpha(0);let a=e.state;a!=null&&(a.buffers.color.setMask(!0),a.buffers.depth.setMask(!0)),e.clear();let o=e.autoClear;e.autoClear=!1,e.render(t,this.camera),e.autoClear=o,t.overrideMaterial=null,this.floor.visible=!0,this.blurShadow(e),e.xr.enabled=i,e.setRenderTarget(n),e.setClearAlpha(r)}blurShadow(e){let{camera:t,horizontalBlurMaterial:i,verticalBlurMaterial:n,renderTarget:r,renderTargetBlur:a,blurPlane:o}=this;o.visible=!0,o.material=i,i.uniforms.h.value=1/this.renderTarget.width,i.uniforms.tDiffuse.value=this.renderTarget.texture,e.setRenderTarget(a),e.render(o,t),o.material=n,n.uniforms.v.value=1/this.renderTarget.height,n.uniforms.tDiffuse.value=this.renderTargetBlur.texture,e.setRenderTarget(r),e.render(o,t),o.visible=!1}dispose(){this.renderTarget!=null&&this.renderTarget.dispose(),this.renderTargetBlur!=null&&this.renderTargetBlur.dispose(),this.depthMaterial.dispose(),this.horizontalBlurMaterial.dispose(),this.verticalBlurMaterial.dispose(),this.floor.material.dispose(),this.floor.geometry.dispose(),this.blurPlane.geometry.dispose(),this.removeFromParent()}};var Id=new T,Wb=new T,Xb=new T,Xl=new class{constructor(s,e,t=0,i=1/0){this.ray=new $s(s,e),this.near=t,this.far=i,this.camera=null,this.layers=new Do,this.params={Mesh:{},Line:{threshold:1},LOD:{},Points:{threshold:1},Sprite:{}}}set(s,e){this.ray.set(s,e)}setFromCamera(s,e){e.isPerspectiveCamera?(this.ray.origin.setFromMatrixPosition(e.matrixWorld),this.ray.direction.set(s.x,s.y,.5).unproject(e).sub(this.ray.origin).normalize(),this.camera=e):e.isOrthographicCamera?(this.ray.origin.set(s.x,s.y,(e.near+e.far)/(e.near-e.far)).unproject(e),this.ray.direction.set(0,0,-1).transformDirection(e.matrixWorld),this.camera=e):Ie(\"Raycaster: Unsupported camera type: \"+e.type)}setFromXRController(s){return _f.identity().extractRotation(s.matrixWorld),this.ray.origin.setFromMatrixPosition(s.matrixWorld),this.ray.direction.set(0,0,-1).applyMatrix4(_f),this}intersectObject(s,e=!0,t=[]){return cA(s,this,t,e),t.sort(Ef),t}intersectObjects(s,e=!0,t=[]){for(let i=0,n=s.length;i{}){if((!e||e===this.url)&&t.join(\",\")===this.extraUrls.join(\",\"))return void i(1);if(this.reset(),this.url=e,this.extraUrls=t,this.externalRenderer!=null){let l=await this.externalRenderer.load(i);return this.boundingSphere.radius=l.framedRadius,void(this.idealAspect=l.fieldOfViewAspect)}this.cancelPendingSourceChange!=null&&(this.cancelPendingSourceChange(),this.cancelPendingSourceChange=null);let n=[];try{let l=[];e&&l.push(e),t&&l.push(...t),l.length>0&&(n=await new Promise((c,h)=>{this.cancelPendingSourceChange=()=>h(),(async()=>{try{let u=await Promise.all(l.map(d=>this.element[et].loader.load(d,this.element,i)));c(u)}catch(u){h(u)}})()}))}catch(l){if(l==null)return;throw l}this.cancelPendingSourceChange=null,this.reset(),this.url=e,this.extraUrls=t,this._currentGLTFs=n;for(let l of n)l!=null?(this._models.push(l.scene),this.target.add(l.scene),this.mixers.push(new uh(l.scene)),this.mixerPausedStates.push(!1),this.currentAnimationActions.push(null)):(this.mixers.push(new uh(this.target)),this.mixerPausedStates.push(!1),this.currentAnimationActions.push(null));let r=new Map,a=[],o=[];for(let l of n)for(let c of l.animations||[])r.set(c.name,c),a.push(c.name),o.push(c);this.animations=o,this.animationsByName=r,this.animationNames=a,await this.setupScene()}async setupScene(){this.applyTransform(),this.updateBoundingBox(),await this.updateFraming(),this.updateShadow(),this.setShadowIntensity(this.shadowIntensity),this.setGroundedSkybox()}updateModelTransforms(e,t,i,n){let r=this._models[e];if(r){if(t){let a=t.split(\" \").map(o=>o.trim()).filter(o=>o.length>0).map(Number);a.length!==3||a.some(isNaN)||r.position.set(a[0],a[1],a[2])}if(n){let a=n.split(\" \").map(o=>o.trim()).filter(o=>o.length>0).map(Number);a.length!==1||isNaN(a[0])?a.length!==3||a.some(isNaN)||r.scale.set(a[0],a[1],a[2]):r.scale.setScalar(a[0])}r.updateMatrixWorld(!0),this.boundsAndShadowDirty=!0,this.queueRender()}}updateBoundingBoxAndShadowIfDirty(){this.boundsAndShadowDirty&&(this.boundsAndShadowDirty=!1,this.updateBoundingBox(),this.updateShadow())}reset(){this.url=null,this.renderCount=0,this.queueRender(),this.shadow!=null&&this.shadow.setIntensity(0),this.bakedShadows.clear();let{_models:e}=this;for(let i of e)i?.removeFromParent();this._models=[];let t=this._currentGLTFs;for(let i of t)i?.dispose();this._currentGLTFs=[];for(let i of this.currentAnimationActions)i?.stop();this.currentAnimationActions=[];for(let i of this.mixers)i.stopAllAction(),i.uncacheRoot(this);this.mixers=[],this.mixerPausedStates=[]}dispose(){this.reset(),this.shadow!=null&&(this.shadow.dispose(),this.shadow=null),this.element[gc]=null,this.element[mc]=null,this.element[Ps]=null}get currentGLTF(){return this._currentGLTFs[0]||null}get currentGLTFs(){return this._currentGLTFs}setSize(e,t){if(this.width!==e||this.height!==t){if(this.width=Math.max(e,1),this.height=Math.max(t,1),this.annotationRenderer.setSize(e,t),this.aspect=this.width/this.height,this.externalRenderer!=null){let i=window.devicePixelRatio;this.externalRenderer.resize(e*i,t*i)}this.queueRender()}}markBakedShadow(e){e.userData.noHit=!0,this.bakedShadows.add(e)}unmarkBakedShadow(e){e.userData.noHit=!1,e.visible=!0,this.bakedShadows.delete(e),this.boundingBox.expandByObject(e)}findBakedShadows(e){let t=new Wt;e.traverse(i=>{let n=i;if(!n.material||!n.material.transparent)return;t.setFromObject(n);let r=t.getSize(sw),a=Math.min(r.x,r.y,r.z);Math.max(r.x,r.y,r.z)<100*a||this.markBakedShadow(n)})}checkBakedShadows(){let{min:e,max:t}=this.boundingBox,i=new Wt;this.boundingBox.getSize(this.size);for(let n of this.bakedShadows)i.setFromObject(n),i.min.y=t.x&&i.min.z<=e.z&&i.max.z>=t.z||i.min.z=t.x&&i.min.y<=e.y&&i.max.y>=t.y||this.unmarkBakedShadow(n)}applyTransform(){let{models:e}=this;if(e.length===0)return;let t=In(this.element.orientation)[0].terms,i=nn(t[0]).number,n=nn(t[1]).number,r=nn(t[2]).number,a=In(this.element.scale)[0].terms;for(let o of e)o.quaternion.setFromEuler(new Mi(n,r,i,\"YXZ\")),o.scale.set(a[0].number,a[1].number,a[2].number)}updateBoundingBox(){let{models:e}=this;if(e.length===0)return;for(let n of e)this.target.remove(n),this.findBakedShadows(n);let t=(n,r)=>n.expandByPoint(r);this.setBakedShadowVisibility(!1);let i=new Wt;for(let n of e)i=Tl(n,t,i);if(this.boundingBox=i,this.boundingBox.isEmpty()){this.setBakedShadowVisibility(!0),this.bakedShadows.forEach(n=>this.unmarkBakedShadow(n)),i=new Wt;for(let n of e)i=Tl(n,t,i);this.boundingBox=i}this.checkBakedShadows(),this.setBakedShadowVisibility(),this.boundingBox.getSize(this.size);for(let n of e)this.target.add(n)}async updateFraming(){let{models:e}=this;if(e.length===0)return;for(let o of e)this.target.remove(o);this.setBakedShadowVisibility(!1);let{center:t}=this.boundingSphere;this.element.requestUpdate(\"cameraTarget\"),await this.element.updateComplete,t.copy(this.getTarget());let i=(o,l)=>Math.max(o,t.distanceToSquared(l)),n=0;for(let o of e)n=Math.max(n,Tl(o,i,0));this.boundingSphere.radius=Math.sqrt(n);let r=(o,l)=>{l.sub(t);let c=Math.sqrt(l.x*l.x+l.z*l.z);return Math.max(o,c/(this.idealCameraDistance()-Math.abs(l.y)))},a=0;for(let o of e)a=Math.max(a,Tl(o,r,0));this.idealAspect=a/Math.tan(this.framedFoVDeg/2*Math.PI/180),this.setBakedShadowVisibility();for(let o of e)this.target.add(o)}setBakedShadowVisibility(e=this.shadowIntensity<=0){for(let t of this.bakedShadows)t.visible=e}idealCameraDistance(){let e=this.framedFoVDeg/2*Math.PI/180;return this.boundingSphere.radius/Math.sin(e)}adjustedFoV(e){let t=Math.tan(e/2*Math.PI/180)*Math.max(1,this.idealAspect/this.aspect);return 2*Math.atan(t)*180/Math.PI}getNDC(e,t){if(this.xrCamera!=null)Xa.set(e/window.screen.width,t/window.screen.height);else{let i=this.element.getBoundingClientRect();Xa.set((e-i.x)/this.width,(t-i.y)/this.height)}return Xa.multiplyScalar(2).subScalar(1),Xa.y*=-1,Xa}getSize(){return{width:this.width,height:this.height}}setEnvironmentAndSkybox(e,t){this.element[et].arRenderer.presentedScene!==this&&(this.environment=e,this.setBackground(t),this.queueRender())}setBackground(e){this.groundedSkybox.map=e,this.groundedSkybox.isUsable()?(this.target.add(this.groundedSkybox),this.background=null):(this.target.remove(this.groundedSkybox),this.background=e)}farRadius(){return this.boundingSphere.radius*(this.groundedSkybox.parent!=null?10:1)}setGroundedSkybox(){let e=In(this.element.skyboxHeight)[0].terms[0],t=nn(e).number,i=10*this.boundingSphere.radius;this.groundedSkybox.updateGeometry(t,i),this.groundedSkybox.position.y=t-(this.shadow?2*this.shadow.gap():0),this.setBackground(this.groundedSkybox.map)}setTarget(e,t,i){this.goalTarget.set(-e,-t,-i)}setTargetDamperDecayTime(e){this.targetDamperX.setDecayTime(e),this.targetDamperY.setDecayTime(e),this.targetDamperZ.setDecayTime(e)}getTarget(){return this.goalTarget.clone().multiplyScalar(-1)}getDynamicTarget(){return this.target.position.clone().multiplyScalar(-1)}jumpToGoal(){this.updateTarget(1e4)}updateTarget(e){let t=this.goalTarget,i=this.target.position;if(t.equals(i))return!1;{let n=this.boundingSphere.radius/10,{x:r,y:a,z:o}=i;return r=this.targetDamperX.update(r,t.x,e,n),a=this.targetDamperY.update(a,t.y,e,n),o=this.targetDamperZ.update(o,t.z,e,n),this.groundedSkybox.position.x=-r,this.groundedSkybox.position.z=-o,this.target.position.set(r,a,o),this.target.updateMatrixWorld(),this.queueRender(),!0}}pointTowards(e,t){let{x:i,z:n}=this.position;this.yaw=Math.atan2(e-i,t-n)}get model(){return this._models[0]||null}get models(){return this._models}set yaw(e){this.scenePivot.rotation.y=e,this.groundedSkybox.rotation.y=-e,this.queueRender()}get yaw(){return this.scenePivot.rotation.y}set animationTime(e){for(let t of this.mixers)t.setTime(e);this.queueShadowRender()}get animationTime(){let e=0;for(let t of this.currentAnimationActions)if(t!=null){let i=t.time,n=Math.max(t._loopCount,0);t.loop===Dc&&!(1&~n)&&(i=(t.getClip()?t.getClip().duration:0)-t.time),i>e&&(e=i)}return e}set animationTimeScale(e){for(let t of this.mixers)t.timeScale=e}get animationTimeScale(){return this.mixers.length>0?this.mixers[0].timeScale:1}get duration(){let e=0;for(let t of this.currentAnimationActions)if(t!=null&&t.getClip()){let i=t.getClip().duration;i>e&&(e=i)}return e}get hasActiveAnimation(){return this.currentAnimationActions.some(e=>e!=null)}playAnimation(e=null,t=0,i=2201,n=1/0,r=null){let a=r??0,o=r!=null?r+1:this._models.length;for(let l=a;l=0;d--)if(h[d].name===e){u=h[d];break}if(u==null){let d=parseInt(e);!isNaN(d)&&d>=0&&d0&&this.animationTime==this.duration&&(this.animationTime=0)),A.setLoop(i,n),A.enabled=!0,A.clampWhenFinished=!0,A.play()}catch(d){console.error(d)}}}appendAnimation(e=\"\",t=2201,i=1/0,n=1,r=1,a=!1,o=!1,l=!0,c=null,h=!1,u=null){if(this.currentGLTF==null||e===this.element.animationName)return;let{animations:d}=this;if(d==null||d.length===0)return;let p=e?this.animationsByName.get(e):null;if(p==null)return;if(typeof i==\"string\"?isNaN(parseFloat(i))?(i=1/0,console.warn(`Invalid repetitionCount value: ${i}. Using default: Infinity`)):(parseInt(i)<1&&console.warn(`Invalid repetitionCount value: ${i}. Using 1 as minimum.`),i=Math.max(parseInt(i),1)):typeof i==\"number\"&&i<1?(i=1,console.warn(`Invalid repetitionCount value: ${i}. Using 1 value as minimum.`)):console.warn(`Invalid repetitionCount value: ${i}. Using default: Infinity`),i===1&&t!==To&&(t=To),typeof n==\"string\"){let f=parseFloat(n);isNaN(f)||f<0||f>1?(n=1,console.warn(`Invalid weight value: ${n}. Using default: 1`)):n=f}if(typeof r==\"string\"){let f=parseFloat(r);isNaN(f)||f<0?(r=1,console.warn(`Invalid timeScale value: ${r}. Using default: 1`)):r=f}if(typeof c==\"string\"){let f=parseFloat(c);isNaN(f)?(c=null,console.warn(`Invalid time value: ${c}. Using default: 0 or previous time`)):c=f}let{shouldFade:A,duration:g}=this.parseFadeValue(a,!1,1.25),b=!1,m=0;typeof o==\"boolean\"?(b=o,m=o?1.25:0):typeof o==\"number\"?(b=o>0,m=Math.max(o,0),o<0&&console.warn(`Invalid warp value: ${o}. Using default: false`)):typeof o==\"string\"&&(o.toLowerCase().trim()===\"true\"?(b=!0,m=1.25):o.toLowerCase().trim()===\"false\"?b=!1:isNaN(parseFloat(o))?console.warn(`Invalid warp value: ${o}. Using default: false`):(m=Math.max(parseFloat(o),0),b=m>0,m<=0&&console.warn(`Invalid warp value: ${o}. Using default: false`)));try{h&&this.appendedAnimations.includes(e)&&(this.markedAnimations.map(v=>v.name).includes(e)||this.markedAnimations.push({name:e,loopMode:t,repetitionCount:i}));let f=u??0,y=u!=null?u+1:this.mixers.length;for(let v=f;v=0&&(_.weight=Math.min(Math.max(n,0),1)),b?_.warp(l?w:0,r,m):_.timeScale=r,_.isRunning()||(_.time==p.duration&&_.stop(),_.setLoop(t,i),_.paused=!1,_.enabled=!0,_.clampWhenFinished=!0,_.play())}this.appendedAnimations.includes(e)||this.element[Y].appendedAnimations.push(e)}catch(f){console.error(f)}}parseFadeValue(e,t=!0,i=1.5){if(typeof e==\"boolean\")return{shouldFade:e,duration:e?i:0};if(typeof e==\"number\"){let n=Math.max(e,0);return{shouldFade:n>0,duration:n}}if(typeof e==\"string\"){let n=e.toLowerCase().trim();if(n===\"true\")return{shouldFade:!0,duration:i};if(n===\"false\")return{shouldFade:!1,duration:0};let r=parseFloat(n);if(!isNaN(r)){let a=Math.max(r,0);return{shouldFade:a>0,duration:a}}}return console.warn(`Invalid fade value: ${e}. Using default: ${t}`),{shouldFade:t,duration:t?i:0}}detachAnimation(e=\"\",t=!0,i=null){if(this.currentGLTF==null||e===this.element.animationName)return;let{animations:n}=this;if(n==null||n.length===0)return;let r=e?this.animationsByName.get(e):null;if(r==null)return;let{shouldFade:a,duration:o}=this.parseFadeValue(t,!0,1.5);try{let l=i??0,c=i!=null?i+1:this.mixers.length;for(let h=l;hh!==e)}catch(l){console.error(l)}}updateAnimationLoop(e=\"\",t=2201,i=1/0,n=null){if(this.currentGLTF==null||e===this.element.animationName)return;let{animations:r}=this;if(r==null||r.length===0)return;let a=null;if(e&&(a=this.animationsByName.get(e)),a!=null)try{let o=n??0,l=n!=null?n+1:this.mixers.length;for(let c=o;ce)}pauseAnimation(e=null){let t=e??0,i=e!=null?e+1:this.mixers.length;for(let n=t;nt.object.visible&&!t.object.userData.noHit)}hitFromController(e,t=this){return Xl.setFromXRController(e),this.getHit(t)}hitFromPoint(e,t=this){return Xl.setFromCamera(e,this.getCamera()),this.getHit(t)}getModelIndexFromHit(e){let t=e.object;for(;t!=null;){let i=this.models.indexOf(t);if(i!==-1)return i;t=t.parent}return 0}positionAndNormalFromPoint(e,t=this){var i;let n=this.hitFromPoint(e,t);if(n==null)return null;let r=n.point,a=n.face!=null?n.face.normal.clone().applyNormalMatrix(new Pe().getNormalMatrix(n.object.matrixWorld)):Xl.ray.direction.clone().multiplyScalar(-1),o=(i=n.uv)!==null&&i!==void 0?i:null,l=this.getModelIndexFromHit(n),c=this.models[l]||this.target;return{position:r,normal:a,uv:o,modelIndex:l,worldToModel:new Re().copy(c.matrixWorld).invert()}}surfaceFromPoint(e,t=this){var i;let n=this.hitFromPoint(e,t);if(n==null||n.face==null)return null;let r=this.getModelIndexFromHit(n),a=r===0?this.element.model:(i=this.element.extraModels)===null||i===void 0?void 0:i[r-1];if(a==null)return null;let o=a[wg](n);if(o==null)return null;let{meshes:l,primitives:c}=o.mesh.userData.associations,h=new T,u=new T,d=new T,{a:p,b:A,c:g}=n.face,b=n.object;b.getVertexPosition(p,h),b.getVertexPosition(A,u),b.getVertexPosition(g,d);let m=new wn(h,u,d),f=new T;m.getBarycoord(b.worldToLocal(n.point),f),m.getBarycoord(b.worldToLocal(n.point),f);let y=`${l} ${c} ${p} ${A} ${g} ${f.x.toFixed(3)} ${f.y.toFixed(3)} ${f.z.toFixed(3)}`;return r===0?y:`${r} ${y}`}updateHotspotAttachment(e){let t=e.modelIndex!=null&&e.modelIndex>0&&this._models[e.modelIndex]?this._models[e.modelIndex]:this.target;e.parent!==t&&(t.add(e),e.updatePosition(e.position.toArray().join(\" \")+\"m\"),e.updateMatrixWorld(!0))}addHotspot(e){this.updateHotspotAttachment(e),this.annotationRenderer.domElement.appendChild(e.element),this.updateSurfaceHotspot(e)}removeHotspot(e){e.parent&&e.parent.remove(e)}forHotspots(e){let t=[...this.target.children];for(let i=0,n=t.length;i=u||d.y>=u||d.z>=u)return void console.warn(e.surface+\" vertex indices out of range in this glTF! Skipping this hotspot.\");let p=new T(n[5+o].number,n[6+o].number,n[7+o].number);e.mesh=h.mesh,e.tri=d,e.bary=p,e.updateSurface()}animateSurfaceHotspots(){this.element.paused||this.forHotspots(e=>{e.updateSurface()})}updateHotspotsVisibility(e){this.forHotspots(t=>{Id.copy(e),Wb.setFromMatrixPosition(t.matrixWorld),Id.sub(Wb),Xb.copy(t.normal).transformDirection(this.target.matrixWorld),Id.dot(Xb)<0?t.hide():t.show()})}orientHotspots(e){this.forHotspots(t=>{t.orient(e)})}setHotspotsVisibility(e){this.forHotspots(t=>{t.visible=e})}updateSchema(e){var t;let{schemaElement:i,element:n}=this,{alt:r,poster:a,iosSrc:o}=n;if(e!=null){let l=[{\"@type\":\"MediaObject\",contentUrl:e,encodingFormat:((t=e.split(\".\").pop())===null||t===void 0?void 0:t.toLowerCase())===\"gltf\"?\"model/gltf+json\":\"model/gltf-binary\"}];o&&l.push({\"@type\":\"MediaObject\",contentUrl:o,encodingFormat:\"model/vnd.usdz+zip\"});let c={\"@context\":\"http://schema.org/\",\"@type\":\"3DModel\",image:a??void 0,name:r??void 0,encoding:l};i.textContent=JSON.stringify(c),document.head.appendChild(i)}else i.parentElement!=null&&i.parentElement.removeChild(i)}};var Dg=class extends EventTarget{constructor(){super(...arguments),this.ongoingActivities=new Set,this.totalProgress=0}get ongoingActivityCount(){return this.ongoingActivities.size}beginActivity(e){let t={progress:0,completed:!1};return this.ongoingActivities.add(t),this.ongoingActivityCount===1&&this.announceTotalProgress(t,0,e),i=>{let n;return n=Math.max(en(i,0,1),t.progress),n!==t.progress&&this.announceTotalProgress(t,n,e),t.progress}}announceTotalProgress(e,t,i){let n=0,r=0;t==1&&(e.completed=!0);for(let l of this.ongoingActivities){let{progress:c}=l;n+=1-c,l.completed&&r++}let a=e.progress;e.progress=t,this.totalProgress+=(t-a)*(1-this.totalProgress)/n;let o=r===this.ongoingActivityCount?1:this.totalProgress;this.dispatchEvent(new CustomEvent(\"progress\",{detail:{totalProgress:o,reason:i}})),r===this.ongoingActivityCount&&(this.totalProgress=0,this.ongoingActivities.clear())}};var Kb,Jb,Yb,$b,Zb,ev,tv,iv,nv,sv,rv,av,ov,lv,cv,Kl=function(s,e,t,i){for(var n,r=arguments.length,a=r<3?e:i===null?i=Object.getOwnPropertyDescriptor(e,t):i,o=s.length-1;o>=0;o--)(n=s[o])&&(a=(r<3?n(a):r>3?n(e,t,a):n(e,t))||a);return r>3&&a&&Object.defineProperty(e,t,a),a},is=document.createElement(\"canvas\"),Td=Symbol(\"fallbackResizeHandler\"),hv=Symbol(\"defaultAriaLabel\"),Jl=Symbol(\"resizeObserver\"),Pr=Symbol(\"clearModelTimeout\"),Bd=Symbol(\"onContextLost\"),Ms=Symbol(\"loaded\"),Rd=Symbol(\"status\"),Dd=Symbol(\"onFocus\"),Ld=Symbol(\"onBlur\"),Pd=Symbol(\"onSlotChange\"),Fd=Symbol(\"onExtraModelChanged\"),kr=Symbol(\"updateSize\"),Yl=Symbol(\"intersectionObserver\"),Fs=Symbol(\"isElementInViewport\"),Th=Symbol(\"announceModelVisibility\"),Vr=Symbol(\"ariaLabel\"),Lg=Symbol(\"altDefaulted\"),Ka=Symbol(\"statusElement\"),Bh=Symbol(\"updateStatus\"),oo=Symbol(\"loadedTime\"),Zn=Symbol(\"updateSource\"),uv=Symbol(\"markLoaded\"),fc=Symbol(\"container\"),Oi=Symbol(\"input\"),Pg=Symbol(\"canvas\"),Y=Symbol(\"scene\"),Vt=Symbol(\"needsRender\"),Yi=Symbol(\"tick\"),$i=Symbol(\"onModelLoad\"),Rh=Symbol(\"onResize\"),et=Symbol(\"renderer\"),Os=Symbol(\"progressTracker\"),dv=Symbol(\"getLoaded\"),ks=Symbol(\"getModelIsVisible\"),qr=Symbol(\"shouldAttemptPreload\"),Vn=s=>({x:s.x,y:s.y,z:s.z,toString(){return`${this.x}m ${this.y}m ${this.z}m`}}),Fg=s=>({u:s.x,v:s.y,toString(){return`${this.u} ${this.v}`}}),Hs=class extends Xn{static get is(){return\"model-viewer\"}static set modelCacheSize(e){$t[Wn].evictionThreshold=e}static get modelCacheSize(){return $t[Wn].evictionThreshold}static set minimumRenderScale(e){e>1&&console.warn(\" minimumRenderScale has been clamped to a maximum value of 1.\"),e<=0&&console.warn(\" minimumRenderScale has been clamped to a minimum value of 0.25.\"),Qi.singleton.minScale=e}static get minimumRenderScale(){return Qi.singleton.minScale}get loaded(){return this[dv]()}get[(Kb=Fs,Jb=Ms,Yb=oo,$b=Rd,Zb=Pr,ev=Pd,tv=Fd,iv=Td,nv=Th,sv=Jl,rv=Yl,av=Os,et)](){return Qi.singleton}get modelIsVisible(){return this[ks]()}constructor(){super(),this.alt=null,this.src=null,this.withCredentials=!1,this.generateSchema=!1,this[Kb]=!1,this[Jb]=!1,this[Yb]=0,this[$b]=\"\",this[Zb]=null,this[ev]=()=>{if(!this[Y])return;let n=Array.from(this.querySelectorAll(\"extra-model\")),r=n.map(o=>o.src).filter(o=>o!=null),a=this[Y].extraUrls||[];r.join(\",\")===a.join(\",\")&&n.length===a.length||this[Zn]()},this[tv]=n=>{let r=n,a=r.target,o=Array.from(this.querySelectorAll(\"extra-model\")).indexOf(a);if(console.log(`[onExtraModelChanged] childIndex: ${o} srcChanged: ${r.detail.srcChanged} offset: ${r.detail.offset}`),o===-1)return;let l=this.src?o+1:o;r.detail.srcChanged?(this[Ms]=!1,this[Zn]()):this[Y]&&this[Y].updateModelTransforms(l,r.detail.offset,r.detail.orientation,r.detail.scale)},this[iv]=Jf(()=>{let n=this.getBoundingClientRect();this[kr](n)},50),this[nv]=Jf(n=>{let r=this.modelIsVisible;r!==n&&this.dispatchEvent(new CustomEvent(\"model-visibility\",{detail:{visible:r}}))},0),this[sv]=null,this[rv]=null,this[av]=new Dg,this[ov]=()=>{this[Ka].textContent=this[Rd]},this[lv]=()=>{this[Ka].textContent=\"\"},this[cv]=n=>{this.dispatchEvent(new CustomEvent(\"error\",{detail:{type:\"webglcontextlost\",sourceError:n.sourceEvent}}))},this.attachShadow({mode:\"open\"});let e=this.shadowRoot,t,i;if((n=>{kv(CS,n)})(e),this[fc]=e.querySelector(\".container\"),this[Oi]=e.querySelector(\".userInput\"),this[Pg]=e.querySelector(\"canvas\"),this[Ka]=e.querySelector(\"#status\"),this[hv]=this[Oi].getAttribute(\"aria-label\"),this.isConnected){let n=this.getBoundingClientRect();t=n.width,i=n.height}else t=300,i=150;this[Y]=new Rg({canvas:this[Pg],element:this,width:t,height:i}),Promise.resolve().then(()=>{this[kr](this.getBoundingClientRect())}),Uu&&(this[Jl]=new ResizeObserver(n=>{if(!this[et].isPresenting)for(let r of n)r.target===this&&this[kr](r.contentRect)})),Nu?this[Yl]=new IntersectionObserver(n=>{for(let r of n)if(r.target===this){let a=this.modelIsVisible;this[Fs]=r.isIntersecting,this[Th](a),console.log(`IntersectionObserver fired! isIntersecting: ${r.isIntersecting}`),this[Fs]&&!this.loaded&&this[Zn]()}},{root:null,rootMargin:\"0px\",threshold:1e-5}):this[Fs]=!0}connectedCallback(){super.connectedCallback&&super.connectedCallback(),Uu?this[Jl].observe(this):self.addEventListener(\"resize\",this[Td]),Nu&&this[Yl].observe(this),this.addEventListener(\"focus\",this[Dd]),this.addEventListener(\"blur\",this[Ld]),this.addEventListener(\"extra-model-changed\",this[Fd]);let e=this.shadowRoot.querySelector(\".slot.default slot\");e&&e.addEventListener(\"slotchange\",this[Pd]);let t=this[et];t.addEventListener(\"contextlost\",this[Bd]),t.registerScene(this[Y]),this[Pr]!=null&&(self.clearTimeout(this[Pr]),this[Pr]=null,this.requestUpdate(\"src\",null))}disconnectedCallback(){super.disconnectedCallback&&super.disconnectedCallback(),Uu?this[Jl].unobserve(this):self.removeEventListener(\"resize\",this[Td]),Nu&&this[Yl].unobserve(this),this.removeEventListener(\"focus\",this[Dd]),this.removeEventListener(\"blur\",this[Ld]),this.removeEventListener(\"extra-model-changed\",this[Fd]);let e=this.shadowRoot.querySelector(\".slot.default slot\");e&&e.removeEventListener(\"slotchange\",this[Pd]);let t=this[et];t.removeEventListener(\"contextlost\",this[Bd]),t.unregisterScene(this[Y]),this[Pr]=self.setTimeout(()=>{this[Y].dispose(),this[Pr]=null},10)}updated(e){if(super.updated(e),e.has(\"src\")){let t=Array.from(this.querySelectorAll(\"extra-model\")),i=t.map(r=>r.src).filter(r=>r!=null),n=i.join(\",\")===(this[Y].extraUrls||[]).join(\",\");this.src==null&&t.length===0?(this[Ms]=!1,this[oo]=0,this[Y].reset()):this.src===this[Y].url&&n||(this[Ms]=!1,this[oo]=0,this[Zn]())}if(e.has(\"alt\")&&this[Oi].setAttribute(\"aria-label\",this[Vr]),e.has(\"generateSchema\"))if(this.generateSchema){let t=Array.from(this.querySelectorAll(\"extra-model\")).map(n=>n.src).filter(n=>n!=null),i=this.src||t[0]||null;this[Y].updateSchema(i)}else this[Y].updateSchema(null)}toDataURL(e,t){return this[et].displayCanvas(this[Y]).toDataURL(e,t)}async toBlob(e){let t=e?e.mimeType:void 0,i=e?e.qualityArgument:void 0,n=e?e.idealAspect:void 0,{width:r,height:a,idealAspect:o,aspect:l}=this[Y],{dpr:c,scaleFactor:h}=this[et],u=r*h*c,d=a*h*c,p=0,A=0;if(n===!0)if(o>l){let g=d;d=Math.round(u/o),A=(g-d)/2}else{let g=u;u=Math.round(d*o),p=(g-u)/2}is.width=u,is.height=d;try{return new Promise(async(g,b)=>{is.getContext(\"2d\").drawImage(this[et].displayCanvas(this[Y]),p,A,u,d,0,0,u,d),is.toBlob(m=>{if(!m)return b(new Error(\"Unable to retrieve canvas blob\"));g(m)},t,i)})}finally{this[kr]({width:r,height:a})}}registerEffectComposer(e){e.setRenderer(this[et].threeRenderer),e.setMainCamera(this[Y].getCamera()),e.setMainScene(this[Y]),this[Y].effectRenderer=e}unregisterEffectComposer(){this[Y].effectRenderer=null}registerRenderer(e){this[Y].externalRenderer=e}unregisterRenderer(){this[Y].externalRenderer=null}get[Vr](){return this[Lg]}get[Lg](){return this.alt==null||this.alt===\"null\"?this[hv]:this.alt}[dv](){return this[Ms]}[ks](){return this.loaded&&this[Fs]}[qr](){let e=Array.from(this.querySelectorAll(\"extra-model\"));return!!(this.src||e.length>0)&&this[Fs]}[kr]({width:e,height:t}){e!==0&&t!==0&&(this[fc].style.width=`${e}px`,this[fc].style.height=`${t}px`,this[Rh]({width:e,height:t}))}[Yi](e,t){var i;(i=this[Y].effectRenderer)===null||i===void 0||i.beforeRender(e,t)}[uv](){this[Ms]||(this[Ms]=!0,this[oo]=performance.now())}[Vt](){this[Y].queueRender()}[$i](){}[Bh](e){this[Rd]=e;let t=this.getRootNode();t!=null&&t.activeElement===this&&this[Ka].textContent!=e&&(this[Ka].textContent=e)}[(ov=Dd,lv=Ld,Rh)](e){this[Y].setSize(e.width,e.height)}async[(cv=Bd,Zn)](){let e=this[Y],t=Array.from(this.querySelectorAll(\"extra-model\")).map(o=>o.src).filter(o=>o!=null),i=t.join(\",\")===(e.extraUrls||[]).join(\",\");if(console.log(`[$updateSource] called! \nsrc: ${this.src}\nextraUrls: ${t.join(\",\")}\nloaded: ${this.loaded}`),this.loaded||!this[qr]()||this.src===e.url&&i)return void console.log(\"[$updateSource] BAILING OUT EARLY!\");let n=this.src,r=n||t[0]||null;this.generateSchema&&e.updateSchema(r),this[Bh](\"Loading\"),e.stopAnimation();let a=this[Os].beginActivity(\"model-load\");try{let o=e.setSource(n,t,c=>a(.95*en(c,0,1))),l=this[Ah]();await Promise.all([o,l]),Array.from(this.querySelectorAll(\"extra-model\")).forEach((c,h)=>{let u=this.src?h+1:h;this[Y].updateModelTransforms(u,c.offset,c.orientation,c.scale)}),this[uv](),this[$i](),this.updateComplete.then(()=>{this.dispatchEvent(new CustomEvent(\"before-render\"))}),await new Promise(c=>{let h=setTimeout(()=>{console.warn(\"rAF timed out in updateSource\"),c()},500);requestAnimationFrame(()=>{requestAnimationFrame(()=>{clearTimeout(h),c()})})}),this.dispatchEvent(new CustomEvent(\"load\",{detail:{url:r}}))}catch(o){this.dispatchEvent(new CustomEvent(\"error\",{detail:{type:\"loadfailure\",sourceError:o}}))}finally{a(1)}}};Kl([Se({type:String})],Hs.prototype,\"alt\",void 0),Kl([Se({type:String})],Hs.prototype,\"src\",void 0),Kl([Se({type:Boolean,attribute:\"with-credentials\"})],Hs.prototype,\"withCredentials\",void 0),Kl([Se({type:Boolean,attribute:\"generate-schema\"})],Hs.prototype,\"generateSchema\",void 0);var Ud=function(s,e,t,i){for(var n,r=arguments.length,a=r<3?e:i===null?i=Object.getOwnPropertyDescriptor(e,t):i,o=s.length-1;o>=0;o--)(n=s[o])&&(a=(r<3?n(a):r>3?n(e,t,a):n(e,t))||a);return r>3&&a&&Object.defineProperty(e,t,a),a},$l=Symbol(\"changeAnimation\"),pv=Symbol(\"appendAnimation\"),Av=Symbol(\"detachAnimation\"),Is=Symbol(\"paused\"),rw={repetitions:1/0,pingpong:!1},aw={pingpong:!1,repetitions:null,weight:1,timeScale:1,fade:!1,warp:!1,relativeWarp:!0,time:null},ow={fade:!0},Ts=Symbol(\"hotspotMap\"),Nd=Symbol(\"mutationCallback\"),Ja=Symbol(\"observer\"),Od=Symbol(\"addHotspot\"),gv=Symbol(\"removeHotspot\");var ri=Uint8Array,bi=Uint16Array,im=Int32Array,nm=new ri([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0,0]),sm=new ri([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13,0,0]),mv=new ri([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),wx=function(s,e){for(var t=new bi(31),i=0;i<31;++i)t[i]=e+=1<>1|(21845&dt)<<1,qn=(61680&(qn=(52428&qn)>>2|(13107&qn)<<2))>>4|(3855&qn)<<4,Dh[dt]=((65280&qn)>>8|(255&qn)<<8)>>1;var qn,Ng,Dh,dt,vo=function(s,e,t){for(var i=s.length,n=0,r=new bi(e);n>l]=c}else for(a=new bi(i),n=0;n>15-s[n]);return a},sr=new ri(288);for(dt=0;dt<144;++dt)sr[dt]=8;for(dt=144;dt<256;++dt)sr[dt]=9;for(dt=256;dt<280;++dt)sr[dt]=7;for(dt=280;dt<288;++dt)sr[dt]=8;var Lh=new ri(32);for(dt=0;dt<32;++dt)Lh[dt]=5;var cw=vo(sr,9,0),hw=vo(Lh,5,0),Ix=function(s){return(s+7)/8|0},Tx=function(s,e,t){return(t==null||t>s.length)&&(t=s.length),new ri(s.subarray(e,t))},uw=[\"unexpected EOF\",\"invalid block type\",\"invalid length/literal\",\"invalid distance\",\"stream finished\",\"no stream handler\",,\"no callback\",\"invalid UTF-8 data\",\"extra field too long\",\"date not in range 1980-2099\",\"filename too long\",\"stream finishing\",\"invalid zip data\"],zh=function(s,e,t){var i=new Error(e||uw[s]);if(i.code=s,Error.captureStackTrace&&Error.captureStackTrace(i,zh),!t)throw i;return i},xn=function(s,e,t){t<<=7&e;var i=e/8|0;s[i]|=t,s[i+1]|=t>>8},Ya=function(s,e,t){t<<=7&e;var i=e/8|0;s[i]|=t,s[i+1]|=t>>8,s[i+2]|=t>>16},kd=function(s,e){for(var t=[],i=0;id&&(d=r[i].s);var p=new bi(d+1),A=Og(t[h-1],p,0);if(A>e){i=0;var g=0,b=A-e,m=1<e))break;g+=m-(1<>=b;g>0;){var y=r[i].s;p[y]=0&&g;--i){var v=r[i].s;p[v]==e&&(--p[v],++g)}A=e}return{t:new ri(p),l:A}},Og=function(s,e,t){return s.s==-1?Math.max(Og(s.l,e,t+1),Og(s.r,e,t+1)):e[s.s]=t},fv=function(s){for(var e=s.length;e&&!s[--e];);for(var t=new bi(++e),i=0,n=s[0],r=1,a=function(l){t[i++]=l},o=1;o<=e;++o)if(s[o]==n&&o!=e)++r;else{if(!n&&r>2){for(;r>138;r-=138)a(32754);r>2&&(a(r>10?r-11<<5|28690:r-3<<5|12305),r=0)}else if(r>3){for(a(n),--r;r>6;r-=6)a(8304);r>2&&(a(r-3<<5|8208),r=0)}for(;r--;)a(n);r=1,n=s[o]}return{c:t.subarray(0,i),n:e}},$a=function(s,e){for(var t=0,i=0;i>8,s[n+2]=255^s[n],s[n+3]=255^s[n+1];for(var r=0;r4&&!F[mv[R-1]];--R);var U,O,k,N,G=c+5<<3,re=$a(n,sr)+$a(r,Lh)+a,ce=$a(n,d)+$a(r,g)+a+14+3*R+$a(w,F)+2*w[16]+3*w[17]+7*w[18];if(l>=0&&G<=re&&G<=ce)return Bx(e,h,s.subarray(l,l+c));if(xn(e,h,1+(ce15&&(xn(e,h,K[B]>>5&127),h+=K[B]>>12)}}}else U=cw,O=sr,k=hw,N=Lh;for(B=0;B255){Ya(e,h,U[(J=Z>>18&31)+257]),h+=O[J+257],J>7&&(xn(e,h,Z>>23&31),h+=nm[J]);var ye=31&Z;Ya(e,h,k[ye]),h+=N[ye],ye>3&&(Ya(e,h,Z>>5&8191),h+=sm[ye])}else Ya(e,h,U[Z]),h+=O[Z]}return Ya(e,h,U[256]),h+O[256]},dw=new im([65540,131080,131088,131104,262176,1048704,1048832,2114560,2117632]),Rx=new ri(0),pw=(function(){for(var s=new Int32Array(256),e=0;e<256;++e){for(var t=e,i=9;--i;)t=(1&t&&-306674912)^t>>>1;s[e]=t}return s})(),Aw=function(){var s=-1;return{p:function(e){for(var t=s,i=0;i>>8;s=t},d:function(){return~s}}},gw=function(s,e,t,i,n){if(!n&&(n={l:1},e.dictionary)){var r=e.dictionary.subarray(-32768),a=new ri(r.length+s.length);a.set(r),a.set(s,r.length),s=a,n.w=r.length}return(function(o,l,c,h,u,d){var p=d.z||o.length,A=new ri(h+p+5*(1+Math.ceil(p/7e3))+u),g=A.subarray(h,A.length-u),b=d.l,m=7&(d.r||0);if(l){m&&(g[0]=d.r>>3);for(var f=dw[l-1],y=f>>13,v=8191&f,x=(1<7e3||G>24576)&&(K>423||!b)){m=bv(o,g,0,Q,R,U,k,G,ce,N-ce,m),G=O=k=0,ce=N;for(var J=0;J<286;++J)R[J]=0;for(J=0;J<30;++J)U[J]=0}var Z=2,ye=0,Ce=v,E=he-se&32767;if(K>2&&fe==F(N-E))for(var S=Math.min(y,K)-1,P=Math.min(32767,N),H=Math.min(258,K);E<=P&&--Ce&&he!=se;){if(o[N+Z]==o[N+Z-E]){for(var C=0;CZ){if(Z=C,ye=E,C>S)break;var L=Math.min(E,C-2),V=0;for(J=0;JV&&(V=te,se=q)}}}E+=(he=se)-(se=_[he])&32767}if(ye){Q[G++]=268435456|Ug[Z]<<18|Ng[ye];var ne=31&Ug[Z],W=31&Ng[ye];k+=nm[ne]+sm[W],++R[257+ne],++U[W],re=N+Z,++O}else Q[G++]=o[N],++R[o[N]]}}for(N=Math.max(N,re);N=p&&(g[m/8|0]=b,oe=p),m=Bx(g,m+1,o.subarray(N,oe))}d.i=p}return Tx(A,0,h+Ix(m)+u)})(s,e.level==null?6:e.level,e.mem==null?n.l?Math.ceil(1.5*Math.max(8,Math.min(13,Math.log(s.length)))):20:12+e.mem,t,i,n)},Dx=function(s,e){var t={};for(var i in s)t[i]=s[i];for(var i in e)t[i]=e[i];return t},Yt=function(s,e,t){for(;t;++e)s[e]=t,t>>>=8};function mw(s,e){return gw(s,e||{},0,0)}var Lx=function(s,e,t,i){for(var n in s){var r=s[n],a=e+n,o=i;Array.isArray(r)&&(o=Dx(i,r[1]),r=r[0]),r instanceof ri?t[a]=[r,o]:(t[a+=\"/\"]=[new ri(0),o],Lx(r,a,t,i))}},vv=typeof TextEncoder<\"u\"&&new TextEncoder,fw=typeof TextDecoder<\"u\"&&new TextDecoder;try{fw.decode(Rx,{stream:!0})}catch{}function Ph(s,e){if(vv)return vv.encode(s);for(var t=s.length,i=new ri(s.length+(s.length>>1)),n=0,r=function(c){i[n++]=c},a=0;ai.length){var o=new ri(n+8+(t-a<<1));o.set(i),i=o}var l=s.charCodeAt(a);l<128||e?r(l):l<2048?(r(192|l>>6),r(128|63&l)):l>55295&&l<57344?(r(240|(l=65536+(1047552&l)|1023&s.charCodeAt(++a))>>18),r(128|l>>12&63),r(128|l>>6&63),r(128|63&l)):(r(224|l>>12),r(128|l>>6&63),r(128|63&l))}return Tx(i,0,n)}var kg=function(s){var e=0;if(s)for(var t in s){var i=s[t].length;i>65535&&zh(9),e+=i+4}return e},yv=function(s,e,t,i,n,r,a,o){var l=i.length,c=t.extra,h=o&&o.length,u=kg(c);Yt(s,e,a!=null?33639248:67324752),e+=4,a!=null&&(s[e++]=20,s[e++]=t.os),s[e]=20,e+=2,s[e++]=t.flag<<1|(r<0&&8),s[e++]=n&&8,s[e++]=255&t.compression,s[e++]=t.compression>>8;var d=new Date(t.mtime==null?Date.now():t.mtime),p=d.getFullYear()-1980;if((p<0||p>119)&&zh(10),Yt(s,e,p<<25|d.getMonth()+1<<21|d.getDate()<<16|d.getHours()<<11|d.getMinutes()<<5|d.getSeconds()>>1),e+=4,r!=-1&&(Yt(s,e,t.crc),Yt(s,e+4,r<0?-r-2:r),Yt(s,e+8,t.size)),Yt(s,e+12,l),Yt(s,e+14,u),e+=16,a!=null&&(Yt(s,e,h),Yt(s,e+6,t.attrs),Yt(s,e+10,a),e+=14),s.set(i,e),e+=l,u)for(var A in c){var g=c[A],b=g.length;Yt(s,e,+A),Yt(s,e+2,b),s.set(g,e+4),e+=4+b}return h&&(s.set(o,e),e+=h),e};function bw(s,e){e||(e={});var t={},i=[];Lx(s,\"\",t,e);var n=0,r=0;for(var a in t){var o=t[a],l=o[0],c=o[1],h=c.level==0?0:8,u=(w=Ph(a)).length,d=c.comment,p=d&&Ph(d),A=p&&p.length,g=kg(c.extra);u>65535&&zh(11);var b=h?mw(l,c):l,m=b.length,f=Aw();f.p(l),i.push(Dx(c,{size:l.length,crc:f.d(),c:b,f:w,m:p,u:u!=a.length||p&&d.length!=A,o:n,compression:h})),n+=30+u+g+m,r+=76+2*(u+g)+(A||0)+m}for(var y=new ri(r+22),v=n,x=r-n,_=0;_{let u=h.key,d=h.value;if(Array.isArray(d)){let p=[];return p.push(`${u} = {`),d.forEach(A=>{p.push(`${t}\t\t${A}`)}),p.push(`${t}\t}`),p.join(`\n`)}return`${u} = ${d}`}),n=i.length?` (\n${i.map(h=>`${t}\t${h}`).join(`\n`)}\n${t})`:\"\",r=this.properties.map(h=>{let u=h.property,d=h.metadata.length?` (\n${h.metadata.map(p=>`${t}\t\t${p}`).join(`\n`)}\n${t}\t)`:\"\";return`${t}\t${u}${d}`}),a=this.children.map(h=>h.toString(e+1)),o=[];if(r.length>0&&o.push(...r),a.length>0){r.length>0&&o.push(\"\");for(let h=0;hm.toBlob(y,\"image/png\",1));n[`textures/Texture_${g}.png`]=new Uint8Array(await f.arrayBuffer())}let A=0;for(let g in n){let b=n[g];A+=34+g.length;let m=63&A;if(m!==4){let f=new Uint8Array(64-m);n[g]=[b,{extra:{12345:f}}]}A=b.length}return bw(n,{level:0})}};function Px(s,e){let t=s.name;return t=t.replace(/[^A-Za-z0-9_]/g,\"\"),/^[0-9]/.test(t)&&(t=\"_\"+t),t===\"\"&&(t=s.isCamera?\"Camera\":\"Object\"),e.has(t)&&(t=t+\"_\"+s.id),e.add(t),t}function vw(s,e,t){if(typeof HTMLImageElement<\"u\"&&s instanceof HTMLImageElement||typeof HTMLCanvasElement<\"u\"&&s instanceof HTMLCanvasElement||typeof OffscreenCanvas<\"u\"&&s instanceof OffscreenCanvas||typeof ImageBitmap<\"u\"&&s instanceof ImageBitmap){let i=t/Math.max(s.width,s.height),n=document.createElement(\"canvas\");n.width=s.width*Math.min(1,i),n.height=s.height*Math.min(1,i);let r=n.getContext(\"2d\");return e===!0&&(r.translate(0,n.height),r.scale(1,-1)),r.drawImage(s,0,0,n.width,n.height),n}throw new Error(\"THREE.USDZExporter: No valid image data found. Unable to process texture.\")}var ht=7;function Fx(){return`#usda 1.0\n(\n\tcustomLayerData = {\n\t\tstring creator = \"Three.js USDZExporter\"\n\t}\n\tdefaultPrim = \"Root\"\n\tmetersPerUnit = 1\n\tupAxis = \"Y\"\n)\n`}function Ux(s,e,t,i,n,r){for(let a=0,o=s.children.length;a`),n.addMetadata(\"prepend apiSchemas\",'[\"MaterialBindingAPI\"]'),n.addProperty(`rel material:binding = `),n}function Ox(s){let e=s.elements;return`( ${Zl(e,0)}, ${Zl(e,4)}, ${Zl(e,8)}, ${Zl(e,12)} )`}function Zl(s,e){return`(${s[e+0]}, ${s[e+1]}, ${s[e+2]}, ${s[e+3]})`}function xw(s){let e=new hi(\"Geometry\"),t=(function(i){let n=\"Geometry\",r=i.attributes,a=r.position.count,o=new hi(n,\"Mesh\");o.addProperty(`int[] faceVertexCounts = [${(function(c){let h=c.index!==null?c.index.count:c.attributes.position.count;return Array(h/3).fill(3).join(\", \")})(i)}]`),o.addProperty(`int[] faceVertexIndices = [${(function(c){let h=c.index,u=[];if(h!==null)for(let d=0;d0?c:\"\",u=r[\"uv\"+h];u!==void 0&&o.addProperty(`texCoord2f[] primvars:st${h} = [${_w(u)}]`,['interpolation = \"vertex\"'])}let l=r.color;return l!==void 0&&o.addProperty(`color3f[] primvars:displayColor = [${Qd(l,a)}]`,['interpolation = \"vertex\"']),o.addProperty('uniform token subdivisionScheme = \"none\"'),o})(s);return e.addChild(t),e}function Qd(s,e){if(s===void 0)return console.warn(\"USDZExporter: Normals missing.\"),Array(e).fill(\"(0, 0, 0)\").join(\", \");let t=[];for(let i=0;i0?\"st\"+a.channel:\"st\",u={1e3:\"repeat\",1001:\"clamp\",1002:\"mirror\"},d=a.repeat.clone(),p=a.offset.clone(),A=a.rotation,g=Math.sin(A),b=Math.cos(A);p.y=1-p.y-d.y,t?(p.x=p.x/d.x,p.y=p.y/d.y,p.x+=g/d.x,p.y+=b-1):(p.x+=g*d.x,p.y+=(1-b)*d.y);let m=new hi(`PrimvarReader_${o}`,\"Shader\");m.addProperty('uniform token info:id = \"UsdPrimvarReader_float2\"'),m.addProperty(\"float2 inputs:fallback = (0.0, 0.0)\"),m.addProperty(`string inputs:varname = \"${h}\"`),m.addProperty(\"float2 outputs:result\");let f=new hi(`Transform2d_${o}`,\"Shader\");f.addProperty('uniform token info:id = \"UsdTransform2d\"'),f.addProperty(`float2 inputs:in.connect = `),f.addProperty(`float inputs:rotation = ${(A*(180/Math.PI)).toFixed(ht)}`),f.addProperty(`float2 inputs:scale = ${_v(d)}`),f.addProperty(`float2 inputs:translation = ${_v(p)}`),f.addProperty(\"float2 outputs:result\");let y=new hi(`Texture_${a.id}_${o}`,\"Shader\");return y.addProperty('uniform token info:id = \"UsdUVTexture\"'),y.addProperty(`asset inputs:file = @textures/Texture_${c}.png@`),y.addProperty(`float2 inputs:st.connect = `),l!==void 0&&y.addProperty(`float4 inputs:scale = ${(function(v){return`(${v.r}, ${v.g}, ${v.b}, 1.0)`})(l)}`),o===\"normal\"&&(y.addProperty(\"float4 inputs:scale = (2, 2, 2, 1)\"),y.addProperty(\"float4 inputs:bias = (-1, -1, -1, 0)\")),y.addProperty(`token inputs:sourceColorSpace = \"${a.colorSpace===Cn?\"raw\":\"sRGB\"}\"`),y.addProperty(`token inputs:wrapS = \"${u[a.wrapS]}\"`),y.addProperty(`token inputs:wrapT = \"${u[a.wrapT]}\"`),y.addProperty(\"float outputs:r\"),y.addProperty(\"float outputs:g\"),y.addProperty(\"float outputs:b\"),y.addProperty(\"float3 outputs:rgb\"),(s.transparent||s.alphaTest>0)&&y.addProperty(\"float outputs:a\"),[m,f,y]}s.side===2&&console.warn(\"THREE.USDZExporter: USDZ does not support double sided materials\",s);let r=new hi(\"PreviewSurface\",\"Shader\");if(r.addProperty('uniform token info:id = \"UsdPreviewSurface\"'),s.map!==null?(r.addProperty(`color3f inputs:diffuseColor.connect = `),s.transparent?r.addProperty(`float inputs:opacity.connect = `):s.alphaTest>0&&(r.addProperty(`float inputs:opacity.connect = `),r.addProperty(`float inputs:opacityThreshold = ${s.alphaTest}`)),n(s.map,\"diffuse\",s.color).forEach(a=>i.addChild(a))):r.addProperty(`color3f inputs:diffuseColor = ${xv(s.color)}`),s.emissiveMap!==null){r.addProperty(`color3f inputs:emissiveColor.connect = `);let a=new ve(s.emissive.r*s.emissiveIntensity,s.emissive.g*s.emissiveIntensity,s.emissive.b*s.emissiveIntensity);n(s.emissiveMap,\"emissive\",a).forEach(o=>i.addChild(o))}else s.emissive.getHex()>0&&r.addProperty(`color3f inputs:emissiveColor = ${xv(s.emissive)}`);if(s.normalMap!==null&&(r.addProperty(`normal3f inputs:normal.connect = `),n(s.normalMap,\"normal\").forEach(a=>i.addChild(a))),s.aoMap!==null){r.addProperty(`float inputs:occlusion.connect = `);let a=new ve(s.aoMapIntensity,s.aoMapIntensity,s.aoMapIntensity);n(s.aoMap,\"occlusion\",a).forEach(o=>i.addChild(o))}if(s.roughnessMap!==null){r.addProperty(`float inputs:roughness.connect = `);let a=new ve(s.roughness,s.roughness,s.roughness);n(s.roughnessMap,\"roughness\",a).forEach(o=>i.addChild(o))}else r.addProperty(`float inputs:roughness = ${s.roughness}`);if(s.metalnessMap!==null){r.addProperty(`float inputs:metallic.connect = `);let a=new ve(s.metalness,s.metalness,s.metalness);n(s.metalnessMap,\"metallic\",a).forEach(o=>i.addChild(o))}else r.addProperty(`float inputs:metallic = ${s.metalness}`);if(s.alphaMap!==null?(r.addProperty(`float inputs:opacity.connect = `),r.addProperty(\"float inputs:opacityThreshold = 0.0001\"),n(s.alphaMap,\"opacity\").forEach(a=>i.addChild(a))):r.addProperty(`float inputs:opacity = ${s.opacity}`),s.isMeshPhysicalMaterial){if(s.clearcoatMap!==null){r.addProperty(`float inputs:clearcoat.connect = `);let a=new ve(s.clearcoat,s.clearcoat,s.clearcoat);n(s.clearcoatMap,\"clearcoat\",a).forEach(o=>i.addChild(o))}else r.addProperty(`float inputs:clearcoat = ${s.clearcoat}`);if(s.clearcoatRoughnessMap!==null){r.addProperty(`float inputs:clearcoatRoughness.connect = `);let a=new ve(s.clearcoatRoughness,s.clearcoatRoughness,s.clearcoatRoughness);n(s.clearcoatRoughnessMap,\"clearcoatRoughness\",a).forEach(o=>i.addChild(o))}else r.addProperty(`float inputs:clearcoatRoughness = ${s.clearcoatRoughness}`);r.addProperty(`float inputs:ior = ${s.ior}`)}return r.addProperty(\"int inputs:useSpecularWorkflow = 0\"),r.addProperty(\"token outputs:surface\"),i.addChild(r),i.addProperty(`token outputs:surface.connect = `),i}function xv(s){return`(${s.r}, ${s.g}, ${s.b})`}function _v(s){return`(${s.x}, ${s.y})`}function Sw(s,e){let t=Px(s,e),i=Ox(s.matrix);s.matrix.determinant()<0&&console.warn(\"THREE.USDZExporter: USDZ does not support negative scales\",s);let n=new hi(t,\"Camera\");n.addProperty(`matrix4d xformOp:transform = ${i}`),n.addProperty('uniform token[] xformOpOrder = [\"xformOp:transform\"]');let r=s.isOrthographicCamera?\"orthographic\":\"perspective\";n.addProperty(`token projection = \"${r}\"`);let a=`(${s.near.toPrecision(ht)}, ${s.far.toPrecision(ht)})`,o,l;if(n.addProperty(`float2 clippingRange = ${a}`),o=s.isOrthographicCamera?(10*(Math.abs(s.left)+Math.abs(s.right))).toPrecision(ht):s.getFilmWidth().toPrecision(ht),n.addProperty(`float horizontalAperture = ${o}`),l=s.isOrthographicCamera?(10*(Math.abs(s.top)+Math.abs(s.bottom))).toPrecision(ht):s.getFilmHeight().toPrecision(ht),n.addProperty(`float verticalAperture = ${l}`),s.isPerspectiveCamera){let c=s.getFocalLength().toPrecision(ht);n.addProperty(`float focalLength = ${c}`);let h=s.focus.toPrecision(ht);n.addProperty(`float focusDistance = ${h}`)}return n}var Bs=function(s,e,t,i){for(var n,r=arguments.length,a=r<3?e:i===null?i=Object.getOwnPropertyDescriptor(e,t):i,o=s.length-1;o>=0;o--)(n=s[o])&&(a=(r<3?n(a):r>3?n(e,t,a):n(e,t))||a);return r>3&&a&&Object.defineProperty(e,t,a),a},Ev=!1,Sv=!1,Cv=\"#model-viewer-no-ar-fallback\",Cw=(wv=[\"quick-look\",\"scene-viewer\",\"webxr\",\"none\"],s=>{try{let e=In(s),t=(e.length?e[0].terms:[]).filter(i=>i&&i.type===\"ident\").map(i=>i.value).filter(i=>wv.indexOf(i)>-1);return new Set(t)}catch{}return new Set}),wv,Gd=\"quick-look\",Mv=\"scene-viewer\",Iv=\"webxr\",Za=\"none\",_n=Symbol(\"arButtonContainer\"),Tv=Symbol(\"enterARWithWebXR\"),Bv=Symbol(\"openSceneViewer\"),Rv=Symbol(\"openIOSARQuickLook\"),ww=Symbol(\"canActivateAR\"),ec=Symbol(\"arMode\"),zd=Symbol(\"arModes\"),Fr=Symbol(\"arAnchor\"),tc=Symbol(\"preload\"),ic=Symbol(\"onARButtonContainerClick\"),Hd=Symbol(\"onARStatus\"),Vd=Symbol(\"onARTracking\"),qd=Symbol(\"onARTap\"),eo=Symbol(\"selectARMode\"),jd=Symbol(\"triggerLoad\");var Wd=function(s,e,t,i){for(var n,r=arguments.length,a=r<3?e:i===null?i=Object.getOwnPropertyDescriptor(e,t):i,o=s.length-1;o>=0;o--)(n=s[o])&&(a=(r<3?n(a):r>3?n(e,t,a):n(e,t))||a);return r>3&&a&&Object.defineProperty(e,t,a),a},Xd=\"auto\",Mw=\"auto\",Iw=\"eager\",Ur=Symbol(\"defaultProgressBarElement\"),Kd=Symbol(\"posterContainerElement\"),Nr=Symbol(\"defaultPosterElement\"),to=Symbol(\"shouldDismissPoster\"),Jd=Symbol(\"hidePoster\"),nc=Symbol(\"modelIsRevealed\"),Yd=Symbol(\"updateProgressBar\"),Tw=Symbol(\"ariaLabelCallToAction\"),$d=Symbol(\"onProgress\");var Zd=function(s,e,t,i){for(var n,r=arguments.length,a=r<3?e:i===null?i=Object.getOwnPropertyDescriptor(e,t):i,o=s.length-1;o>=0;o--)(n=s[o])&&(a=(r<3?n(a):r>3?n(e,t,a):n(e,t))||a);return r>3&&a&&Object.defineProperty(e,t,a),a},Bw=Math.PI/32,Rw={basis:[Gh(si(Bw,\"rad\"))],keywords:{auto:[null]}},Or=Symbol(\"autoRotateStartTime\"),ep=Symbol(\"radiansPerSecond\"),Dv=Symbol(\"syncRotationRate\"),tp=Symbol(\"onCameraChange\"),Dw=(s=>{var e,t,i;class n extends s{constructor(){super(...arguments),this[e]=new Map,this[t]=a=>{a.forEach(o=>{o instanceof MutationRecord&&o.type!==\"childList\"||(o.addedNodes.forEach(l=>{this[Od](l)}),o.removedNodes.forEach(l=>{this[gv](l)}),this[Vt]())})},this[i]=new MutationObserver(this[Nd])}connectedCallback(){super.connectedCallback();for(let o=0;o{a.updateHotspotAttachment(o),a.updateSurfaceHotspot(o)})}[Yi](a,o){super[Yi](a,o);let l=this[Y],{annotationRenderer:c}=l,h=l.getCamera();l.shouldRender()&&(l.animateSurfaceHotspots(),l.updateHotspotsVisibility(h.position),c.domElement.style.display=\"\",c.render(l,h))}updateHotspot(a){let o=this[Ts].get(a.name);o!=null&&(o.updatePosition(a.position),o.updateNormal(a.normal),o.surface=a.surface,a.modelIndex!==void 0&&a.modelIndex!==null&&(o.modelIndex=a.modelIndex),this[Y].updateSurfaceHotspot(o),this[Vt]())}queryHotspot(a){let o=this[Ts].get(a);if(o==null)return null;let l=Vn(o.position),c=Vn(o.normal),h=o.facingCamera,u=this[Y],d=u.getCamera(),p=new T;p.setFromMatrixPosition(o.matrixWorld),p.project(d);let A=u.width/2,g=u.height/2;p.x=p.x*A+A,p.y=-p.y*g+g;let b=Vn(new T(p.x,p.y,p.z));return Number.isFinite(b.x)&&Number.isFinite(b.y)?{position:l,normal:c,canvasPosition:b,facingCamera:h}:null}positionAndNormalFromPoint(a,o){let l=this[Y],c=l.getNDC(a,o),h=l.positionAndNormalFromPoint(c);if(h==null)return null;let u=h.worldToModel,d=Vn(h.position.applyMatrix4(u)),p=Vn(h.normal.transformDirection(u)),A=null;return h.uv!=null&&(A=Fg(h.uv)),{position:d,normal:p,uv:A,modelIndex:h.modelIndex}}surfaceFromPoint(a,o){let l=this[Y],c=l.getNDC(a,o);return l.surfaceFromPoint(c)}[Od](a){if(!(a instanceof HTMLElement&&a.slot.indexOf(\"hotspot\")===0))return;let o=this[Ts].get(a.slot);if(o!=null){o.increment();let l=a.dataset.visibilityAttribute;if(l!=null){let c=`data-${l}`;a.toggleAttribute(c,o.facingCamera)}a.dispatchEvent(new CustomEvent(\"hotspot-visibility\",{detail:{visible:o.facingCamera}}))}else o=new Vo({name:a.slot,position:a.dataset.position,normal:a.dataset.normal,surface:a.dataset.surface,modelIndex:a.dataset.modelIndex!=null?parseInt(a.dataset.modelIndex):null}),this[Ts].set(a.slot,o),this[Y].addHotspot(o);this[Y].queueRender()}[gv](a){if(!(a instanceof HTMLElement))return;let o=this[Ts].get(a.slot);o&&(o.decrement()&&(this[Y].removeHotspot(o),this[Ts].delete(a.slot)),this[Y].queueRender())}}return n})((s=>{var e,t,i,n;class r extends s{constructor(){super(...arguments),this[e]=void 0,this[t]=[],this[i]=null,this[n]=null,this.variantName=null,this.orientation=\"0 0 0\",this.scale=\"1 1 1\"}get model(){return this[Ps]}get extraModels(){return this[ja]}get availableVariants(){return this.model?this.model[Mg]():[]}get originalGltfJson(){return this[mc]}[(e=Ps,t=ja,i=gc,n=mc,ql)](){return()=>{this[Vt]()}}[Wa](o){return o.colorSpace=lt,o.wrapS=sn,o.wrapT=sn,new Mh(this[ql](),o)}async createTexture(o,l=\"image/png\"){let{textureUtils:c}=this[et],h=await c.loadImage(o,this.withCredentials,l),u=l===\"image/ktx2\"?\"image/png\":l;return h.userData.mimeType=u,this[Wa](h)}async createLottieTexture(o,l=1){let{textureUtils:c}=this[et],h=await c.loadLottie(o,l,this.withCredentials);return this[Wa](h)}createVideoTexture(o){let l=document.createElement(\"video\");l.crossOrigin=this.withCredentials?\"use-credentials\":\"anonymous\",l.src=o,l.muted=!0,l.playsInline=!0,l.loop=!0,l.play();let c=new Lp(l);return this[Wa](c)}createCanvasTexture(){let o=document.createElement(\"canvas\"),l=new pa(o);return this[Wa](l)}async updated(o){if(super.updated(o),o.has(\"variantName\")){let l=this[Os].beginActivity(\"variant-update\");l(.1);let c=this[Ps],{variantName:h}=this;c!=null&&(await c[Sx](h),this[Vt](),this.dispatchEvent(new CustomEvent(\"variant-applied\"))),l(1)}if(o.has(\"orientation\")||o.has(\"scale\")){if(!this.loaded)return;let l=this[Y];l.applyTransform(),l.updateBoundingBox(),l.updateShadow(),this[et].arRenderer.onUpdateScene(),this[Vt]()}}[$i](){super[$i]();let{currentGLTFs:o}=this[Y],l=o.length>0?o[0]:null;if(l!=null){let{correlatedSceneGraph:h}=l;h!=null&&l!==this[gc]&&(this[Ps]=new Ih(h,this[ql]()),this[mc]=JSON.parse(JSON.stringify(h.gltf))),\"variants\"in l.userData&&this.requestUpdate(\"variantName\")}this[ja]=[];let c=Array.from(this.querySelectorAll(\"extra-model\"));for(let h=1;h{let u={binary:!0,onlyVisible:!0,maxTextureSize:1/0,includeCustomExtensions:!1,forceIndices:!1};Object.assign(u,o),u.animations=l.animations,u.truncateDrawRange=!0;let d=l.shadow,p=!1;d!=null&&(p=d.visible,d.visible=!1),await this[Ps][Ex]();let A=new Xs().register(b=>new eg(b)),g;if(A.setTextureUtils({decompress:(b,m)=>(function(f,y=1/0,v=null){sd||(sd=new Ii(2,2,1,1)),Cr||(Cr=new Jt({uniforms:{blitTexture:new lA(f)},vertexShader:`\n\t\t\tvarying vec2 vUv;\n\t\t\tvoid main(){\n\t\t\t\tvUv = uv;\n\t\t\t\tgl_Position = vec4(position.xy * 1.0,0.,.999999);\n\t\t\t}`,fragmentShader:`\n\t\t\tuniform sampler2D blitTexture; \n\t\t\tvarying vec2 vUv;\n\n\t\t\tvoid main(){ \n\t\t\t\tgl_FragColor = vec4(vUv.xy, 0, 1);\n\t\t\t\t\n\t\t\t\t#ifdef IS_SRGB\n\t\t\t\tgl_FragColor = sRGBTransferOETF( texture2D( blitTexture, vUv) );\n\t\t\t\t#else\n\t\t\t\tgl_FragColor = texture2D( blitTexture, vUv);\n\t\t\t\t#endif\n\t\t\t}`})),Cr.uniforms.blitTexture.value=f,Cr.defines.IS_SRGB=f.colorSpace==lt,Cr.needsUpdate=!0,Bl||(Bl=new Je(sd,Cr),Bl.frustumCulled=!1);let x=new Pt,_=new rn;_.add(Bl),v===null&&(v=Na=new zo({antialias:!1}));let w=Math.min(f.image.width,y),B=Math.min(f.image.height,y);v.setSize(w,B),v.clear(),v.render(_,x);let I=document.createElement(\"canvas\"),F=I.getContext(\"2d\");I.width=w,I.height=B,F.drawImage(v.domElement,0,0,w,B);let Q=new pa(I);return Q.minFilter=f.minFilter,Q.magFilter=f.magFilter,Q.wrapS=f.wrapS,Q.wrapT=f.wrapT,Q.colorSpace=f.colorSpace,Q.name=f.name,Na&&(Na.forceContextLoss(),Na.dispose(),Na=null),Q})(b,m??1/0,void 0)}),l.models.length>1){g=new ct;for(let b of l.models)g.add(b)}else g=l.models[0];A.parse(g,b=>{if(l.models.length>1)for(let m of l.models)l.target.add(m);else l.target.add(l.models[0]);return c(new Blob([u.binary?b:JSON.stringify(b)],{type:u.binary?\"application/octet-stream\":\"application/json\"}))},()=>h(\"glTF export failed\"),u),d!=null&&(d.visible=p)})}materialFromPoint(o,l){let c=this[Y],h=c.getNDC(o,l),u=c.hitFromPoint(h);if(u==null||u.face==null)return null;let d=this[Ps];if(d!=null){let p=d[Cg](u);if(p!=null)return p}for(let p of this[ja]){let A=p[Cg](u);if(A!=null)return A}return null}}return Cd([Se({type:String,attribute:\"variant-name\"})],r.prototype,\"variantName\",void 0),Cd([Se({type:String,attribute:\"orientation\"})],r.prototype,\"orientation\",void 0),Cd([Se({type:String,attribute:\"scale\"})],r.prototype,\"scale\",void 0),r})((s=>{var e,t,i;class n extends s{constructor(){super(...arguments),this.autoRotate=!1,this.autoRotateDelay=3e3,this.rotationPerSecond=\"auto\",this[e]=performance.now(),this[t]=0,this[i]=a=>{this.autoRotate&&a.detail.source===\"user-interaction\"&&(this[Or]=performance.now())}}connectedCallback(){super.connectedCallback(),this.addEventListener(\"camera-change\",this[tp]),this[Or]=performance.now()}disconnectedCallback(){super.disconnectedCallback(),this.removeEventListener(\"camera-change\",this[tp]),this[Or]=performance.now()}updated(a){super.updated(a),a.has(\"autoRotate\")&&(this[Or]=performance.now())}[(e=Or,t=ep,Dv)](a){this[ep]=a[0]}[Yi](a,o){if(super[Yi](a,o),!this.autoRotate||!this[ks]()||this[et].isPresenting)return;let l=Math.min(o,a-this[Or]-this.autoRotateDelay);l>0&&(this[Y].yaw=this.turntableRotation+this[ep]*l*.001)}get turntableRotation(){return this[Y].yaw}resetTurntableRotation(a=0){this[Y].yaw=a}}return i=tp,Zd([Se({type:Boolean,attribute:\"auto-rotate\"})],n.prototype,\"autoRotate\",void 0),Zd([Se({type:Number,attribute:\"auto-rotate-delay\"})],n.prototype,\"autoRotateDelay\",void 0),Zd([zn({intrinsics:Rw,updateHandler:Dv}),Se({type:String,attribute:\"rotation-per-second\"})],n.prototype,\"rotationPerSecond\",void 0),n})((s=>{var e,t,i;class n extends s{constructor(){super(...arguments),this.environmentImage=null,this.skyboxImage=null,this.shadowIntensity=0,this.shadowSoftness=1,this.exposure=1,this.toneMapping=\"auto\",this.skyboxHeight=\"0\",this[e]=null,this[t]=null,this[i]=null}updated(a){var o;if(super.updated(a),a.has(\"shadowIntensity\")&&(this[Y].setShadowIntensity(.5*this.shadowIntensity),this[Vt]()),a.has(\"shadowSoftness\")&&(this[Y].setShadowSoftness(this.shadowSoftness),this[Vt]()),a.has(\"exposure\")&&(this[Y].exposure=this.exposure,this[Vt]()),a.has(\"toneMapping\")){let l=new Map([[\"aces\",4],[\"agx\",6],[\"reinhard\",2],[\"cineon\",3],[\"linear\",1],[\"none\",0]]);this[Y].toneMapping=(o=l.get(this.toneMapping))!==null&&o!==void 0?o:7,this[Vt]()}(a.has(\"environmentImage\")||a.has(\"skyboxImage\"))&&this[qr]()&&this[Ah](),a.has(\"skyboxHeight\")&&(this[Y].setGroundedSkybox(),this[Vt]())}hasBakedShadow(){return this[Y].bakedShadows.size>0}async[(e=no,t=hc,i=Ml,Ah)](){let{skyboxImage:a,environmentImage:o}=this;this[Ml]!=null&&(this[Ml](),this[Ml]=null);let{textureUtils:l}=this[et];if(l==null)return;let c=this[Os].beginActivity(\"environment-update\");try{let{environmentMap:h,skybox:u}=await l.generateEnvironmentMapAndSkybox(cy(a),o,d=>c(en(d,0,1)),this.withCredentials);this[no]!==h&&(this[no]=h,this.dispatchEvent(new CustomEvent(\"environment-change\"))),this[hc]=u!=null?u.name===h.name?h:u:null,this[Y].setEnvironmentAndSkybox(this[no],this[hc])}catch(h){if(h instanceof Error)throw this[Y].setEnvironmentAndSkybox(null,null),h}finally{c(1)}}}return _s([Se({type:String,attribute:\"environment-image\"})],n.prototype,\"environmentImage\",void 0),_s([Se({type:String,attribute:\"skybox-image\"})],n.prototype,\"skyboxImage\",void 0),_s([Se({type:Number,attribute:\"shadow-intensity\"})],n.prototype,\"shadowIntensity\",void 0),_s([Se({type:Number,attribute:\"shadow-softness\"})],n.prototype,\"shadowSoftness\",void 0),_s([Se({type:Number})],n.prototype,\"exposure\",void 0),_s([Se({type:String,attribute:\"tone-mapping\"})],n.prototype,\"toneMapping\",void 0),_s([Se({type:String,attribute:\"skybox-height\"})],n.prototype,\"skyboxHeight\",void 0),n})((s=>{var e,t,i,n,r,a,o,l,c,h,u,d,p,A,g,b,m,f;class y extends s{constructor(){super(...arguments),this.cameraControls=!1,this.cameraOrbit=ux,this.cameraTarget=\"auto auto auto\",this.fieldOfView=\"auto\",this.minCameraOrbit=\"auto\",this.maxCameraOrbit=\"auto\",this.minFieldOfView=\"auto\",this.maxFieldOfView=\"auto\",this.interactionPromptThreshold=3e3,this.interactionPrompt=Ll,this.interactionPromptStyle=k0,this.orbitSensitivity=1,this.zoomSensitivity=1,this.panSensitivity=1,this.touchAction=CC,this.disableZoom=!1,this.disablePan=!1,this.disableTap=!1,this.interpolationDecay=50,this.a11y=null,this[e]=this.shadowRoot.querySelector(\".interaction-prompt\"),this[t]=this.shadowRoot.querySelector(\"#prompt\"),this[i]=[this.shadowRoot.querySelector(\"#finger0\"),this.shadowRoot.querySelector(\"#finger1\")],this[n]=this.shadowRoot.querySelector(\".pan-target\"),this[r]=0,this[a]=1/0,this[o]=!1,this[l]=!1,this[c]=St.AUTOMATIC,this[h]=new cg(this[Y].camera,this[Oi],this[Y]),this[u]=new Jr,this[d]=!1,this[p]=!1,this[A]=!1,this[g]={},this[b]=()=>{let x=this[st].changeSource;this[Nl]=x,x===St.USER_INTERACTION&&(this[Ul]=!0,this[Fl]())},this[m]=()=>{this[G0](),this[Vt]();let x=this[st].changeSource;this.dispatchEvent(new CustomEvent(\"camera-change\",{detail:{source:x}}))},this[f]=x=>{this[fc].classList.toggle(\"pointer-tumbling\",x.type===\"pointer-change-start\")}}get inputSensitivity(){return this[st].inputSensitivity}set inputSensitivity(x){this[st].inputSensitivity=x}getCameraOrbit(){let{theta:x,phi:_,radius:w}=this[pd];return{theta:x,phi:_,radius:w,toString(){return`${this.theta}rad ${this.phi}rad ${this.radius}m`}}}getCameraTarget(){return Vn(this[et].isPresenting?this[et].arRenderer.target:this[Y].getDynamicTarget())}getFieldOfView(){return this[st].getFieldOfView()}getMinimumFieldOfView(){return this[st].options.minimumFieldOfView}getMaximumFieldOfView(){return this[st].options.maximumFieldOfView}getIdealAspect(){return this[Y].idealAspect}jumpCameraToGoal(){this[Qa]=!0,this.requestUpdate(Qa,!1)}resetInteractionPrompt(){this[ka]=0,this[Tr]=1/0,this[Ul]=!1,this[Ss]=this.interactionPrompt===Ll&&this.cameraControls}zoom(x){let _=new WheelEvent(\"wheel\",{deltaY:-30*x});this[Oi].dispatchEvent(_)}connectedCallback(){super.connectedCallback(),this[st].addEventListener(\"user-interaction\",this[Ir]),this[st].addEventListener(\"pointer-change-start\",this[Oa]),this[st].addEventListener(\"pointer-change-end\",this[Oa])}disconnectedCallback(){super.disconnectedCallback(),this[st].removeEventListener(\"user-interaction\",this[Ir]),this[st].removeEventListener(\"pointer-change-start\",this[Oa]),this[st].removeEventListener(\"pointer-change-end\",this[Oa])}updated(x){super.updated(x);let _=this[st],w=this[Y];if(x.has(\"cameraControls\")&&(this.cameraControls?(_.enableInteraction(),this.interactionPrompt===Ll&&(this[Ss]=!0)):(_.disableInteraction(),this[Fl]()),this[Oi].setAttribute(\"aria-label\",this[Vr])),x.has(\"disableZoom\")&&(_.disableZoom=this.disableZoom),x.has(\"disablePan\")&&(_.enablePan=!this.disablePan),x.has(\"disableTap\")&&(_.enableTap=!this.disableTap),(x.has(\"interactionPrompt\")||x.has(\"cameraControls\")||x.has(\"src\"))&&(this.interactionPrompt===Ll&&this.cameraControls&&!this[Ul]?this[Ss]=!0:this[Fl]()),x.has(\"interactionPromptStyle\")&&(this[Pl].style.opacity=this.interactionPromptStyle==SC?\"1\":\"0\"),x.has(\"touchAction\")){let B=this.touchAction;_.applyOptions({touchAction:B}),_.updateTouchActionStyle()}x.has(\"orbitSensitivity\")&&(_.orbitSensitivity=this.orbitSensitivity),x.has(\"zoomSensitivity\")&&(_.zoomSensitivity=this.zoomSensitivity),x.has(\"panSensitivity\")&&(_.panSensitivity=this.panSensitivity),x.has(\"interpolationDecay\")&&(_.setDamperDecayTime(this.interpolationDecay),w.setTargetDamperDecayTime(this.interpolationDecay)),x.has(\"a11y\")&&this[z0](),this[Qa]===!0&&Promise.resolve().then(()=>{_.jumpToGoal(),w.jumpToGoal(),this[dd](),this[Qa]=!1})}async updateFraming(){let x=this[Y],_=x.adjustedFoV(x.framedFoVDeg);await x.updateFraming();let w=x.adjustedFoV(x.framedFoVDeg),B=this[st].getFieldOfView()/_;this[st].setFieldOfView(w*B),this[Ga]=!0,this.requestUpdate(\"maxFieldOfView\"),this.requestUpdate(\"fieldOfView\"),this.requestUpdate(\"minCameraOrbit\"),this.requestUpdate(\"maxCameraOrbit\"),this.requestUpdate(\"cameraOrbit\"),await this.updateComplete}interact(x,_,w){let B=this[Oi],I=this[ud];if(I[0].style.opacity===\"1\")return void console.warn(\"interact() failed because an existing interaction is running.\");let F=new Array;F.push({x:Hr(_.x),y:Hr(_.y)});let Q=[{x:F[0].x(0),y:F[0].y(0)}];w!=null&&(F.push({x:Hr(w.x),y:Hr(w.y)}),Q.push({x:F[1].x(0),y:F[1].y(0)}));let R=performance.now(),{width:U,height:O}=this[Y],k=this.getBoundingClientRect(),N=ce=>{for(let[fe,he]of Q.entries()){let{style:se}=I[fe];se.transform=`translateX(${U*he.x}px) translateY(${O*he.y}px)`,ce===\"pointerdown\"?se.opacity=\"1\":ce===\"pointerup\"&&(se.opacity=\"0\");let K={pointerId:fe-5678,pointerType:\"touch\",target:B,clientX:U*he.x+k.x,clientY:O*he.y+k.y,altKey:!0};B.dispatchEvent(new PointerEvent(ce,K))}},G=()=>{let ce=this[Nl];if(ce!==St.AUTOMATIC||!B.isConnected){for(let he of this[ud])he.style.opacity=\"0\";return N(\"pointercancel\"),this.dispatchEvent(new CustomEvent(\"interact-stopped\",{detail:{source:ce}})),void document.removeEventListener(\"visibilitychange\",re)}let fe=Math.min(1,(performance.now()-R)/x);for(let[he,se]of Q.entries())se.x=F[he].x(fe),se.y=F[he].y(fe);N(\"pointermove\"),fe<1?requestAnimationFrame(G):(N(\"pointerup\"),this.dispatchEvent(new CustomEvent(\"interact-stopped\",{detail:{source:St.AUTOMATIC}})),document.removeEventListener(\"visibilitychange\",re))},re=()=>{let ce=0;document.visibilityState===\"hidden\"?ce=performance.now()-R:R=performance.now()-ce};document.addEventListener(\"visibilitychange\",re),N(\"pointerdown\"),this[Nl]=St.AUTOMATIC,requestAnimationFrame(G)}[(e=hd,t=Pl,i=ud,n=pc,r=ka,a=Tr,o=Ul,l=Ss,c=Nl,h=st,u=pd,d=Qa,p=Ad,A=Ga,g=bn,q0)](x){let _=this[st],w=this[Y];w.framedFoVDeg=180*x[0]/Math.PI,_.changeSource=St.NONE,_.setFieldOfView(w.adjustedFoV(w.framedFoVDeg)),this[Ir]()}[V0](x){let _=this[st];if(this[Ga]){let{theta:w,phi:B}=this.getCameraOrbit();x[0]=w,x[1]=B,this[Ga]=!1}_.changeSource=St.NONE,_.setOrbit(x[0],x[1],x[2]),this[Ir]()}[W0](x){this[st].applyOptions({minimumAzimuthalAngle:x[0],minimumPolarAngle:x[1],minimumRadius:x[2]}),this.jumpCameraToGoal()}[X0](x){this[st].applyOptions({maximumAzimuthalAngle:x[0],maximumPolarAngle:x[1],maximumRadius:x[2]}),this[H0](x[2]),this.jumpCameraToGoal()}[K0](x){this[st].applyOptions({minimumFieldOfView:180*x[0]/Math.PI}),this.jumpCameraToGoal()}[J0](x){let _=this[Y].adjustedFoV(180*x[0]/Math.PI);this[st].applyOptions({maximumFieldOfView:_}),this.jumpCameraToGoal()}[j0](x){let[_,w,B]=x;this[et].arRenderer.isPresenting||this[Y].setTarget(_,w,B),this[st].changeSource=St.NONE,this[et].arRenderer.updateTarget(),this[Ir]()}[Yi](x,_){if(super[Yi](x,_),this[et].isPresenting||!this[ks]())return;let w=this[st],B=this[Y],I=performance.now();if(this[Ss]&&this.loaded&&I>this[oo]+this.interactionPromptThreshold&&(this[Ss]=!1,this[Tr]=I,this[hd].classList.add(\"visible\")),isFinite(this[Tr])&&this.interactionPromptStyle===k0){let R=(I-this[Tr])/5e3%1,U=yC(R),O=xC(R);if(this[Pl].style.opacity=`${O}`,U!==this[ka]){let k=U*B.width*.05,N=(U-this[ka])*Math.PI/16;this[Pl].style.transform=`translateX(${k}px)`,w.changeSource=St.AUTOMATIC,w.adjustOrbit(N,0,0),this[ka]=U}}let F=w.update(x,_),Q=B.updateTarget(_);(F||Q)&&this[dd]()}[Fl](){this[Ss]=!1,this[hd].classList.remove(\"visible\"),this[Tr]=1/0}[H0](x){let _=Math.max(this[Y].farRadius(),x),w=Math.abs(2*_);this[st].updateNearFar(0,w)}[G0](){let{theta:x,phi:_}=this[st].getCameraSpherical(this[pd]),w=(4+Math.floor((x%DC+RC)/px))%4,B=Math.floor(_/BC),I=`${EC[B]}${_C[w]}`,F=I;F in this[bn]?this[Bh](this[bn][F]):this[Bh](`View from stage ${I}`)}get[Vr](){let x=\". Use mouse, touch or arrow keys to move.\";return\"interaction-prompt\"in this[bn]&&(x=`. ${this[bn][\"interaction-prompt\"]}`),super[Vr].replace(/\\.$/,\"\")+(this.cameraControls?x:\"\")}async[Rh](x){let _=this[st],w=this[Y],B=w.adjustedFoV(w.framedFoVDeg);super[Rh](x);let I=w.adjustedFoV(w.framedFoVDeg)/B,F=_.getFieldOfView()*(isFinite(I)?I:1);_.updateAspect(this[Y].aspect),this.requestUpdate(\"maxFieldOfView\",this.maxFieldOfView),await this.updateComplete,this[st].setFieldOfView(F),this.jumpCameraToGoal()}[$i](){super[$i](),this[Ad]?this[Ga]=!0:this[Ad]=!0,this.requestUpdate(\"maxFieldOfView\",this.maxFieldOfView),this.requestUpdate(\"fieldOfView\",this.fieldOfView),this.requestUpdate(\"minCameraOrbit\",this.minCameraOrbit),this.requestUpdate(\"maxCameraOrbit\",this.maxCameraOrbit),this.requestUpdate(\"cameraOrbit\",this.cameraOrbit),this.requestUpdate(\"cameraTarget\",this.cameraTarget),this.jumpCameraToGoal()}[(b=Ir,m=dd,f=Oa,z0)](){if(typeof this.a11y==\"string\")if(this.a11y.startsWith(\"{\"))try{this[bn]=JSON.parse(this.a11y)}catch(x){console.warn(\"Error parsing a11y JSON:\",x)}else this.a11y.length>0?console.warn(\"Error not supported format, should be a JSON string:\",this.a11y):this[bn]={};else typeof this.a11y==\"object\"&&this.a11y!=null?this[bn]=Object.assign({},this.a11y):this[bn]={};this[Oi].setAttribute(\"aria-label\",this[Vr])}}return Lt([Se({type:Boolean,attribute:\"camera-controls\"})],y.prototype,\"cameraControls\",void 0),Lt([zn({intrinsics:dx,observeEffects:!0,updateHandler:V0}),Se({type:String,attribute:\"camera-orbit\",hasChanged:()=>!0})],y.prototype,\"cameraOrbit\",void 0),Lt([zn({intrinsics:TC,observeEffects:!0,updateHandler:j0}),Se({type:String,attribute:\"camera-target\",hasChanged:()=>!0})],y.prototype,\"cameraTarget\",void 0),Lt([zn({intrinsics:Q0,observeEffects:!0,updateHandler:q0}),Se({type:String,attribute:\"field-of-view\",hasChanged:()=>!0})],y.prototype,\"fieldOfView\",void 0),Lt([zn({intrinsics:MC,updateHandler:W0}),Se({type:String,attribute:\"min-camera-orbit\",hasChanged:()=>!0})],y.prototype,\"minCameraOrbit\",void 0),Lt([zn({intrinsics:IC,updateHandler:X0}),Se({type:String,attribute:\"max-camera-orbit\",hasChanged:()=>!0})],y.prototype,\"maxCameraOrbit\",void 0),Lt([zn({intrinsics:wC,updateHandler:K0}),Se({type:String,attribute:\"min-field-of-view\",hasChanged:()=>!0})],y.prototype,\"minFieldOfView\",void 0),Lt([zn({intrinsics:Q0,updateHandler:J0}),Se({type:String,attribute:\"max-field-of-view\",hasChanged:()=>!0})],y.prototype,\"maxFieldOfView\",void 0),Lt([Se({type:Number,attribute:\"interaction-prompt-threshold\"})],y.prototype,\"interactionPromptThreshold\",void 0),Lt([Se({type:String,attribute:\"interaction-prompt\"})],y.prototype,\"interactionPrompt\",void 0),Lt([Se({type:String,attribute:\"interaction-prompt-style\"})],y.prototype,\"interactionPromptStyle\",void 0),Lt([Se({type:Number,attribute:\"orbit-sensitivity\"})],y.prototype,\"orbitSensitivity\",void 0),Lt([Se({type:Number,attribute:\"zoom-sensitivity\"})],y.prototype,\"zoomSensitivity\",void 0),Lt([Se({type:Number,attribute:\"pan-sensitivity\"})],y.prototype,\"panSensitivity\",void 0),Lt([Se({type:String,attribute:\"touch-action\"})],y.prototype,\"touchAction\",void 0),Lt([Se({type:Boolean,attribute:\"disable-zoom\"})],y.prototype,\"disableZoom\",void 0),Lt([Se({type:Boolean,attribute:\"disable-pan\"})],y.prototype,\"disablePan\",void 0),Lt([Se({type:Boolean,attribute:\"disable-tap\"})],y.prototype,\"disableTap\",void 0),Lt([Se({type:Number,attribute:\"interpolation-decay\"})],y.prototype,\"interpolationDecay\",void 0),Lt([Se()],y.prototype,\"a11y\",void 0),y})((s=>{var e,t,i,n,r,a,o,l,c,h;class u extends s{constructor(){super(...arguments),this.ar=!1,this.arScale=\"auto\",this.arUsdzMaxTextureSize=\"auto\",this.arPlacement=\"floor\",this.arModes=\"webxr scene-viewer quick-look\",this.iosSrc=null,this.xrEnvironment=!1,this[e]=!1,this[t]=this.shadowRoot.querySelector(\".ar-button\"),this[i]=document.createElement(\"a\"),this[n]=new Set,this[r]=Za,this[a]=!1,this[o]=p=>{p.preventDefault(),this.activateAR()},this[l]=({status:p})=>{p!==Ac&&this[et].arRenderer.presentedScene!==this[Y]||(this.setAttribute(\"ar-status\",p),this.dispatchEvent(new CustomEvent(\"ar-status\",{detail:{status:p}})),p===Ac?this.removeAttribute(\"ar-tracking\"):p===Ax&&this.setAttribute(\"ar-tracking\",gx))},this[c]=({status:p})=>{this.setAttribute(\"ar-tracking\",p),this.dispatchEvent(new CustomEvent(\"ar-tracking\",{detail:{status:p}}))},this[h]=p=>{p.data==\"_apple_ar_quicklook_button_tapped\"&&this.dispatchEvent(new CustomEvent(\"quick-look-button-tapped\"))}}get canActivateAR(){return this[ec]!==Za}connectedCallback(){super.connectedCallback(),this[et].arRenderer!=null&&(this[et].arRenderer.addEventListener(\"status\",this[Hd]),this.setAttribute(\"ar-status\",Ac),this[et].arRenderer.addEventListener(\"tracking\",this[Vd])),this[Fr].addEventListener(\"message\",this[qd])}disconnectedCallback(){super.disconnectedCallback(),this[et].arRenderer!=null&&(this[et].arRenderer.removeEventListener(\"status\",this[Hd]),this[et].arRenderer.removeEventListener(\"tracking\",this[Vd])),this[Fr].removeEventListener(\"message\",this[qd])}update(p){super.update(p),p.has(\"arScale\")&&(this[Y].canScale=this.arScale!==\"fixed\"),p.has(\"arPlacement\")&&(this[Y].updateShadow(),this[Vt]()),p.has(\"arModes\")&&(this[zd]=Cw(this.arModes)),(p.has(\"ar\")||p.has(\"arModes\")||p.has(\"src\")||p.has(\"iosSrc\")||p.has(\"arUsdzMaxTextureSize\"))&&this[eo]()}async activateAR(){switch(this[ec]){case Gd:await this[Rv]();break;case Iv:await this[Tv]();break;case Mv:await this[Bv]();break;default:console.warn(\"No AR Mode can be activated. This is probably due to missing configuration or device capabilities\")}}async[(e=ww,t=_n,i=Fr,n=zd,r=ec,a=tc,o=ic,l=Hd,c=Vd,h=qd,eo)](){var p;let A=Za;if(this.ar){if(this.src!=null)for(let g of this[zd]){if(g===\"webxr\"&&oy&&!Ev&&await this[et].arRenderer.supportsPresentation()){A=Iv;break}if(g===\"scene-viewer\"&&!Sv&&(ES||navigator.userAgentData&&navigator.userAgentData.getHighEntropyValues&&(!((p=(await navigator.userAgentData.getHighEntropyValues([\"formFactor\"])).formFactor)===null||p===void 0)&&p.includes(\"XR\")))){A=Mv;break}if(g===\"quick-look\"&&Xf&&!Wf&&(!ly||this.iosSrc!=null)){A=Gd;break}}A===Za&&this.iosSrc!=null&&Xf&&!Wf&&(A=Gd)}if(A!==Za)this[_n].classList.add(\"enabled\"),this[_n].addEventListener(\"click\",this[ic]);else if(this[_n].classList.contains(\"enabled\")){this[_n].removeEventListener(\"click\",this[ic]),this[_n].classList.remove(\"enabled\");let g=NC;this.setAttribute(\"ar-status\",g),this.dispatchEvent(new CustomEvent(\"ar-status\",{detail:{status:g}}))}this[ec]=A}async[Tv](){console.log(\"Attempting to present in AR with WebXR...\"),await this[jd]();try{this[_n].removeEventListener(\"click\",this[ic]);let{arRenderer:p}=this[et];p.placeOnWall=this.arPlacement===\"wall\",await p.present(this[Y],this.xrEnvironment)}catch(p){console.warn(\"Error while trying to present in AR with WebXR\"),console.error(p),await this[et].arRenderer.stopPresenting(),Ev=!0,console.warn(\"Falling back to next ar-mode\"),await this[eo](),this.activateAR()}finally{this[eo]()}}async[jd](){this.loaded||(this[tc]=!0,this[Zn](),await((p,A,g=null)=>new Promise(b=>{p.addEventListener(A,function m(f){g&&!g(f)||(b(f),p.removeEventListener(A,m))})}))(this,\"load\"),this[tc]=!1)}[qr](){return super[qr]()||this[tc]}async[Bv](){let p=self.location.toString(),A=new URL(p),g=Array.from(this.querySelectorAll(\"extra-model\")).map(v=>v.src).filter(v=>v!=null),b=this.src||g[0]||null;if(!b)return void console.warn(\"No src or extra-model provided for Scene Viewer fallback.\");let m=new URL(b,p);m.hash&&(m.hash=\"\");let f=new URLSearchParams(m.search);if(A.hash=Cv,f.set(\"mode\",\"ar_preferred\"),f.has(\"disable_occlusion\")||f.set(\"disable_occlusion\",\"true\"),this.arScale===\"fixed\"&&f.set(\"resizable\",\"false\"),this.arPlacement===\"wall\"&&f.set(\"enable_vertical_placement\",\"true\"),f.has(\"sound\")){let v=new URL(f.get(\"sound\"),p);f.set(\"sound\",v.toString())}if(f.has(\"link\")){let v=new URL(f.get(\"link\"),p);f.set(\"link\",v.toString())}let y=`intent://arvr.google.com/scene-viewer/1.2?${f.toString()+\"&file=\"+encodeURIComponent(m.toString())}#Intent;scheme=https;package=com.google.android.googlequicksearchbox;action=android.intent.action.VIEW;S.browser_fallback_url=${encodeURIComponent(A.toString())};end;`;self.addEventListener(\"hashchange\",()=>{self.location.hash===Cv&&(Sv=!0,self.history.back(),console.warn(\"Error while trying to present in AR with Scene Viewer\"),console.warn(\"Falling back to next ar-mode\"),this[eo]())},{once:!0}),this[Fr].setAttribute(\"href\",y),console.log(\"Attempting to present in AR with Scene Viewer...\"),this[Fr].click()}async[Rv](){let p=!this.iosSrc;this[_n].classList.remove(\"enabled\");let A=p?await this.prepareUSDZ():this.iosSrc,g=new URL(A,self.location.toString());if(p){let f=self.location.toString(),y=new URL(f),v=Array.from(this.querySelectorAll(\"extra-model\")).map(_=>_.src).filter(_=>_!=null),x=this.src||v[0]||null;if(x){let _=new URL(x,y);_.hash&&(g.hash=_.hash)}}this.arScale===\"fixed\"&&(g.hash&&(g.hash+=\"&\"),g.hash+=\"allowsContentScaling=0\");let b=this[Fr];b.setAttribute(\"rel\",\"ar\");let m=document.createElement(\"img\");b.appendChild(m),b.setAttribute(\"href\",g.toString()),p&&b.setAttribute(\"download\",\"model.usdz\"),b.style.display=\"none\",b.isConnected||this.shadowRoot.appendChild(b),console.log(\"Attempting to present in AR with Quick Look...\"),b.click(),b.removeChild(m),p&&URL.revokeObjectURL(A),this[_n].classList.add(\"enabled\")}async prepareUSDZ(){let p=this[Os].beginActivity(\"usdz-conversion\");await this[jd]();let{models:A,shadow:g,target:b}=this[Y];if(A.length===0||A[0]==null)return\"\";let m=!1;g!=null&&(m=g.visible,g.visible=!1),p(.2);let f=new Qg,y=new ct;y.position.copy(b.position);for(let w of A)b.remove(w),y.add(w);y.updateWorldMatrix(!1,!0);let v=await f.parseAsync(y,{maxTextureSize:isNaN(this.arUsdzMaxTextureSize)?1/0:Math.max(parseInt(this.arUsdzMaxTextureSize),16)});for(let w of A)y.remove(w),b.add(w);let x=new Blob([v],{type:\"model/vnd.usdz+zip\"}),_=URL.createObjectURL(x);return p(1),g!=null&&(g.visible=m),_}}return Bs([Se({type:Boolean,attribute:\"ar\"})],u.prototype,\"ar\",void 0),Bs([Se({type:String,attribute:\"ar-scale\"})],u.prototype,\"arScale\",void 0),Bs([Se({type:String,attribute:\"ar-usdz-max-texture-size\"})],u.prototype,\"arUsdzMaxTextureSize\",void 0),Bs([Se({type:String,attribute:\"ar-placement\"})],u.prototype,\"arPlacement\",void 0),Bs([Se({type:String,attribute:\"ar-modes\"})],u.prototype,\"arModes\",void 0),Bs([Se({type:String,attribute:\"ios-src\"})],u.prototype,\"iosSrc\",void 0),Bs([Se({type:Boolean,attribute:\"xr-environment\"})],u.prototype,\"xrEnvironment\",void 0),u})((s=>{var e,t,i,n,r,a,o,l;class c extends s{static set dracoDecoderLocation(u){$t.setDRACODecoderLocation(u)}static get dracoDecoderLocation(){return $t.getDRACODecoderLocation()}static set ktx2TranscoderLocation(u){$t.setKTX2TranscoderLocation(u)}static get ktx2TranscoderLocation(){return $t.getKTX2TranscoderLocation()}static set meshoptDecoderLocation(u){$t.setMeshoptDecoderLocation(u)}static get meshoptDecoderLocation(){return $t.getMeshoptDecoderLocation()}static set lottieLoaderLocation(u){Qi.singleton.textureUtils.lottieLoaderUrl=u}static get lottieLoaderLocation(){return Qi.singleton.textureUtils.lottieLoaderUrl}static mapURLs(u){Qi.singleton.loader[Rs].manager.setURLModifier(u)}dismissPoster(){this.loaded?this[Jd]():(this[to]=!0,this[Zn]())}showPoster(){let u=this[Kd];if(u.classList.contains(\"show\"))return;u.classList.add(\"show\"),this[Oi].classList.remove(\"show\");let d=this[Nr];d.removeAttribute(\"tabindex\"),d.removeAttribute(\"aria-hidden\");let p=this.modelIsVisible;this[nc]=!1,this[Th](p)}getDimensions(){return this[Y].updateBoundingBoxAndShadowIfDirty(),Vn(this[Y].size)}getBoundingBoxCenter(){return this[Y].updateBoundingBoxAndShadowIfDirty(),Vn(this[Y].boundingBox.getCenter(new T))}constructor(...u){super(...u),this.poster=null,this.reveal=Xd,this.loading=Mw,this[e]=!1,this[t]=!1,this[i]=this.shadowRoot.querySelector(\".slot.poster\"),this[n]=this.shadowRoot.querySelector(\"#default-poster\"),this[r]=this.shadowRoot.querySelector(\"#default-progress-bar > .bar\"),this[a]=this[Nr].getAttribute(\"aria-label\"),this[o]=((b,m)=>{let f=null,y=(...v)=>{f==null&&(b(...v),f=self.setTimeout(()=>f=null,m))};return y.flush=()=>{f!=null&&(self.clearTimeout(f),f=null)},y})(b=>{let m=this[Ur].parentNode;requestAnimationFrame(()=>{this[Ur].style.transform=`scaleX(${b})`,b===0&&(m.removeChild(this[Ur]),m.appendChild(this[Ur])),this[Ur].classList.toggle(\"hide\",b===1)})},100),this[l]=b=>{let m=b.detail.totalProgress,f=b.detail.reason;m===1&&(this[Yd].flush(),this.loaded&&(this[to]||this.reveal===Xd)&&this[Jd]()),this[Yd](m),this.dispatchEvent(new CustomEvent(\"progress\",{detail:{totalProgress:m,reason:f}}))};let d=self.ModelViewerElement||{},p=d.dracoDecoderLocation||\"https://www.gstatic.com/draco/versioned/decoders/1.5.6/\";$t.setDRACODecoderLocation(p);let A=d.ktx2TranscoderLocation||\"https://www.gstatic.com/basis-universal/versioned/2021-04-15-ba1c3e4/\";$t.setKTX2TranscoderLocation(A),d.meshoptDecoderLocation&&$t.setMeshoptDecoderLocation(d.meshoptDecoderLocation);let g=d.lottieLoaderLocation||\"https://cdn.jsdelivr.net/npm/three@0.149.0/examples/jsm/loaders/LottieLoader.js\";Qi.singleton.textureUtils!=null&&(Qi.singleton.textureUtils.lottieLoaderUrl=g)}connectedCallback(){super.connectedCallback(),this.loaded||this.showPoster(),this[Os].addEventListener(\"progress\",this[$d])}disconnectedCallback(){super.disconnectedCallback(),this[Os].removeEventListener(\"progress\",this[$d])}async updated(u){super.updated(u),u.has(\"poster\")&&this.poster!=null&&(this[Nr].style.backgroundImage=`url(${this.poster})`),u.has(\"alt\")&&this[Nr].setAttribute(\"aria-label\",this[Lg]),(u.has(\"reveal\")||u.has(\"loading\"))&&this[Zn]()}[(e=nc,t=to,i=Kd,n=Nr,r=Ur,a=Tw,o=Yd,l=$d,qr)](){let u=Array.from(this.querySelectorAll(\"extra-model\"));return!!(this.src||u.length>0)&&(this[to]||this.loading===Iw||this.reveal===Xd&&this[Fs])}[Jd](){this[to]=!1;let u=this[Kd];if(!u.classList.contains(\"show\"))return;u.classList.remove(\"show\"),this[Oi].classList.add(\"show\");let d=this.modelIsVisible;this[nc]=!0,this[Th](d);let p=this.getRootNode();p&&p.activeElement===this&&this[Oi].focus();let A=this[Nr];A.setAttribute(\"aria-hidden\",\"true\"),A.tabIndex=-1,this.dispatchEvent(new CustomEvent(\"poster-dismissed\"))}[ks](){return super[ks]()&&this[nc]}}return Wd([Se({type:String})],c.prototype,\"poster\",void 0),Wd([Se({type:String})],c.prototype,\"reveal\",void 0),Wd([Se({type:String})],c.prototype,\"loading\",void 0),c})((s=>{var e;class t extends s{constructor(...n){super(n),this.autoplay=!1,this.animationName=void 0,this.animationCrossfadeDuration=300,this[e]=!0,this[Y].subscribeMixerEvent(\"loop\",r=>{let a=r.action._loopCount,o=r.action._clip.name,l=r.action._clip.uuid,c=this[Y].markedAnimations.find(h=>h.name===o);if(c){this[Y].updateAnimationLoop(c.name,c.loopMode,c.repetitionCount);let h=this[Y].markedAnimations.filter(u=>u.name!==o);this[Y].markedAnimations=h}this.dispatchEvent(new CustomEvent(\"loop\",{detail:{count:a,name:o,uuid:l}}))}),this[Y].subscribeMixerEvent(\"finished\",r=>{if(this[Y].appendedAnimations.includes(r.action._clip.name)){let a=this[Y].appendedAnimations.filter(o=>o!==r.action._clip.name);this[Y].appendedAnimations=a}else this[Is]=!0;this.dispatchEvent(new CustomEvent(\"finished\"))})}get availableAnimations(){return this.loaded?this[Y].animationNames:[]}get duration(){return this[Y].duration}get paused(){return this[Y].isAllAnimationsPaused()}get currentTime(){return this[Y].animationTime}get appendedAnimations(){return this[Y].appendedAnimations}set currentTime(n){this[Y].animationTime=n,this[Vt]()}get timeScale(){return this[Y].animationTimeScale}set timeScale(n){this[Y].animationTimeScale=n}pause(n){var r;if(n?.modelIndex==null&&this.paused)return;let a=(r=n?.modelIndex)!==null&&r!==void 0?r:null;this[Y].pauseAnimation(a),this.dispatchEvent(new CustomEvent(\"pause\"))}play(n){var r;if(this.availableAnimations.length>0){let a=(r=n?.modelIndex)!==null&&r!==void 0?r:null;this[Y].unpauseAnimation(a),this[$l](n),this.dispatchEvent(new CustomEvent(\"play\"))}}appendAnimation(n,r){var a;this.availableAnimations.length>0&&(this[Is]=!1,this[Y].unpauseAnimation((a=r?.modelIndex)!==null&&a!==void 0?a:null),this[pv](n,r),this.dispatchEvent(new CustomEvent(\"append-animation\")))}detachAnimation(n,r){var a;this.availableAnimations.length>0&&(this[Is]=!1,this[Y].unpauseAnimation((a=r?.modelIndex)!==null&&a!==void 0?a:null),this[Av](n,r),this.dispatchEvent(new CustomEvent(\"detach-animation\")))}[(e=Is,$i)](){super[$i](),this[Y].pauseAnimation(),this.animationName!=null&&this[$l](),this.autoplay&&this.play()}[Yi](n,r){super[Yi](n,r),this.paused||!this[ks]()&&!this[et].isPresenting||(this[Y].updateAnimation(r/1e3),this[Vt]())}updated(n){super.updated(n),n.has(\"autoplay\")&&this.autoplay&&this.play(),n.has(\"animationName\")&&this[$l]()}[$l](n=rw){var r;let a=(r=n.repetitions)!==null&&r!==void 0?r:1/0,o=n.pingpong?Dc:a===1?To:_p;this[Y].playAnimation(this.animationName,this.animationCrossfadeDuration/1e3,o,a,n.modelIndex),this[Is]&&(this[Y].updateAnimation(0),this[Vt]())}[pv](n=\"\",r={}){var a;let o=Object.assign(Object.assign({},aw),r),l=(a=o.repetitions)!==null&&a!==void 0?a:1/0,c=o.pingpong?Dc:l===1?To:_p,h=!!r.repetitions||\"pingpong\"in r;this[Y].appendAnimation(n||this.animationName,c,l,o.weight,o.timeScale,o.fade,o.warp,o.relativeWarp,o.time,h,o.modelIndex),this[Is]&&(this[Y].updateAnimation(0),this[Vt]())}[Av](n=\"\",r={}){let a=Object.assign(Object.assign({},ow),r);this[Y].detachAnimation(n||this.animationName,a.fade,a.modelIndex),this[Is]&&(this[Y].updateAnimation(0),this[Vt]())}}return Ud([Se({type:Boolean})],t.prototype,\"autoplay\",void 0),Ud([Se({type:String,attribute:\"animation-name\"})],t.prototype,\"animationName\",void 0),Ud([Se({type:Number,attribute:\"animation-crossfade-duration\"})],t.prototype,\"animationCrossfadeDuration\",void 0),t})(Hs))))))));customElements.define(\"model-viewer\",Dw);})();\n/*! Bundled license information:\n\n@google/model-viewer/dist/model-viewer.min.js:\n (**\n * @license\n * Copyright 2019 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n *)\n (**\n * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n *)\n (* @license\n * Copyright 2026 Google LLC. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the 'License');\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an 'AS IS' BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *)\n (**\n * @license\n * Copyright 2010-2026 Three.js Authors\n * SPDX-License-Identifier: MIT\n *)\n (* @license\n * Copyright 2019 Google LLC. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the 'License');\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an 'AS IS' BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *)\n (* @license\n * Copyright 2021 Google LLC. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the 'License');\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an 'AS IS' BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *)\n (* @license\n * Copyright 2020 Google LLC. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the 'License');\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an 'AS IS' BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *)\n (* @license\n * Copyright 2023 Google LLC. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the 'License');\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an 'AS IS' BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *)\n (* @license\n * Copyright 2022 Google LLC. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the 'License');\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an 'AS IS' BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *)\n (* @license\n * Copyright 2025 Google LLC. All Rights Reserved.\n * Licensed under the Apache License, Version 2.0 (the 'License');\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an 'AS IS' BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *)\n (*!\n fflate - fast JavaScript compression/decompression\n \n Licensed under MIT. https://github.com/101arrowz/fflate/blob/master/LICENSE\n version 0.8.2\n *)\n*/\n"; +// draw.io viewer: identical to the desktop vendor distribution, including its notices. +export const MOBILE_DRAWIO_JS = "window.PROXY_URL=window.PROXY_URL||\"https://viewer.diagrams.net/proxy\";window.STYLE_PATH=window.STYLE_PATH||\"https://viewer.diagrams.net/styles\";window.SHAPES_PATH=window.SHAPES_PATH||\"https://viewer.diagrams.net/shapes\";window.STENCIL_PATH=window.STENCIL_PATH||\"https://viewer.diagrams.net/stencils\";window.DRAW_MATH_URL=window.DRAW_MATH_URL||\"https://viewer.diagrams.net/math4/es5\";window.GRAPH_IMAGE_PATH=window.GRAPH_IMAGE_PATH||\"https://viewer.diagrams.net/img\";\nwindow.mxImageBasePath=window.mxImageBasePath||\"https://viewer.diagrams.net/mxgraph/images\";window.mxBasePath=window.mxBasePath||\"https://viewer.diagrams.net/mxgraph/\";window.mxLoadStylesheets=window.mxLoadStylesheets||!1;\n//fgnass.github.com/spin.js#v2.0.0\n!function(a,b){\"object\"==typeof exports?module.exports=b():\"function\"==typeof define&&define.amd?define(b):a.Spinner=b()}(this,function(){\"use strict\";function a(a,b){var c,d=document.createElement(a||\"div\");for(c in b)d[c]=b[c];return d}function b(a){for(var b=1,c=arguments.length;c>b;b++)a.appendChild(arguments[b]);return a}function c(a,b,c,d){var e=[\"opacity\",b,~~(100*a),c,d].join(\"-\"),f=.01+c/d*100,g=Math.max(1-(1-a)/b*(100-f),a),h=j.substring(0,j.indexOf(\"Animation\")).toLowerCase(),i=h&&\"-\"+h+\"-\"||\"\";return l[e]||(m.insertRule(\"@\"+i+\"keyframes \"+e+\"{0%{opacity:\"+g+\"}\"+f+\"%{opacity:\"+a+\"}\"+(f+.01)+\"%{opacity:1}\"+(f+b)%100+\"%{opacity:\"+a+\"}100%{opacity:\"+g+\"}}\",m.cssRules.length),l[e]=1),e}function d(a,b){var c,d,e=a.style;for(b=b.charAt(0).toUpperCase()+b.slice(1),d=0;d',c)}m.addRule(\".spin-vml\",\"behavior:url(#default#VML)\"),h.prototype.lines=function(a,d){function f(){return e(c(\"group\",{coordsize:k+\" \"+k,coordorigin:-j+\" \"+-j}),{width:k,height:k})}function h(a,h,i){b(m,b(e(f(),{rotation:360/d.lines*a+\"deg\",left:~~h}),b(e(c(\"roundrect\",{arcsize:d.corners}),{width:j,height:d.width,left:d.radius,top:-d.width>>1,filter:i}),c(\"fill\",{color:g(d.color,a),opacity:d.opacity}),c(\"stroke\",{opacity:0}))))}var i,j=d.length+d.width,k=2*j,l=2*-(d.width+d.length)+\"px\",m=e(f(),{position:\"absolute\",top:l,left:l});if(d.shadow)for(i=1;i<=d.lines;i++)h(i,-2,\"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)\");for(i=1;i<=d.lines;i++)h(i);return b(a,m)},h.prototype.opacity=function(a,b,c,d){var e=a.firstChild;d=d.shadow&&d.lines||0,e&&b+d>1)+\"px\"})}for(var i,k=0,l=(f.lines-1)*(1-f.direction)/2;ke.length)&&(t=e.length);for(var n=0,o=Array(t);n2?n-2:0),r=2;r1?t-1:0),o=1;o1?n-1:0),r=1;r2&&void 0!==arguments[2]?arguments[2]:b;if(o&&o(e,null),!T(t))return e;let i=t.length;for(;i--;){let o=t[i];if(\"string\"==typeof o){const e=n(o);e!==o&&(r(t)||(t[i]=e),o=e)}e[o]=!0}return e}function F(e){for(let t=0;t/g),J=c(/\\${[\\w\\W]*/g),Q=c(/^data-[\\-\\w.\\u00B7-\\uFFFF]+$/),ee=c(/^aria-[\\-\\w]+$/),te=c(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\\-]+(?:[^a-z+.\\-:]|$))/i),ne=c(/^(?:\\w+script|data):/i),oe=c(/[\\u0000-\\u0020\\u00A0\\u1680\\u180E\\u2000-\\u2029\\u205F\\u3000]/g),re=c(/^html$/i),ie=c(/^[a-z][.\\w]*(-[.\\w]+)+$/i),ae=1,le=3,ce=7,se=8,ue=9,fe=function(){return\"undefined\"==typeof window?null:window};var me=function e(){let t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:fe();const o=t=>e(t);if(o.version=\"3.4.5\",o.removed=[],!t||!t.document||t.document.nodeType!==ue||!t.Element)return o.isSupported=!1,o;let r=t.document;const i=r,a=i.currentScript,c=t.DocumentFragment,u=t.HTMLTemplateElement,f=t.Node,m=t.Element,k=t.NodeFilter,L=t.NamedNodeMap,F=void 0===L?t.NamedNodeMap||t.MozNamedAttrMap:L,me=t.HTMLFormElement,pe=t.DOMParser,de=t.trustedTypes,he=m.prototype,ge=P(he,\"cloneNode\"),ye=P(he,\"remove\"),Te=P(he,\"nextSibling\"),be=P(he,\"childNodes\"),Ae=P(he,\"parentNode\"),Se=f&&f.prototype?P(f.prototype,\"nodeType\"):null;if(\"function\"==typeof u){const e=r.createElement(\"template\");e.content&&e.content.ownerDocument&&(r=e.content.ownerDocument)}let Ee,Ne=\"\";const _e=r,Oe=_e.implementation,De=_e.createNodeIterator,Re=_e.createDocumentFragment,we=_e.getElementsByTagName,Ie=i.importNode;let ve={afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]};o.isSupported=\"function\"==typeof n&&\"function\"==typeof Ae&&Oe&&void 0!==Oe.createHTMLDocument;const Ce=V,xe=Z,ke=J,Le=Q,Me=ee,Fe=ne,ze=oe,Pe=ie;let Ue=te,He=null;const Be=M({},[...U,...H,...B,...j,...Y]);let Ge=null;const je=M({},[...X,...q,...$,...K]);let We=Object.seal(s(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),Ye=null,Xe=null;const qe=Object.seal(s(null,{tagCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeCheck:{writable:!0,configurable:!1,enumerable:!0,value:null}}));let $e=!0,Ke=!0,Ve=!1,Ze=!0,Je=!1,Qe=!0,et=!1,tt=!1,nt=!1,ot=!1,rt=!1,it=!1,at=!0,lt=!1;const ct=\"user-content-\";let st=!0,ut=!1,ft={},mt=null;const pt=M({},[\"annotation-xml\",\"audio\",\"colgroup\",\"desc\",\"foreignobject\",\"head\",\"iframe\",\"math\",\"mi\",\"mn\",\"mo\",\"ms\",\"mtext\",\"noembed\",\"noframes\",\"noscript\",\"plaintext\",\"script\",\"style\",\"svg\",\"template\",\"thead\",\"title\",\"video\",\"xmp\"]);let dt=null;const ht=M({},[\"audio\",\"video\",\"img\",\"source\",\"image\",\"track\"]);let gt=null;const yt=M({},[\"alt\",\"class\",\"for\",\"id\",\"label\",\"name\",\"pattern\",\"placeholder\",\"role\",\"summary\",\"title\",\"value\",\"style\",\"xmlns\"]),Tt=\"http://www.w3.org/1998/Math/MathML\",bt=\"http://www.w3.org/2000/svg\",At=\"http://www.w3.org/1999/xhtml\";let St=At,Et=!1,Nt=null;const _t=M({},[Tt,bt,At],A);let Ot=M({},[\"mi\",\"mo\",\"mn\",\"ms\",\"mtext\"]),Dt=M({},[\"annotation-xml\"]);const Rt=M({},[\"title\",\"style\",\"font\",\"a\",\"script\"]);let wt=null;const It=[\"application/xhtml+xml\",\"text/html\"];let vt=null,Ct=null;const xt=r.createElement(\"form\"),kt=function(e){return e instanceof RegExp||e instanceof Function},Lt=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(Ct&&Ct===e)return;e&&\"object\"==typeof e||(e={}),e=z(e),wt=-1===It.indexOf(e.PARSER_MEDIA_TYPE)?\"text/html\":e.PARSER_MEDIA_TYPE,vt=\"application/xhtml+xml\"===wt?A:b,He=I(e,\"ALLOWED_TAGS\")&&T(e.ALLOWED_TAGS)?M({},e.ALLOWED_TAGS,vt):Be,Ge=I(e,\"ALLOWED_ATTR\")&&T(e.ALLOWED_ATTR)?M({},e.ALLOWED_ATTR,vt):je,Nt=I(e,\"ALLOWED_NAMESPACES\")&&T(e.ALLOWED_NAMESPACES)?M({},e.ALLOWED_NAMESPACES,A):_t,gt=I(e,\"ADD_URI_SAFE_ATTR\")&&T(e.ADD_URI_SAFE_ATTR)?M(z(yt),e.ADD_URI_SAFE_ATTR,vt):yt,dt=I(e,\"ADD_DATA_URI_TAGS\")&&T(e.ADD_DATA_URI_TAGS)?M(z(ht),e.ADD_DATA_URI_TAGS,vt):ht,mt=I(e,\"FORBID_CONTENTS\")&&T(e.FORBID_CONTENTS)?M({},e.FORBID_CONTENTS,vt):pt,Ye=I(e,\"FORBID_TAGS\")&&T(e.FORBID_TAGS)?M({},e.FORBID_TAGS,vt):z({}),Xe=I(e,\"FORBID_ATTR\")&&T(e.FORBID_ATTR)?M({},e.FORBID_ATTR,vt):z({}),ft=!!I(e,\"USE_PROFILES\")&&(e.USE_PROFILES&&\"object\"==typeof e.USE_PROFILES?z(e.USE_PROFILES):e.USE_PROFILES),$e=!1!==e.ALLOW_ARIA_ATTR,Ke=!1!==e.ALLOW_DATA_ATTR,Ve=e.ALLOW_UNKNOWN_PROTOCOLS||!1,Ze=!1!==e.ALLOW_SELF_CLOSE_IN_ATTR,Je=e.SAFE_FOR_TEMPLATES||!1,Qe=!1!==e.SAFE_FOR_XML,et=e.WHOLE_DOCUMENT||!1,ot=e.RETURN_DOM||!1,rt=e.RETURN_DOM_FRAGMENT||!1,it=e.RETURN_TRUSTED_TYPE||!1,nt=e.FORCE_BODY||!1,at=!1!==e.SANITIZE_DOM,lt=e.SANITIZE_NAMED_PROPS||!1,st=!1!==e.KEEP_CONTENT,ut=e.IN_PLACE||!1,Ue=function(e){try{return C(e,\"\"),!0}catch(e){return!1}}(e.ALLOWED_URI_REGEXP)?e.ALLOWED_URI_REGEXP:te,St=\"string\"==typeof e.NAMESPACE?e.NAMESPACE:At,Ot=I(e,\"MATHML_TEXT_INTEGRATION_POINTS\")&&e.MATHML_TEXT_INTEGRATION_POINTS&&\"object\"==typeof e.MATHML_TEXT_INTEGRATION_POINTS?z(e.MATHML_TEXT_INTEGRATION_POINTS):M({},[\"mi\",\"mo\",\"mn\",\"ms\",\"mtext\"]),Dt=I(e,\"HTML_INTEGRATION_POINTS\")&&e.HTML_INTEGRATION_POINTS&&\"object\"==typeof e.HTML_INTEGRATION_POINTS?z(e.HTML_INTEGRATION_POINTS):M({},[\"annotation-xml\"]);const t=I(e,\"CUSTOM_ELEMENT_HANDLING\")&&e.CUSTOM_ELEMENT_HANDLING&&\"object\"==typeof e.CUSTOM_ELEMENT_HANDLING?z(e.CUSTOM_ELEMENT_HANDLING):s(null);if(We=s(null),I(t,\"tagNameCheck\")&&kt(t.tagNameCheck)&&(We.tagNameCheck=t.tagNameCheck),I(t,\"attributeNameCheck\")&&kt(t.attributeNameCheck)&&(We.attributeNameCheck=t.attributeNameCheck),I(t,\"allowCustomizedBuiltInElements\")&&\"boolean\"==typeof t.allowCustomizedBuiltInElements&&(We.allowCustomizedBuiltInElements=t.allowCustomizedBuiltInElements),Je&&(Ke=!1),rt&&(ot=!0),ft&&(He=M({},Y),Ge=s(null),!0===ft.html&&(M(He,U),M(Ge,X)),!0===ft.svg&&(M(He,H),M(Ge,q),M(Ge,K)),!0===ft.svgFilters&&(M(He,B),M(Ge,q),M(Ge,K)),!0===ft.mathMl&&(M(He,j),M(Ge,$),M(Ge,K))),qe.tagCheck=null,qe.attributeCheck=null,I(e,\"ADD_TAGS\")&&(\"function\"==typeof e.ADD_TAGS?qe.tagCheck=e.ADD_TAGS:T(e.ADD_TAGS)&&(He===Be&&(He=z(He)),M(He,e.ADD_TAGS,vt))),I(e,\"ADD_ATTR\")&&(\"function\"==typeof e.ADD_ATTR?qe.attributeCheck=e.ADD_ATTR:T(e.ADD_ATTR)&&(Ge===je&&(Ge=z(Ge)),M(Ge,e.ADD_ATTR,vt))),I(e,\"ADD_URI_SAFE_ATTR\")&&T(e.ADD_URI_SAFE_ATTR)&&M(gt,e.ADD_URI_SAFE_ATTR,vt),I(e,\"FORBID_CONTENTS\")&&T(e.FORBID_CONTENTS)&&(mt===pt&&(mt=z(mt)),M(mt,e.FORBID_CONTENTS,vt)),I(e,\"ADD_FORBID_CONTENTS\")&&T(e.ADD_FORBID_CONTENTS)&&(mt===pt&&(mt=z(mt)),M(mt,e.ADD_FORBID_CONTENTS,vt)),st&&(He[\"#text\"]=!0),et&&M(He,[\"html\",\"head\",\"body\"]),He.table&&(M(He,[\"tbody\"]),delete Ye.tbody),e.TRUSTED_TYPES_POLICY){if(\"function\"!=typeof e.TRUSTED_TYPES_POLICY.createHTML)throw x('TRUSTED_TYPES_POLICY configuration option must provide a \"createHTML\" hook.');if(\"function\"!=typeof e.TRUSTED_TYPES_POLICY.createScriptURL)throw x('TRUSTED_TYPES_POLICY configuration option must provide a \"createScriptURL\" hook.');Ee=e.TRUSTED_TYPES_POLICY,Ne=Ee.createHTML(\"\")}else void 0===Ee&&(Ee=function(e,t){if(\"object\"!=typeof e||\"function\"!=typeof e.createPolicy)return null;let n=null;const o=\"data-tt-policy-suffix\";t&&t.hasAttribute(o)&&(n=t.getAttribute(o));const r=\"dompurify\"+(n?\"#\"+n:\"\");try{return e.createPolicy(r,{createHTML:e=>e,createScriptURL:e=>e})}catch(e){return console.warn(\"TrustedTypes policy \"+r+\" could not be created.\"),null}}(de,a)),null!==Ee&&\"string\"==typeof Ne&&(Ne=Ee.createHTML(\"\"));l&&l(e),Ct=e},Mt=M({},[...H,...B,...G]),Ft=M({},[...j,...W]),zt=function(e){g(o.removed,{element:e});try{Ae(e).removeChild(e)}catch(t){ye(e)}},Pt=function(e,t){try{g(o.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){g(o.removed,{attribute:null,from:t})}if(t.removeAttribute(e),\"is\"===e)if(ot||rt)try{zt(t)}catch(e){}else try{t.setAttribute(e,\"\")}catch(e){}},Ut=function(e){let t=null,n=null;if(nt)e=\"\"+e;else{const t=S(e,/^[\\r\\n\\t ]+/);n=t&&t[0]}\"application/xhtml+xml\"===wt&&St===At&&(e=''+e+\"\");const o=Ee?Ee.createHTML(e):e;if(St===At)try{t=(new pe).parseFromString(o,wt)}catch(e){}if(!t||!t.documentElement){t=Oe.createDocument(St,\"template\",null);try{t.documentElement.innerHTML=Et?Ne:o}catch(e){}}const i=t.body||t.documentElement;return e&&n&&i.insertBefore(r.createTextNode(n),i.childNodes[0]||null),St===At?we.call(t,et?\"html\":\"body\")[0]:et?t.documentElement:i},Ht=function(e){return De.call(e.ownerDocument||e,e,k.SHOW_ELEMENT|k.SHOW_COMMENT|k.SHOW_TEXT|k.SHOW_PROCESSING_INSTRUCTION|k.SHOW_CDATA_SECTION,null)},Bt=function(e){e.normalize();const t=De.call(e.ownerDocument||e,e,k.SHOW_TEXT|k.SHOW_COMMENT|k.SHOW_CDATA_SECTION|k.SHOW_PROCESSING_INSTRUCTION,null);let n=t.nextNode();for(;n;){let e=n.data;p([Ce,xe,ke],t=>{e=E(e,t,\" \")}),n.data=e,n=t.nextNode()}},Gt=function(e){return e instanceof me&&(\"string\"!=typeof e.nodeName||\"string\"!=typeof e.textContent||\"function\"!=typeof e.removeChild||!(e.attributes instanceof F)||\"function\"!=typeof e.removeAttribute||\"function\"!=typeof e.setAttribute||\"string\"!=typeof e.namespaceURI||\"function\"!=typeof e.insertBefore||\"function\"!=typeof e.hasChildNodes)},jt=function(e){if(!Se||\"object\"!=typeof e||null===e)return!1;try{return\"number\"==typeof Se(e)}catch(e){return!1}};function Wt(e,t,n){p(e,e=>{e.call(o,t,n,Ct)})}const Yt=function(e){let t=null;if(Wt(ve.beforeSanitizeElements,e,null),Gt(e))return zt(e),!0;const n=vt(e.nodeName);if(Wt(ve.uponSanitizeElement,e,{tagName:n,allowedTags:He}),Qe&&e.hasChildNodes()&&!jt(e.firstElementChild)&&C(/<[/\\w!]/g,e.innerHTML)&&C(/<[/\\w!]/g,e.textContent))return zt(e),!0;if(Qe&&e.namespaceURI===At&&\"style\"===n&&jt(e.firstElementChild))return zt(e),!0;if(e.nodeType===ce)return zt(e),!0;if(Qe&&e.nodeType===se&&C(/<[/\\w]/g,e.data))return zt(e),!0;if(Ye[n]||!(qe.tagCheck instanceof Function&&qe.tagCheck(n))&&!He[n]){if(!Ye[n]&&$t(n)){if(We.tagNameCheck instanceof RegExp&&C(We.tagNameCheck,n))return!1;if(We.tagNameCheck instanceof Function&&We.tagNameCheck(n))return!1}if(st&&!mt[n]){const t=Ae(e)||e.parentNode,n=be(e)||e.childNodes;if(n&&t){for(let o=n.length-1;o>=0;--o){const r=ge(n[o],!0);t.insertBefore(r,Te(e))}}}return zt(e),!0}return e instanceof m&&!function(e){let t=Ae(e);t&&t.tagName||(t={namespaceURI:St,tagName:\"template\"});const n=b(e.tagName),o=b(t.tagName);return!!Nt[e.namespaceURI]&&(e.namespaceURI===bt?t.namespaceURI===At?\"svg\"===n:t.namespaceURI===Tt?\"svg\"===n&&(\"annotation-xml\"===o||Ot[o]):Boolean(Mt[n]):e.namespaceURI===Tt?t.namespaceURI===At?\"math\"===n:t.namespaceURI===bt?\"math\"===n&&Dt[o]:Boolean(Ft[n]):e.namespaceURI===At?!(t.namespaceURI===bt&&!Dt[o])&&!(t.namespaceURI===Tt&&!Ot[o])&&!Ft[n]&&(Rt[n]||!Mt[n]):!(\"application/xhtml+xml\"!==wt||!Nt[e.namespaceURI]))}(e)?(zt(e),!0):\"noscript\"!==n&&\"noembed\"!==n&&\"noframes\"!==n||!C(/<\\/no(script|embed|frames)/i,e.innerHTML)?(Je&&e.nodeType===le&&(t=e.textContent,p([Ce,xe,ke],e=>{t=E(t,e,\" \")}),e.textContent!==t&&(g(o.removed,{element:e.cloneNode()}),e.textContent=t)),Wt(ve.afterSanitizeElements,e,null),!1):(zt(e),!0)},Xt=function(e,t,n){if(Xe[t])return!1;if(at&&(\"id\"===t||\"name\"===t)&&(n in r||n in xt))return!1;const o=Ge[t]||qe.attributeCheck instanceof Function&&qe.attributeCheck(t,e);if(Ke&&!Xe[t]&&C(Le,t));else if($e&&C(Me,t));else if(!o||Xe[t]){if(!($t(e)&&(We.tagNameCheck instanceof RegExp&&C(We.tagNameCheck,e)||We.tagNameCheck instanceof Function&&We.tagNameCheck(e))&&(We.attributeNameCheck instanceof RegExp&&C(We.attributeNameCheck,t)||We.attributeNameCheck instanceof Function&&We.attributeNameCheck(t,e))||\"is\"===t&&We.allowCustomizedBuiltInElements&&(We.tagNameCheck instanceof RegExp&&C(We.tagNameCheck,n)||We.tagNameCheck instanceof Function&&We.tagNameCheck(n))))return!1}else if(gt[t]);else if(C(Ue,E(n,ze,\"\")));else if(\"src\"!==t&&\"xlink:href\"!==t&&\"href\"!==t||\"script\"===e||0!==N(n,\"data:\")||!dt[e]){if(Ve&&!C(Fe,E(n,ze,\"\")));else if(n)return!1}else;return!0},qt=M({},[\"annotation-xml\",\"color-profile\",\"font-face\",\"font-face-format\",\"font-face-name\",\"font-face-src\",\"font-face-uri\",\"missing-glyph\"]),$t=function(e){return!qt[b(e)]&&C(Pe,e)},Kt=function(e){Wt(ve.beforeSanitizeAttributes,e,null);const t=e.attributes;if(!t||Gt(e))return;const n={attrName:\"\",attrValue:\"\",keepAttr:!0,allowedAttributes:Ge,forceKeepAttr:void 0};let r=t.length;for(;r--;){const i=t[r],a=i.name,l=i.namespaceURI,c=i.value,s=vt(a),u=c;let f=\"value\"===a?u:_(u);if(n.attrName=s,n.attrValue=f,n.keepAttr=!0,n.forceKeepAttr=void 0,Wt(ve.uponSanitizeAttribute,e,n),f=n.attrValue,!lt||\"id\"!==s&&\"name\"!==s||0===N(f,ct)||(Pt(a,e),f=ct+f),Qe&&C(/((--!?|])>)|<\\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i,f)){Pt(a,e);continue}if(\"attributename\"===s&&S(f,\"href\")){Pt(a,e);continue}if(n.forceKeepAttr)continue;if(!n.keepAttr){Pt(a,e);continue}if(!Ze&&C(/\\/>/i,f)){Pt(a,e);continue}Je&&p([Ce,xe,ke],e=>{f=E(f,e,\" \")});const m=vt(e.nodeName);if(Xt(m,s,f)){if(Ee&&\"object\"==typeof de&&\"function\"==typeof de.getAttributeType)if(l);else switch(de.getAttributeType(m,s)){case\"TrustedHTML\":f=Ee.createHTML(f);break;case\"TrustedScriptURL\":f=Ee.createScriptURL(f)}if(f!==u)try{l?e.setAttributeNS(l,a,f):e.setAttribute(a,f),Gt(e)?zt(e):h(o.removed)}catch(t){Pt(a,e)}}else Pt(a,e)}Wt(ve.afterSanitizeAttributes,e,null)},Vt=function(e){let t=null;const n=Ht(e);for(Wt(ve.beforeSanitizeShadowDOM,e,null);t=n.nextNode();)Wt(ve.uponSanitizeShadowNode,t,null),Yt(t),Kt(t),t.content instanceof c&&Vt(t.content);Wt(ve.afterSanitizeShadowDOM,e,null)},Zt=function(e){if(e.nodeType===ae&&e.shadowRoot instanceof c){const t=e.shadowRoot;Zt(t),Vt(t)}const t=e.childNodes;if(!t)return;const n=[];p(t,e=>{g(n,e)});for(const e of n)Zt(e)};return o.sanitize=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=null,r=null,a=null,l=null;if(Et=!e,Et&&(e=\"\\x3c!--\\x3e\"),\"string\"!=typeof e&&!jt(e)&&\"string\"!=typeof(e=function(e){switch(typeof e){case\"string\":return e;case\"number\":return O(e);case\"boolean\":return D(e);case\"bigint\":return R?R(e):\"0\";case\"symbol\":return w?w(e):\"Symbol()\";case\"undefined\":default:return v(e);case\"function\":case\"object\":{if(null===e)return v(e);const t=e,n=P(t,\"toString\");if(\"function\"==typeof n){const e=n(t);return\"string\"==typeof e?e:v(e)}return v(e)}}}(e)))throw x(\"dirty is not a string, aborting\");if(!o.isSupported)return e;if(tt||Lt(t),o.removed=[],\"string\"==typeof e&&(ut=!1),ut){const t=e.nodeName;if(\"string\"==typeof t){const e=vt(t);if(!He[e]||Ye[e])throw x(\"root node is forbidden and cannot be sanitized in-place\")}Zt(e)}else if(jt(e))n=Ut(\"\\x3c!----\\x3e\"),r=n.ownerDocument.importNode(e,!0),r.nodeType===ae&&\"BODY\"===r.nodeName||\"HTML\"===r.nodeName?n=r:n.appendChild(r),Zt(r);else{if(!ot&&!Je&&!et&&-1===e.indexOf(\"<\"))return Ee&&it?Ee.createHTML(e):e;if(n=Ut(e),!n)return ot?null:it?Ne:\"\"}n&&nt&&zt(n.firstChild);const s=Ht(ut?e:n);for(;a=s.nextNode();)Yt(a),Kt(a),a.content instanceof c&&Vt(a.content);if(ut)return Je&&Bt(e),e;if(ot){if(Je&&Bt(n),rt)for(l=Re.call(n.ownerDocument);n.firstChild;)l.appendChild(n.firstChild);else l=n;return(Ge.shadowroot||Ge.shadowrootmode)&&(l=Ie.call(i,l,!0)),l}let u=et?n.outerHTML:n.innerHTML;return et&&He[\"!doctype\"]&&n.ownerDocument&&n.ownerDocument.doctype&&n.ownerDocument.doctype.name&&C(re,n.ownerDocument.doctype.name)&&(u=\"\\n\"+u),Je&&p([Ce,xe,ke],e=>{u=E(u,e,\" \")}),Ee&&it?Ee.createHTML(u):u},o.setConfig=function(){Lt(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}),tt=!0},o.clearConfig=function(){Ct=null,tt=!1},o.isValidAttribute=function(e,t,n){Ct||Lt({});const o=vt(e),r=vt(t);return Xt(o,r,n)},o.addHook=function(e,t){\"function\"==typeof t&&g(ve[e],t)},o.removeHook=function(e,t){if(void 0!==t){const n=d(ve[e],t);return-1===n?void 0:y(ve[e],n,1)[0]}return h(ve[e])},o.removeHooks=function(e){ve[e]=[]},o.removeAllHooks=function(){ve={afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}},o}();return me});\n//# sourceMappingURL=purify.min.js.map\n/*! pako 2.1.0 https://github.com/nodeca/pako @license (MIT AND Zlib) */\n!function(t,e){\"object\"==typeof exports&&\"undefined\"!=typeof module?e(exports):\"function\"==typeof define&&define.amd?define([\"exports\"],e):e((t=\"undefined\"!=typeof globalThis?globalThis:t||self).pako={})}(this,(function(t){\"use strict\";function e(t){for(var e=t.length;--e>=0;)t[e]=0}var a=256,n=286,i=30,r=15,s=new Uint8Array([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]),o=new Uint8Array([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]),l=new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7]),h=new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]),d=new Array(576);e(d);var _=new Array(60);e(_);var f=new Array(512);e(f);var u=new Array(256);e(u);var c=new Array(29);e(c);var w,m,b,g=new Array(i);function p(t,e,a,n,i){this.static_tree=t,this.extra_bits=e,this.extra_base=a,this.elems=n,this.max_length=i,this.has_stree=t&&t.length}function v(t,e){this.dyn_tree=t,this.max_code=0,this.stat_desc=e}e(g);var k=function(t){return t<256?f[t]:f[256+(t>>>7)]},y=function(t,e){t.pending_buf[t.pending++]=255&e,t.pending_buf[t.pending++]=e>>>8&255},x=function(t,e,a){t.bi_valid>16-a?(t.bi_buf|=e<>16-t.bi_valid,t.bi_valid+=a-16):(t.bi_buf|=e<>>=1,a<<=1}while(--e>0);return a>>>1},E=function(t,e,a){var n,i,s=new Array(16),o=0;for(n=1;n<=r;n++)o=o+a[n-1]<<1,s[n]=o;for(i=0;i<=e;i++){var l=t[2*i+1];0!==l&&(t[2*i]=A(s[l]++,l))}},R=function(t){var e;for(e=0;e8?y(t,t.bi_buf):t.bi_valid>0&&(t.pending_buf[t.pending++]=t.bi_buf),t.bi_buf=0,t.bi_valid=0},S=function(t,e,a,n){var i=2*e,r=2*a;return t[i]>1;a>=1;a--)U(t,s,a);i=h;do{a=t.heap[1],t.heap[1]=t.heap[t.heap_len--],U(t,s,1),n=t.heap[1],t.heap[--t.heap_max]=a,t.heap[--t.heap_max]=n,s[2*i]=s[2*a]+s[2*n],t.depth[i]=(t.depth[a]>=t.depth[n]?t.depth[a]:t.depth[n])+1,s[2*a+1]=s[2*n+1]=i,t.heap[1]=i++,U(t,s,1)}while(t.heap_len>=2);t.heap[--t.heap_max]=t.heap[1],function(t,e){var a,n,i,s,o,l,h=e.dyn_tree,d=e.max_code,_=e.stat_desc.static_tree,f=e.stat_desc.has_stree,u=e.stat_desc.extra_bits,c=e.stat_desc.extra_base,w=e.stat_desc.max_length,m=0;for(s=0;s<=r;s++)t.bl_count[s]=0;for(h[2*t.heap[t.heap_max]+1]=0,a=t.heap_max+1;a<573;a++)(s=h[2*h[2*(n=t.heap[a])+1]+1]+1)>w&&(s=w,m++),h[2*n+1]=s,n>d||(t.bl_count[s]++,o=0,n>=c&&(o=u[n-c]),l=h[2*n],t.opt_len+=l*(s+o),f&&(t.static_len+=l*(_[2*n+1]+o)));if(0!==m){do{for(s=w-1;0===t.bl_count[s];)s--;t.bl_count[s]--,t.bl_count[s+1]+=2,t.bl_count[w]--,m-=2}while(m>0);for(s=w;0!==s;s--)for(n=t.bl_count[s];0!==n;)(i=t.heap[--a])>d||(h[2*i+1]!==s&&(t.opt_len+=(s-h[2*i+1])*h[2*i],h[2*i+1]=s),n--)}}(t,e),E(s,d,t.bl_count)},O=function(t,e,a){var n,i,r=-1,s=e[1],o=0,l=7,h=4;for(0===s&&(l=138,h=3),e[2*(a+1)+1]=65535,n=0;n<=a;n++)i=s,s=e[2*(n+1)+1],++o0?(2===t.strm.data_type&&(t.strm.data_type=function(t){var e,n=4093624447;for(e=0;e<=31;e++,n>>>=1)if(1&n&&0!==t.dyn_ltree[2*e])return 0;if(0!==t.dyn_ltree[18]||0!==t.dyn_ltree[20]||0!==t.dyn_ltree[26])return 1;for(e=32;e=3&&0===t.bl_tree[2*h[e]+1];e--);return t.opt_len+=3*(e+1)+5+5+4,e}(t),r=t.opt_len+3+7>>>3,(s=t.static_len+3+7>>>3)<=r&&(r=s)):r=s=n+5,n+4<=r&&-1!==e?L(t,e,n,i):4===t.strategy||s===r?(x(t,2+(i?1:0),3),D(t,d,_)):(x(t,4+(i?1:0),3),function(t,e,a,n){var i;for(x(t,e-257,5),x(t,a-1,5),x(t,n-4,4),i=0;i>=7;h>8,t.pending_buf[t.sym_buf+t.sym_next++]=n,0===e?t.dyn_ltree[2*n]++:(t.matches++,e--,t.dyn_ltree[2*(u[n]+a+1)]++,t.dyn_dtree[2*k(e)]++),t.sym_next===t.sym_end},_tr_align:function(t){x(t,2,3),z(t,256,d),function(t){16===t.bi_valid?(y(t,t.bi_buf),t.bi_buf=0,t.bi_valid=0):t.bi_valid>=8&&(t.pending_buf[t.pending++]=255&t.bi_buf,t.bi_buf>>=8,t.bi_valid-=8)}(t)}},C=function(t,e,a,n){for(var i=65535&t|0,r=t>>>16&65535|0,s=0;0!==a;){a-=s=a>2e3?2e3:a;do{r=r+(i=i+e[n++]|0)|0}while(--s);i%=65521,r%=65521}return i|r<<16|0},M=new Uint32Array(function(){for(var t,e=[],a=0;a<256;a++){t=a;for(var n=0;n<8;n++)t=1&t?3988292384^t>>>1:t>>>1;e[a]=t}return e}()),H=function(t,e,a,n){var i=M,r=n+a;t^=-1;for(var s=n;s>>8^i[255&(t^e[s])];return-1^t},j={2:\"need dictionary\",1:\"stream end\",0:\"\",\"-1\":\"file error\",\"-2\":\"stream error\",\"-3\":\"data error\",\"-4\":\"insufficient memory\",\"-5\":\"buffer error\",\"-6\":\"incompatible version\"},K={Z_NO_FLUSH:0,Z_PARTIAL_FLUSH:1,Z_SYNC_FLUSH:2,Z_FULL_FLUSH:3,Z_FINISH:4,Z_BLOCK:5,Z_TREES:6,Z_OK:0,Z_STREAM_END:1,Z_NEED_DICT:2,Z_ERRNO:-1,Z_STREAM_ERROR:-2,Z_DATA_ERROR:-3,Z_MEM_ERROR:-4,Z_BUF_ERROR:-5,Z_NO_COMPRESSION:0,Z_BEST_SPEED:1,Z_BEST_COMPRESSION:9,Z_DEFAULT_COMPRESSION:-1,Z_FILTERED:1,Z_HUFFMAN_ONLY:2,Z_RLE:3,Z_FIXED:4,Z_DEFAULT_STRATEGY:0,Z_BINARY:0,Z_TEXT:1,Z_UNKNOWN:2,Z_DEFLATED:8},P=B._tr_init,Y=B._tr_stored_block,G=B._tr_flush_block,X=B._tr_tally,W=B._tr_align,q=K.Z_NO_FLUSH,J=K.Z_PARTIAL_FLUSH,Q=K.Z_FULL_FLUSH,V=K.Z_FINISH,$=K.Z_BLOCK,tt=K.Z_OK,et=K.Z_STREAM_END,at=K.Z_STREAM_ERROR,nt=K.Z_DATA_ERROR,it=K.Z_BUF_ERROR,rt=K.Z_DEFAULT_COMPRESSION,st=K.Z_FILTERED,ot=K.Z_HUFFMAN_ONLY,lt=K.Z_RLE,ht=K.Z_FIXED,dt=K.Z_DEFAULT_STRATEGY,_t=K.Z_UNKNOWN,ft=K.Z_DEFLATED,ut=258,ct=262,wt=42,mt=113,bt=666,gt=function(t,e){return t.msg=j[e],e},pt=function(t){return 2*t-(t>4?9:0)},vt=function(t){for(var e=t.length;--e>=0;)t[e]=0},kt=function(t){var e,a,n,i=t.w_size;n=e=t.hash_size;do{a=t.head[--n],t.head[n]=a>=i?a-i:0}while(--e);n=e=i;do{a=t.prev[--n],t.prev[n]=a>=i?a-i:0}while(--e)},yt=function(t,e,a){return(e<t.avail_out&&(a=t.avail_out),0!==a&&(t.output.set(e.pending_buf.subarray(e.pending_out,e.pending_out+a),t.next_out),t.next_out+=a,e.pending_out+=a,t.total_out+=a,t.avail_out-=a,e.pending-=a,0===e.pending&&(e.pending_out=0))},zt=function(t,e){G(t,t.block_start>=0?t.block_start:-1,t.strstart-t.block_start,e),t.block_start=t.strstart,xt(t.strm)},At=function(t,e){t.pending_buf[t.pending++]=e},Et=function(t,e){t.pending_buf[t.pending++]=e>>>8&255,t.pending_buf[t.pending++]=255&e},Rt=function(t,e,a,n){var i=t.avail_in;return i>n&&(i=n),0===i?0:(t.avail_in-=i,e.set(t.input.subarray(t.next_in,t.next_in+i),a),1===t.state.wrap?t.adler=C(t.adler,e,i,a):2===t.state.wrap&&(t.adler=H(t.adler,e,i,a)),t.next_in+=i,t.total_in+=i,i)},Zt=function(t,e){var a,n,i=t.max_chain_length,r=t.strstart,s=t.prev_length,o=t.nice_match,l=t.strstart>t.w_size-ct?t.strstart-(t.w_size-ct):0,h=t.window,d=t.w_mask,_=t.prev,f=t.strstart+ut,u=h[r+s-1],c=h[r+s];t.prev_length>=t.good_match&&(i>>=2),o>t.lookahead&&(o=t.lookahead);do{if(h[(a=e)+s]===c&&h[a+s-1]===u&&h[a]===h[r]&&h[++a]===h[r+1]){r+=2,a++;do{}while(h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&h[++r]===h[++a]&&rs){if(t.match_start=e,s=n,n>=o)break;u=h[r+s-1],c=h[r+s]}}}while((e=_[e&d])>l&&0!=--i);return s<=t.lookahead?s:t.lookahead},St=function(t){var e,a,n,i=t.w_size;do{if(a=t.window_size-t.lookahead-t.strstart,t.strstart>=i+(i-ct)&&(t.window.set(t.window.subarray(i,i+i-a),0),t.match_start-=i,t.strstart-=i,t.block_start-=i,t.insert>t.strstart&&(t.insert=t.strstart),kt(t),a+=i),0===t.strm.avail_in)break;if(e=Rt(t.strm,t.window,t.strstart+t.lookahead,a),t.lookahead+=e,t.lookahead+t.insert>=3)for(n=t.strstart-t.insert,t.ins_h=t.window[n],t.ins_h=yt(t,t.ins_h,t.window[n+1]);t.insert&&(t.ins_h=yt(t,t.ins_h,t.window[n+3-1]),t.prev[n&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=n,n++,t.insert--,!(t.lookahead+t.insert<3)););}while(t.lookaheadt.w_size?t.w_size:t.pending_buf_size-5,s=0,o=t.strm.avail_in;do{if(a=65535,i=t.bi_valid+42>>3,t.strm.avail_out(n=t.strstart-t.block_start)+t.strm.avail_in&&(a=n+t.strm.avail_in),a>i&&(a=i),a>8,t.pending_buf[t.pending-2]=~a,t.pending_buf[t.pending-1]=~a>>8,xt(t.strm),n&&(n>a&&(n=a),t.strm.output.set(t.window.subarray(t.block_start,t.block_start+n),t.strm.next_out),t.strm.next_out+=n,t.strm.avail_out-=n,t.strm.total_out+=n,t.block_start+=n,a-=n),a&&(Rt(t.strm,t.strm.output,t.strm.next_out,a),t.strm.next_out+=a,t.strm.avail_out-=a,t.strm.total_out+=a)}while(0===s);return(o-=t.strm.avail_in)&&(o>=t.w_size?(t.matches=2,t.window.set(t.strm.input.subarray(t.strm.next_in-t.w_size,t.strm.next_in),0),t.strstart=t.w_size,t.insert=t.strstart):(t.window_size-t.strstart<=o&&(t.strstart-=t.w_size,t.window.set(t.window.subarray(t.w_size,t.w_size+t.strstart),0),t.matches<2&&t.matches++,t.insert>t.strstart&&(t.insert=t.strstart)),t.window.set(t.strm.input.subarray(t.strm.next_in-o,t.strm.next_in),t.strstart),t.strstart+=o,t.insert+=o>t.w_size-t.insert?t.w_size-t.insert:o),t.block_start=t.strstart),t.high_wateri&&t.block_start>=t.w_size&&(t.block_start-=t.w_size,t.strstart-=t.w_size,t.window.set(t.window.subarray(t.w_size,t.w_size+t.strstart),0),t.matches<2&&t.matches++,i+=t.w_size,t.insert>t.strstart&&(t.insert=t.strstart)),i>t.strm.avail_in&&(i=t.strm.avail_in),i&&(Rt(t.strm,t.window,t.strstart,i),t.strstart+=i,t.insert+=i>t.w_size-t.insert?t.w_size-t.insert:i),t.high_water>3,r=(i=t.pending_buf_size-i>65535?65535:t.pending_buf_size-i)>t.w_size?t.w_size:i,((n=t.strstart-t.block_start)>=r||(n||e===V)&&e!==q&&0===t.strm.avail_in&&n<=i)&&(a=n>i?i:n,s=e===V&&0===t.strm.avail_in&&a===n?1:0,Y(t,t.block_start,a,s),t.block_start+=a,xt(t.strm)),s?3:1)},Dt=function(t,e){for(var a,n;;){if(t.lookahead=3&&(t.ins_h=yt(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),0!==a&&t.strstart-a<=t.w_size-ct&&(t.match_length=Zt(t,a)),t.match_length>=3)if(n=X(t,t.strstart-t.match_start,t.match_length-3),t.lookahead-=t.match_length,t.match_length<=t.max_lazy_match&&t.lookahead>=3){t.match_length--;do{t.strstart++,t.ins_h=yt(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart}while(0!=--t.match_length);t.strstart++}else t.strstart+=t.match_length,t.match_length=0,t.ins_h=t.window[t.strstart],t.ins_h=yt(t,t.ins_h,t.window[t.strstart+1]);else n=X(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++;if(n&&(zt(t,!1),0===t.strm.avail_out))return 1}return t.insert=t.strstart<2?t.strstart:2,e===V?(zt(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(zt(t,!1),0===t.strm.avail_out)?1:2},Tt=function(t,e){for(var a,n,i;;){if(t.lookahead=3&&(t.ins_h=yt(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart),t.prev_length=t.match_length,t.prev_match=t.match_start,t.match_length=2,0!==a&&t.prev_length4096)&&(t.match_length=2)),t.prev_length>=3&&t.match_length<=t.prev_length){i=t.strstart+t.lookahead-3,n=X(t,t.strstart-1-t.prev_match,t.prev_length-3),t.lookahead-=t.prev_length-1,t.prev_length-=2;do{++t.strstart<=i&&(t.ins_h=yt(t,t.ins_h,t.window[t.strstart+3-1]),a=t.prev[t.strstart&t.w_mask]=t.head[t.ins_h],t.head[t.ins_h]=t.strstart)}while(0!=--t.prev_length);if(t.match_available=0,t.match_length=2,t.strstart++,n&&(zt(t,!1),0===t.strm.avail_out))return 1}else if(t.match_available){if((n=X(t,0,t.window[t.strstart-1]))&&zt(t,!1),t.strstart++,t.lookahead--,0===t.strm.avail_out)return 1}else t.match_available=1,t.strstart++,t.lookahead--}return t.match_available&&(n=X(t,0,t.window[t.strstart-1]),t.match_available=0),t.insert=t.strstart<2?t.strstart:2,e===V?(zt(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(zt(t,!1),0===t.strm.avail_out)?1:2};function Ot(t,e,a,n,i){this.good_length=t,this.max_lazy=e,this.nice_length=a,this.max_chain=n,this.func=i}var It=[new Ot(0,0,0,0,Ut),new Ot(4,4,8,4,Dt),new Ot(4,5,16,8,Dt),new Ot(4,6,32,32,Dt),new Ot(4,4,16,16,Tt),new Ot(8,16,32,32,Tt),new Ot(8,16,128,128,Tt),new Ot(8,32,128,256,Tt),new Ot(32,128,258,1024,Tt),new Ot(32,258,258,4096,Tt)];function Ft(){this.strm=null,this.status=0,this.pending_buf=null,this.pending_buf_size=0,this.pending_out=0,this.pending=0,this.wrap=0,this.gzhead=null,this.gzindex=0,this.method=ft,this.last_flush=-1,this.w_size=0,this.w_bits=0,this.w_mask=0,this.window=null,this.window_size=0,this.prev=null,this.head=null,this.ins_h=0,this.hash_size=0,this.hash_bits=0,this.hash_mask=0,this.hash_shift=0,this.block_start=0,this.match_length=0,this.prev_match=0,this.match_available=0,this.strstart=0,this.match_start=0,this.lookahead=0,this.prev_length=0,this.max_chain_length=0,this.max_lazy_match=0,this.level=0,this.strategy=0,this.good_match=0,this.nice_match=0,this.dyn_ltree=new Uint16Array(1146),this.dyn_dtree=new Uint16Array(122),this.bl_tree=new Uint16Array(78),vt(this.dyn_ltree),vt(this.dyn_dtree),vt(this.bl_tree),this.l_desc=null,this.d_desc=null,this.bl_desc=null,this.bl_count=new Uint16Array(16),this.heap=new Uint16Array(573),vt(this.heap),this.heap_len=0,this.heap_max=0,this.depth=new Uint16Array(573),vt(this.depth),this.sym_buf=0,this.lit_bufsize=0,this.sym_next=0,this.sym_end=0,this.opt_len=0,this.static_len=0,this.matches=0,this.insert=0,this.bi_buf=0,this.bi_valid=0}var Lt=function(t){if(!t)return 1;var e=t.state;return!e||e.strm!==t||e.status!==wt&&57!==e.status&&69!==e.status&&73!==e.status&&91!==e.status&&103!==e.status&&e.status!==mt&&e.status!==bt?1:0},Nt=function(t){if(Lt(t))return gt(t,at);t.total_in=t.total_out=0,t.data_type=_t;var e=t.state;return e.pending=0,e.pending_out=0,e.wrap<0&&(e.wrap=-e.wrap),e.status=2===e.wrap?57:e.wrap?wt:mt,t.adler=2===e.wrap?0:1,e.last_flush=-2,P(e),tt},Bt=function(t){var e,a=Nt(t);return a===tt&&((e=t.state).window_size=2*e.w_size,vt(e.head),e.max_lazy_match=It[e.level].max_lazy,e.good_match=It[e.level].good_length,e.nice_match=It[e.level].nice_length,e.max_chain_length=It[e.level].max_chain,e.strstart=0,e.block_start=0,e.lookahead=0,e.insert=0,e.match_length=e.prev_length=2,e.match_available=0,e.ins_h=0),a},Ct=function(t,e,a,n,i,r){if(!t)return at;var s=1;if(e===rt&&(e=6),n<0?(s=0,n=-n):n>15&&(s=2,n-=16),i<1||i>9||a!==ft||n<8||n>15||e<0||e>9||r<0||r>ht||8===n&&1!==s)return gt(t,at);8===n&&(n=9);var o=new Ft;return t.state=o,o.strm=t,o.status=wt,o.wrap=s,o.gzhead=null,o.w_bits=n,o.w_size=1<$||e<0)return t?gt(t,at):at;var a=t.state;if(!t.output||0!==t.avail_in&&!t.input||a.status===bt&&e!==V)return gt(t,0===t.avail_out?it:at);var n=a.last_flush;if(a.last_flush=e,0!==a.pending){if(xt(t),0===t.avail_out)return a.last_flush=-1,tt}else if(0===t.avail_in&&pt(e)<=pt(n)&&e!==V)return gt(t,it);if(a.status===bt&&0!==t.avail_in)return gt(t,it);if(a.status===wt&&0===a.wrap&&(a.status=mt),a.status===wt){var i=ft+(a.w_bits-8<<4)<<8;if(i|=(a.strategy>=ot||a.level<2?0:a.level<6?1:6===a.level?2:3)<<6,0!==a.strstart&&(i|=32),Et(a,i+=31-i%31),0!==a.strstart&&(Et(a,t.adler>>>16),Et(a,65535&t.adler)),t.adler=1,a.status=mt,xt(t),0!==a.pending)return a.last_flush=-1,tt}if(57===a.status)if(t.adler=0,At(a,31),At(a,139),At(a,8),a.gzhead)At(a,(a.gzhead.text?1:0)+(a.gzhead.hcrc?2:0)+(a.gzhead.extra?4:0)+(a.gzhead.name?8:0)+(a.gzhead.comment?16:0)),At(a,255&a.gzhead.time),At(a,a.gzhead.time>>8&255),At(a,a.gzhead.time>>16&255),At(a,a.gzhead.time>>24&255),At(a,9===a.level?2:a.strategy>=ot||a.level<2?4:0),At(a,255&a.gzhead.os),a.gzhead.extra&&a.gzhead.extra.length&&(At(a,255&a.gzhead.extra.length),At(a,a.gzhead.extra.length>>8&255)),a.gzhead.hcrc&&(t.adler=H(t.adler,a.pending_buf,a.pending,0)),a.gzindex=0,a.status=69;else if(At(a,0),At(a,0),At(a,0),At(a,0),At(a,0),At(a,9===a.level?2:a.strategy>=ot||a.level<2?4:0),At(a,3),a.status=mt,xt(t),0!==a.pending)return a.last_flush=-1,tt;if(69===a.status){if(a.gzhead.extra){for(var r=a.pending,s=(65535&a.gzhead.extra.length)-a.gzindex;a.pending+s>a.pending_buf_size;){var o=a.pending_buf_size-a.pending;if(a.pending_buf.set(a.gzhead.extra.subarray(a.gzindex,a.gzindex+o),a.pending),a.pending=a.pending_buf_size,a.gzhead.hcrc&&a.pending>r&&(t.adler=H(t.adler,a.pending_buf,a.pending-r,r)),a.gzindex+=o,xt(t),0!==a.pending)return a.last_flush=-1,tt;r=0,s-=o}var l=new Uint8Array(a.gzhead.extra);a.pending_buf.set(l.subarray(a.gzindex,a.gzindex+s),a.pending),a.pending+=s,a.gzhead.hcrc&&a.pending>r&&(t.adler=H(t.adler,a.pending_buf,a.pending-r,r)),a.gzindex=0}a.status=73}if(73===a.status){if(a.gzhead.name){var h,d=a.pending;do{if(a.pending===a.pending_buf_size){if(a.gzhead.hcrc&&a.pending>d&&(t.adler=H(t.adler,a.pending_buf,a.pending-d,d)),xt(t),0!==a.pending)return a.last_flush=-1,tt;d=0}h=a.gzindexd&&(t.adler=H(t.adler,a.pending_buf,a.pending-d,d)),a.gzindex=0}a.status=91}if(91===a.status){if(a.gzhead.comment){var _,f=a.pending;do{if(a.pending===a.pending_buf_size){if(a.gzhead.hcrc&&a.pending>f&&(t.adler=H(t.adler,a.pending_buf,a.pending-f,f)),xt(t),0!==a.pending)return a.last_flush=-1,tt;f=0}_=a.gzindexf&&(t.adler=H(t.adler,a.pending_buf,a.pending-f,f))}a.status=103}if(103===a.status){if(a.gzhead.hcrc){if(a.pending+2>a.pending_buf_size&&(xt(t),0!==a.pending))return a.last_flush=-1,tt;At(a,255&t.adler),At(a,t.adler>>8&255),t.adler=0}if(a.status=mt,xt(t),0!==a.pending)return a.last_flush=-1,tt}if(0!==t.avail_in||0!==a.lookahead||e!==q&&a.status!==bt){var u=0===a.level?Ut(a,e):a.strategy===ot?function(t,e){for(var a;;){if(0===t.lookahead&&(St(t),0===t.lookahead)){if(e===q)return 1;break}if(t.match_length=0,a=X(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++,a&&(zt(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,e===V?(zt(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(zt(t,!1),0===t.strm.avail_out)?1:2}(a,e):a.strategy===lt?function(t,e){for(var a,n,i,r,s=t.window;;){if(t.lookahead<=ut){if(St(t),t.lookahead<=ut&&e===q)return 1;if(0===t.lookahead)break}if(t.match_length=0,t.lookahead>=3&&t.strstart>0&&(n=s[i=t.strstart-1])===s[++i]&&n===s[++i]&&n===s[++i]){r=t.strstart+ut;do{}while(n===s[++i]&&n===s[++i]&&n===s[++i]&&n===s[++i]&&n===s[++i]&&n===s[++i]&&n===s[++i]&&n===s[++i]&&it.lookahead&&(t.match_length=t.lookahead)}if(t.match_length>=3?(a=X(t,1,t.match_length-3),t.lookahead-=t.match_length,t.strstart+=t.match_length,t.match_length=0):(a=X(t,0,t.window[t.strstart]),t.lookahead--,t.strstart++),a&&(zt(t,!1),0===t.strm.avail_out))return 1}return t.insert=0,e===V?(zt(t,!0),0===t.strm.avail_out?3:4):t.sym_next&&(zt(t,!1),0===t.strm.avail_out)?1:2}(a,e):It[a.level].func(a,e);if(3!==u&&4!==u||(a.status=bt),1===u||3===u)return 0===t.avail_out&&(a.last_flush=-1),tt;if(2===u&&(e===J?W(a):e!==$&&(Y(a,0,0,!1),e===Q&&(vt(a.head),0===a.lookahead&&(a.strstart=0,a.block_start=0,a.insert=0))),xt(t),0===t.avail_out))return a.last_flush=-1,tt}return e!==V?tt:a.wrap<=0?et:(2===a.wrap?(At(a,255&t.adler),At(a,t.adler>>8&255),At(a,t.adler>>16&255),At(a,t.adler>>24&255),At(a,255&t.total_in),At(a,t.total_in>>8&255),At(a,t.total_in>>16&255),At(a,t.total_in>>24&255)):(Et(a,t.adler>>>16),Et(a,65535&t.adler)),xt(t),a.wrap>0&&(a.wrap=-a.wrap),0!==a.pending?tt:et)},deflateEnd:function(t){if(Lt(t))return at;var e=t.state.status;return t.state=null,e===mt?gt(t,nt):tt},deflateSetDictionary:function(t,e){var a=e.length;if(Lt(t))return at;var n=t.state,i=n.wrap;if(2===i||1===i&&n.status!==wt||n.lookahead)return at;if(1===i&&(t.adler=C(t.adler,e,a,0)),n.wrap=0,a>=n.w_size){0===i&&(vt(n.head),n.strstart=0,n.block_start=0,n.insert=0);var r=new Uint8Array(n.w_size);r.set(e.subarray(a-n.w_size,a),0),e=r,a=n.w_size}var s=t.avail_in,o=t.next_in,l=t.input;for(t.avail_in=a,t.next_in=0,t.input=e,St(n);n.lookahead>=3;){var h=n.strstart,d=n.lookahead-2;do{n.ins_h=yt(n,n.ins_h,n.window[h+3-1]),n.prev[h&n.w_mask]=n.head[n.ins_h],n.head[n.ins_h]=h,h++}while(--d);n.strstart=h,n.lookahead=2,St(n)}return n.strstart+=n.lookahead,n.block_start=n.strstart,n.insert=n.lookahead,n.lookahead=0,n.match_length=n.prev_length=2,n.match_available=0,t.next_in=o,t.input=l,t.avail_in=s,n.wrap=i,tt},deflateInfo:\"pako deflate (from Nodeca project)\"};function Ht(t){return Ht=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&\"function\"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?\"symbol\":typeof t},Ht(t)}var jt=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},Kt=function(t){for(var e=Array.prototype.slice.call(arguments,1);e.length;){var a=e.shift();if(a){if(\"object\"!==Ht(a))throw new TypeError(a+\"must be non-object\");for(var n in a)jt(a,n)&&(t[n]=a[n])}}return t},Pt=function(t){for(var e=0,a=0,n=t.length;a=252?6:Xt>=248?5:Xt>=240?4:Xt>=224?3:Xt>=192?2:1;Gt[254]=Gt[254]=1;var Wt=function(t){if(\"function\"==typeof TextEncoder&&TextEncoder.prototype.encode)return(new TextEncoder).encode(t);var e,a,n,i,r,s=t.length,o=0;for(i=0;i>>6,e[r++]=128|63&a):a<65536?(e[r++]=224|a>>>12,e[r++]=128|a>>>6&63,e[r++]=128|63&a):(e[r++]=240|a>>>18,e[r++]=128|a>>>12&63,e[r++]=128|a>>>6&63,e[r++]=128|63&a);return e},qt=function(t,e){var a,n,i=e||t.length;if(\"function\"==typeof TextDecoder&&TextDecoder.prototype.decode)return(new TextDecoder).decode(t.subarray(0,e));var r=new Array(2*i);for(n=0,a=0;a4)r[n++]=65533,a+=o-1;else{for(s&=2===o?31:3===o?15:7;o>1&&a1?r[n++]=65533:s<65536?r[n++]=s:(s-=65536,r[n++]=55296|s>>10&1023,r[n++]=56320|1023&s)}}}return function(t,e){if(e<65534&&t.subarray&&Yt)return String.fromCharCode.apply(null,t.length===e?t:t.subarray(0,e));for(var a=\"\",n=0;nt.length&&(e=t.length);for(var a=e-1;a>=0&&128==(192&t[a]);)a--;return a<0||0===a?e:a+Gt[t[a]]>e?a:e};var Qt=function(){this.input=null,this.next_in=0,this.avail_in=0,this.total_in=0,this.output=null,this.next_out=0,this.avail_out=0,this.total_out=0,this.msg=\"\",this.state=null,this.data_type=2,this.adler=0},Vt=Object.prototype.toString,$t=K.Z_NO_FLUSH,te=K.Z_SYNC_FLUSH,ee=K.Z_FULL_FLUSH,ae=K.Z_FINISH,ne=K.Z_OK,ie=K.Z_STREAM_END,re=K.Z_DEFAULT_COMPRESSION,se=K.Z_DEFAULT_STRATEGY,oe=K.Z_DEFLATED;function le(t){this.options=Kt({level:re,method:oe,chunkSize:16384,windowBits:15,memLevel:8,strategy:se},t||{});var e=this.options;e.raw&&e.windowBits>0?e.windowBits=-e.windowBits:e.gzip&&e.windowBits>0&&e.windowBits<16&&(e.windowBits+=16),this.err=0,this.msg=\"\",this.ended=!1,this.chunks=[],this.strm=new Qt,this.strm.avail_out=0;var a=Mt.deflateInit2(this.strm,e.level,e.method,e.windowBits,e.memLevel,e.strategy);if(a!==ne)throw new Error(j[a]);if(e.header&&Mt.deflateSetHeader(this.strm,e.header),e.dictionary){var n;if(n=\"string\"==typeof e.dictionary?Wt(e.dictionary):\"[object ArrayBuffer]\"===Vt.call(e.dictionary)?new Uint8Array(e.dictionary):e.dictionary,(a=Mt.deflateSetDictionary(this.strm,n))!==ne)throw new Error(j[a]);this._dict_set=!0}}function he(t,e){var a=new le(e);if(a.push(t,!0),a.err)throw a.msg||j[a.err];return a.result}le.prototype.push=function(t,e){var a,n,i=this.strm,r=this.options.chunkSize;if(this.ended)return!1;for(n=e===~~e?e:!0===e?ae:$t,\"string\"==typeof t?i.input=Wt(t):\"[object ArrayBuffer]\"===Vt.call(t)?i.input=new Uint8Array(t):i.input=t,i.next_in=0,i.avail_in=i.input.length;;)if(0===i.avail_out&&(i.output=new Uint8Array(r),i.next_out=0,i.avail_out=r),(n===te||n===ee)&&i.avail_out<=6)this.onData(i.output.subarray(0,i.next_out)),i.avail_out=0;else{if((a=Mt.deflate(i,n))===ie)return i.next_out>0&&this.onData(i.output.subarray(0,i.next_out)),a=Mt.deflateEnd(this.strm),this.onEnd(a),this.ended=!0,a===ne;if(0!==i.avail_out){if(n>0&&i.next_out>0)this.onData(i.output.subarray(0,i.next_out)),i.avail_out=0;else if(0===i.avail_in)break}else this.onData(i.output)}return!0},le.prototype.onData=function(t){this.chunks.push(t)},le.prototype.onEnd=function(t){t===ne&&(this.result=Pt(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg};var de={Deflate:le,deflate:he,deflateRaw:function(t,e){return(e=e||{}).raw=!0,he(t,e)},gzip:function(t,e){return(e=e||{}).gzip=!0,he(t,e)},constants:K},_e=16209,fe=function(t,e){var a,n,i,r,s,o,l,h,d,_,f,u,c,w,m,b,g,p,v,k,y,x,z,A,E=t.state;a=t.next_in,z=t.input,n=a+(t.avail_in-5),i=t.next_out,A=t.output,r=i-(e-t.avail_out),s=i+(t.avail_out-257),o=E.dmax,l=E.wsize,h=E.whave,d=E.wnext,_=E.window,f=E.hold,u=E.bits,c=E.lencode,w=E.distcode,m=(1<>>=p=g>>>24,u-=p,0===(p=g>>>16&255))A[i++]=65535&g;else{if(!(16&p)){if(0==(64&p)){g=c[(65535&g)+(f&(1<>>=p,u-=p),u<15&&(f+=z[a++]<>>=p=g>>>24,u-=p,!(16&(p=g>>>16&255))){if(0==(64&p)){g=w[(65535&g)+(f&(1<o){t.msg=\"invalid distance too far back\",E.mode=_e;break t}if(f>>>=p,u-=p,k>(p=i-r)){if((p=k-p)>h&&E.sane){t.msg=\"invalid distance too far back\",E.mode=_e;break t}if(y=0,x=_,0===d){if(y+=l-p,p2;)A[i++]=x[y++],A[i++]=x[y++],A[i++]=x[y++],v-=3;v&&(A[i++]=x[y++],v>1&&(A[i++]=x[y++]))}else{y=i-k;do{A[i++]=A[y++],A[i++]=A[y++],A[i++]=A[y++],v-=3}while(v>2);v&&(A[i++]=A[y++],v>1&&(A[i++]=A[y++]))}break}}break}}while(a>3,f&=(1<<(u-=v<<3))-1,t.next_in=a,t.next_out=i,t.avail_in=a=1&&0===S[k];k--);if(y>k&&(y=k),0===k)return i[r++]=20971520,i[r++]=20971520,o.bits=1,0;for(v=1;v0&&(0===t||1!==k))return-1;for(U[1]=0,g=1;g852||2===t&&E>592)return 1;for(;;){c=g-z,s[p]+1=u?(w=D[s[p]-u],m=Z[s[p]-u]):(w=96,m=0),l=1<>z)+(h-=l)]=c<<24|w<<16|m|0}while(0!==h);for(l=1<>=1;if(0!==l?(R&=l-1,R+=l):R=0,p++,0==--S[g]){if(g===k)break;g=e[a+s[p]]}if(g>y&&(R&_)!==d){for(0===z&&(z=y),f+=v,A=1<<(x=g-z);x+z852||2===t&&E>592)return 1;i[d=R&_]=y<<24|x<<16|f-r|0}}return 0!==R&&(i[f+R]=g-z<<24|64<<16|0),o.bits=y,0},pe=K.Z_FINISH,ve=K.Z_BLOCK,ke=K.Z_TREES,ye=K.Z_OK,xe=K.Z_STREAM_END,ze=K.Z_NEED_DICT,Ae=K.Z_STREAM_ERROR,Ee=K.Z_DATA_ERROR,Re=K.Z_MEM_ERROR,Ze=K.Z_BUF_ERROR,Se=K.Z_DEFLATED,Ue=16180,De=16190,Te=16191,Oe=16192,Ie=16194,Fe=16199,Le=16200,Ne=16206,Be=16209,Ce=function(t){return(t>>>24&255)+(t>>>8&65280)+((65280&t)<<8)+((255&t)<<24)};function Me(){this.strm=null,this.mode=0,this.last=!1,this.wrap=0,this.havedict=!1,this.flags=0,this.dmax=0,this.check=0,this.total=0,this.head=null,this.wbits=0,this.wsize=0,this.whave=0,this.wnext=0,this.window=null,this.hold=0,this.bits=0,this.length=0,this.offset=0,this.extra=0,this.lencode=null,this.distcode=null,this.lenbits=0,this.distbits=0,this.ncode=0,this.nlen=0,this.ndist=0,this.have=0,this.next=null,this.lens=new Uint16Array(320),this.work=new Uint16Array(288),this.lendyn=null,this.distdyn=null,this.sane=0,this.back=0,this.was=0}var He,je,Ke=function(t){if(!t)return 1;var e=t.state;return!e||e.strm!==t||e.mode16211?1:0},Pe=function(t){if(Ke(t))return Ae;var e=t.state;return t.total_in=t.total_out=e.total=0,t.msg=\"\",e.wrap&&(t.adler=1&e.wrap),e.mode=Ue,e.last=0,e.havedict=0,e.flags=-1,e.dmax=32768,e.head=null,e.hold=0,e.bits=0,e.lencode=e.lendyn=new Int32Array(852),e.distcode=e.distdyn=new Int32Array(592),e.sane=1,e.back=-1,ye},Ye=function(t){if(Ke(t))return Ae;var e=t.state;return e.wsize=0,e.whave=0,e.wnext=0,Pe(t)},Ge=function(t,e){var a;if(Ke(t))return Ae;var n=t.state;return e<0?(a=0,e=-e):(a=5+(e>>4),e<48&&(e&=15)),e&&(e<8||e>15)?Ae:(null!==n.window&&n.wbits!==e&&(n.window=null),n.wrap=a,n.wbits=e,Ye(t))},Xe=function(t,e){if(!t)return Ae;var a=new Me;t.state=a,a.strm=t,a.window=null,a.mode=Ue;var n=Ge(t,e);return n!==ye&&(t.state=null),n},We=!0,qe=function(t){if(We){He=new Int32Array(512),je=new Int32Array(32);for(var e=0;e<144;)t.lens[e++]=8;for(;e<256;)t.lens[e++]=9;for(;e<280;)t.lens[e++]=7;for(;e<288;)t.lens[e++]=8;for(ge(1,t.lens,0,288,He,0,t.work,{bits:9}),e=0;e<32;)t.lens[e++]=5;ge(2,t.lens,0,32,je,0,t.work,{bits:5}),We=!1}t.lencode=He,t.lenbits=9,t.distcode=je,t.distbits=5},Je=function(t,e,a,n){var i,r=t.state;return null===r.window&&(r.wsize=1<=r.wsize?(r.window.set(e.subarray(a-r.wsize,a),0),r.wnext=0,r.whave=r.wsize):((i=r.wsize-r.wnext)>n&&(i=n),r.window.set(e.subarray(a-n,a-n+i),r.wnext),(n-=i)?(r.window.set(e.subarray(a-n,a),0),r.wnext=n,r.whave=r.wsize):(r.wnext+=i,r.wnext===r.wsize&&(r.wnext=0),r.whave>>8&255,a.check=H(a.check,R,2,0),h=0,d=0,a.mode=16181;break}if(a.head&&(a.head.done=!1),!(1&a.wrap)||(((255&h)<<8)+(h>>8))%31){t.msg=\"incorrect header check\",a.mode=Be;break}if((15&h)!==Se){t.msg=\"unknown compression method\",a.mode=Be;break}if(d-=4,y=8+(15&(h>>>=4)),0===a.wbits&&(a.wbits=y),y>15||y>a.wbits){t.msg=\"invalid window size\",a.mode=Be;break}a.dmax=1<>8&1),512&a.flags&&4&a.wrap&&(R[0]=255&h,R[1]=h>>>8&255,a.check=H(a.check,R,2,0)),h=0,d=0,a.mode=16182;case 16182:for(;d<32;){if(0===o)break t;o--,h+=n[r++]<>>8&255,R[2]=h>>>16&255,R[3]=h>>>24&255,a.check=H(a.check,R,4,0)),h=0,d=0,a.mode=16183;case 16183:for(;d<16;){if(0===o)break t;o--,h+=n[r++]<>8),512&a.flags&&4&a.wrap&&(R[0]=255&h,R[1]=h>>>8&255,a.check=H(a.check,R,2,0)),h=0,d=0,a.mode=16184;case 16184:if(1024&a.flags){for(;d<16;){if(0===o)break t;o--,h+=n[r++]<>>8&255,a.check=H(a.check,R,2,0)),h=0,d=0}else a.head&&(a.head.extra=null);a.mode=16185;case 16185:if(1024&a.flags&&((u=a.length)>o&&(u=o),u&&(a.head&&(y=a.head.extra_len-a.length,a.head.extra||(a.head.extra=new Uint8Array(a.head.extra_len)),a.head.extra.set(n.subarray(r,r+u),y)),512&a.flags&&4&a.wrap&&(a.check=H(a.check,n,u,r)),o-=u,r+=u,a.length-=u),a.length))break t;a.length=0,a.mode=16186;case 16186:if(2048&a.flags){if(0===o)break t;u=0;do{y=n[r+u++],a.head&&y&&a.length<65536&&(a.head.name+=String.fromCharCode(y))}while(y&&u>9&1,a.head.done=!0),t.adler=a.check=0,a.mode=Te;break;case 16189:for(;d<32;){if(0===o)break t;o--,h+=n[r++]<>>=7&d,d-=7&d,a.mode=Ne;break}for(;d<3;){if(0===o)break t;o--,h+=n[r++]<>>=1)){case 0:a.mode=16193;break;case 1:if(qe(a),a.mode=Fe,e===ke){h>>>=2,d-=2;break t}break;case 2:a.mode=16196;break;case 3:t.msg=\"invalid block type\",a.mode=Be}h>>>=2,d-=2;break;case 16193:for(h>>>=7&d,d-=7&d;d<32;){if(0===o)break t;o--,h+=n[r++]<>>16^65535)){t.msg=\"invalid stored block lengths\",a.mode=Be;break}if(a.length=65535&h,h=0,d=0,a.mode=Ie,e===ke)break t;case Ie:a.mode=16195;case 16195:if(u=a.length){if(u>o&&(u=o),u>l&&(u=l),0===u)break t;i.set(n.subarray(r,r+u),s),o-=u,r+=u,l-=u,s+=u,a.length-=u;break}a.mode=Te;break;case 16196:for(;d<14;){if(0===o)break t;o--,h+=n[r++]<>>=5,d-=5,a.ndist=1+(31&h),h>>>=5,d-=5,a.ncode=4+(15&h),h>>>=4,d-=4,a.nlen>286||a.ndist>30){t.msg=\"too many length or distance symbols\",a.mode=Be;break}a.have=0,a.mode=16197;case 16197:for(;a.have>>=3,d-=3}for(;a.have<19;)a.lens[Z[a.have++]]=0;if(a.lencode=a.lendyn,a.lenbits=7,z={bits:a.lenbits},x=ge(0,a.lens,0,19,a.lencode,0,a.work,z),a.lenbits=z.bits,x){t.msg=\"invalid code lengths set\",a.mode=Be;break}a.have=0,a.mode=16198;case 16198:for(;a.have>>16&255,g=65535&E,!((m=E>>>24)<=d);){if(0===o)break t;o--,h+=n[r++]<>>=m,d-=m,a.lens[a.have++]=g;else{if(16===g){for(A=m+2;d>>=m,d-=m,0===a.have){t.msg=\"invalid bit length repeat\",a.mode=Be;break}y=a.lens[a.have-1],u=3+(3&h),h>>>=2,d-=2}else if(17===g){for(A=m+3;d>>=m)),h>>>=3,d-=3}else{for(A=m+7;d>>=m)),h>>>=7,d-=7}if(a.have+u>a.nlen+a.ndist){t.msg=\"invalid bit length repeat\",a.mode=Be;break}for(;u--;)a.lens[a.have++]=y}}if(a.mode===Be)break;if(0===a.lens[256]){t.msg=\"invalid code -- missing end-of-block\",a.mode=Be;break}if(a.lenbits=9,z={bits:a.lenbits},x=ge(1,a.lens,0,a.nlen,a.lencode,0,a.work,z),a.lenbits=z.bits,x){t.msg=\"invalid literal/lengths set\",a.mode=Be;break}if(a.distbits=6,a.distcode=a.distdyn,z={bits:a.distbits},x=ge(2,a.lens,a.nlen,a.ndist,a.distcode,0,a.work,z),a.distbits=z.bits,x){t.msg=\"invalid distances set\",a.mode=Be;break}if(a.mode=Fe,e===ke)break t;case Fe:a.mode=Le;case Le:if(o>=6&&l>=258){t.next_out=s,t.avail_out=l,t.next_in=r,t.avail_in=o,a.hold=h,a.bits=d,fe(t,f),s=t.next_out,i=t.output,l=t.avail_out,r=t.next_in,n=t.input,o=t.avail_in,h=a.hold,d=a.bits,a.mode===Te&&(a.back=-1);break}for(a.back=0;b=(E=a.lencode[h&(1<>>16&255,g=65535&E,!((m=E>>>24)<=d);){if(0===o)break t;o--,h+=n[r++]<>p)])>>>16&255,g=65535&E,!(p+(m=E>>>24)<=d);){if(0===o)break t;o--,h+=n[r++]<>>=p,d-=p,a.back+=p}if(h>>>=m,d-=m,a.back+=m,a.length=g,0===b){a.mode=16205;break}if(32&b){a.back=-1,a.mode=Te;break}if(64&b){t.msg=\"invalid literal/length code\",a.mode=Be;break}a.extra=15&b,a.mode=16201;case 16201:if(a.extra){for(A=a.extra;d>>=a.extra,d-=a.extra,a.back+=a.extra}a.was=a.length,a.mode=16202;case 16202:for(;b=(E=a.distcode[h&(1<>>16&255,g=65535&E,!((m=E>>>24)<=d);){if(0===o)break t;o--,h+=n[r++]<>p)])>>>16&255,g=65535&E,!(p+(m=E>>>24)<=d);){if(0===o)break t;o--,h+=n[r++]<>>=p,d-=p,a.back+=p}if(h>>>=m,d-=m,a.back+=m,64&b){t.msg=\"invalid distance code\",a.mode=Be;break}a.offset=g,a.extra=15&b,a.mode=16203;case 16203:if(a.extra){for(A=a.extra;d>>=a.extra,d-=a.extra,a.back+=a.extra}if(a.offset>a.dmax){t.msg=\"invalid distance too far back\",a.mode=Be;break}a.mode=16204;case 16204:if(0===l)break t;if(u=f-l,a.offset>u){if((u=a.offset-u)>a.whave&&a.sane){t.msg=\"invalid distance too far back\",a.mode=Be;break}u>a.wnext?(u-=a.wnext,c=a.wsize-u):c=a.wnext-u,u>a.length&&(u=a.length),w=a.window}else w=i,c=s-a.offset,u=a.length;u>l&&(u=l),l-=u,a.length-=u;do{i[s++]=w[c++]}while(--u);0===a.length&&(a.mode=Le);break;case 16205:if(0===l)break t;i[s++]=a.length,l--,a.mode=Le;break;case Ne:if(a.wrap){for(;d<32;){if(0===o)break t;o--,h|=n[r++]<=0&&e.windowBits<16&&(e.windowBits=-e.windowBits,0===e.windowBits&&(e.windowBits=-15)),!(e.windowBits>=0&&e.windowBits<16)||t&&t.windowBits||(e.windowBits+=32),e.windowBits>15&&e.windowBits<48&&0==(15&e.windowBits)&&(e.windowBits|=15),this.err=0,this.msg=\"\",this.ended=!1,this.chunks=[],this.strm=new Qt,this.strm.avail_out=0;var a=Qe.inflateInit2(this.strm,e.windowBits);if(a!==aa)throw new Error(j[a]);if(this.header=new Ve,Qe.inflateGetHeader(this.strm,this.header),e.dictionary&&(\"string\"==typeof e.dictionary?e.dictionary=Wt(e.dictionary):\"[object ArrayBuffer]\"===$e.call(e.dictionary)&&(e.dictionary=new Uint8Array(e.dictionary)),e.raw&&(a=Qe.inflateSetDictionary(this.strm,e.dictionary))!==aa))throw new Error(j[a])}function ha(t,e){var a=new la(e);if(a.push(t),a.err)throw a.msg||j[a.err];return a.result}la.prototype.push=function(t,e){var a,n,i,r=this.strm,s=this.options.chunkSize,o=this.options.dictionary;if(this.ended)return!1;for(n=e===~~e?e:!0===e?ea:ta,\"[object ArrayBuffer]\"===$e.call(t)?r.input=new Uint8Array(t):r.input=t,r.next_in=0,r.avail_in=r.input.length;;){for(0===r.avail_out&&(r.output=new Uint8Array(s),r.next_out=0,r.avail_out=s),(a=Qe.inflate(r,n))===ia&&o&&((a=Qe.inflateSetDictionary(r,o))===aa?a=Qe.inflate(r,n):a===sa&&(a=ia));r.avail_in>0&&a===na&&r.state.wrap>0&&0!==t[r.next_in];)Qe.inflateReset(r),a=Qe.inflate(r,n);switch(a){case ra:case sa:case ia:case oa:return this.onEnd(a),this.ended=!0,!1}if(i=r.avail_out,r.next_out&&(0===r.avail_out||a===na))if(\"string\"===this.options.to){var l=Jt(r.output,r.next_out),h=r.next_out-l,d=qt(r.output,l);r.next_out=h,r.avail_out=s-h,h&&r.output.set(r.output.subarray(l,l+h),0),this.onData(d)}else this.onData(r.output.length===r.next_out?r.output:r.output.subarray(0,r.next_out));if(a!==aa||0!==i){if(a===na)return a=Qe.inflateEnd(this.strm),this.onEnd(a),this.ended=!0,!0;if(0===r.avail_in)break}}return!0},la.prototype.onData=function(t){this.chunks.push(t)},la.prototype.onEnd=function(t){t===aa&&(\"string\"===this.options.to?this.result=this.chunks.join(\"\"):this.result=Pt(this.chunks)),this.chunks=[],this.err=t,this.msg=this.strm.msg};var da={Inflate:la,inflate:ha,inflateRaw:function(t,e){return(e=e||{}).raw=!0,ha(t,e)},ungzip:ha,constants:K},_a=de.Deflate,fa=de.deflate,ua=de.deflateRaw,ca=de.gzip,wa=da.Inflate,ma=da.inflate,ba=da.inflateRaw,ga=da.ungzip,pa=K,va={Deflate:_a,deflate:fa,deflateRaw:ua,gzip:ca,Inflate:wa,inflate:ma,inflateRaw:ba,ungzip:ga,constants:pa};t.Deflate=_a,t.Inflate=wa,t.constants=pa,t.default=va,t.deflate=fa,t.deflateRaw=ua,t.gzip=ca,t.inflate=ma,t.inflateRaw=ba,t.ungzip=ga,Object.defineProperty(t,\"__esModule\",{value:!0})}));\nvar rough=function(){\"use strict\";function t(t,e,s){if(t&&t.length){const[n,o]=e,a=Math.PI/180*s,h=Math.cos(a),r=Math.sin(a);for(const e of t){const[t,s]=e;e[0]=(t-n)*h-(s-o)*r+n,e[1]=(t-n)*r+(s-o)*h+o}}}function e(t,e){return t[0]===e[0]&&t[1]===e[1]}function s(s,n,o,a=1){const h=o,r=Math.max(n,.1),i=s[0]&&s[0][0]&&\"number\"==typeof s[0][0]?[s]:s,c=[0,0];if(h)for(const e of i)t(e,c,h);const l=function(t,s,n){const o=[];for(const s of t){const t=[...s];e(t[0],t[t.length-1])||t.push([t[0][0],t[0][1]]),t.length>2&&o.push(t)}const a=[];s=Math.max(s,.1);const h=[];for(const t of o)for(let e=0;et.ymine.ymin?1:t.xe.x?1:t.ymax===e.ymax?0:(t.ymax-e.ymax)/Math.abs(t.ymax-e.ymax))),!h.length)return a;let r=[],i=h[0].ymin,c=0;for(;r.length||h.length;){if(h.length){let t=-1;for(let e=0;ei);e++)t=e;h.splice(0,t+1).forEach((t=>{r.push({s:i,edge:t})}))}if(r=r.filter((t=>!(t.edge.ymax<=i))),r.sort(((t,e)=>t.edge.x===e.edge.x?0:(t.edge.x-e.edge.x)/Math.abs(t.edge.x-e.edge.x))),(1!==n||c%s==0)&&r.length>1)for(let t=0;t=r.length)break;const s=r[t].edge,n=r[e].edge;a.push([[Math.round(s.x),i],[Math.round(n.x),i]])}i+=n,r.forEach((t=>{t.edge.x=t.edge.x+n*t.edge.islope})),c++}return a}(i,r,a);if(h){for(const e of i)t(e,c,-h);!function(e,s,n){const o=[];e.forEach((t=>o.push(...t))),t(o,s,n)}(l,c,-h)}return l}function n(t,e){var n;const o=e.hachureAngle+90;let a=e.hachureGap;a<0&&(a=4*e.strokeWidth),a=Math.round(Math.max(a,.1));let h=1;return e.roughness>=1&&((null===(n=e.randomizer)||void 0===n?void 0:n.next())||Math.random())>.7&&(h=a),s(t,a,o,h||1)}class o{constructor(t){this.helper=t}fillPolygons(t,e){return this._fillPolygons(t,e)}_fillPolygons(t,e){const s=n(t,e);return{type:\"fillSketch\",ops:this.renderLines(s,e)}}renderLines(t,e){const s=[];for(const n of t)s.push(...this.helper.doubleLineOps(n[0][0],n[0][1],n[1][0],n[1][1],e));return s}}function a(t){const e=t[0],s=t[1];return Math.sqrt(Math.pow(e[0]-s[0],2)+Math.pow(e[1]-s[1],2))}class h extends o{fillPolygons(t,e){let s=e.hachureGap;s<0&&(s=4*e.strokeWidth),s=Math.max(s,.1);const o=n(t,Object.assign({},e,{hachureGap:s})),h=Math.PI/180*e.hachureAngle,r=[],i=.5*s*Math.cos(h),c=.5*s*Math.sin(h);for(const[t,e]of o)a([t,e])&&r.push([[t[0]-i,t[1]+c],[...e]],[[t[0]+i,t[1]-c],[...e]]);return{type:\"fillSketch\",ops:this.renderLines(r,e)}}}class r extends o{fillPolygons(t,e){const s=this._fillPolygons(t,e),n=Object.assign({},e,{hachureAngle:e.hachureAngle+90}),o=this._fillPolygons(t,n);return s.ops=s.ops.concat(o.ops),s}}class i{constructor(t){this.helper=t}fillPolygons(t,e){const s=n(t,e=Object.assign({},e,{hachureAngle:0}));return this.dotsOnLines(s,e)}dotsOnLines(t,e){const s=[];let n=e.hachureGap;n<0&&(n=4*e.strokeWidth),n=Math.max(n,.1);let o=e.fillWeight;o<0&&(o=e.strokeWidth/2);const h=n/4;for(const r of t){const t=a(r),i=t/n,c=Math.ceil(i)-1,l=t-c*n,u=(r[0][0]+r[1][0])/2-n/4,p=Math.min(r[0][1],r[1][1]);for(let t=0;t{const h=a(t),r=Math.floor(h/(s+n)),i=(h+n-r*(s+n))/2;let c=t[0],l=t[1];c[0]>l[0]&&(c=t[1],l=t[0]);const u=Math.atan((l[1]-c[1])/(l[0]-c[0]));for(let t=0;t{const o=a(t),h=Math.round(o/(2*e));let r=t[0],i=t[1];r[0]>i[0]&&(r=t[1],i=t[0]);const c=Math.atan((i[1]-r[1])/(i[0]-r[0]));for(let t=0;tn%2?t+s:t+e));a.push({key:\"C\",data:t}),e=t[4],s=t[5];break}case\"Q\":a.push({key:\"Q\",data:[...r]}),e=r[2],s=r[3];break;case\"q\":{const t=r.map(((t,n)=>n%2?t+s:t+e));a.push({key:\"Q\",data:t}),e=t[2],s=t[3];break}case\"A\":a.push({key:\"A\",data:[...r]}),e=r[5],s=r[6];break;case\"a\":e+=r[5],s+=r[6],a.push({key:\"A\",data:[r[0],r[1],r[2],r[3],r[4],e,s]});break;case\"H\":a.push({key:\"H\",data:[...r]}),e=r[0];break;case\"h\":e+=r[0],a.push({key:\"H\",data:[e]});break;case\"V\":a.push({key:\"V\",data:[...r]}),s=r[0];break;case\"v\":s+=r[0],a.push({key:\"V\",data:[s]});break;case\"S\":a.push({key:\"S\",data:[...r]}),e=r[2],s=r[3];break;case\"s\":{const t=r.map(((t,n)=>n%2?t+s:t+e));a.push({key:\"S\",data:t}),e=t[2],s=t[3];break}case\"T\":a.push({key:\"T\",data:[...r]}),e=r[0],s=r[1];break;case\"t\":e+=r[0],s+=r[1],a.push({key:\"T\",data:[e,s]});break;case\"Z\":case\"z\":a.push({key:\"Z\",data:[]}),e=n,s=o}return a}function m(t){const e=[];let s=\"\",n=0,o=0,a=0,h=0,r=0,i=0;for(const{key:c,data:l}of t){switch(c){case\"M\":e.push({key:\"M\",data:[...l]}),[n,o]=l,[a,h]=l;break;case\"C\":e.push({key:\"C\",data:[...l]}),n=l[4],o=l[5],r=l[2],i=l[3];break;case\"L\":e.push({key:\"L\",data:[...l]}),[n,o]=l;break;case\"H\":n=l[0],e.push({key:\"L\",data:[n,o]});break;case\"V\":o=l[0],e.push({key:\"L\",data:[n,o]});break;case\"S\":{let t=0,a=0;\"C\"===s||\"S\"===s?(t=n+(n-r),a=o+(o-i)):(t=n,a=o),e.push({key:\"C\",data:[t,a,...l]}),r=l[0],i=l[1],n=l[2],o=l[3];break}case\"T\":{const[t,a]=l;let h=0,c=0;\"Q\"===s||\"T\"===s?(h=n+(n-r),c=o+(o-i)):(h=n,c=o);const u=n+2*(h-n)/3,p=o+2*(c-o)/3,f=t+2*(h-t)/3,d=a+2*(c-a)/3;e.push({key:\"C\",data:[u,p,f,d,t,a]}),r=h,i=c,n=t,o=a;break}case\"Q\":{const[t,s,a,h]=l,c=n+2*(t-n)/3,u=o+2*(s-o)/3,p=a+2*(t-a)/3,f=h+2*(s-h)/3;e.push({key:\"C\",data:[c,u,p,f,a,h]}),r=t,i=s,n=a,o=h;break}case\"A\":{const t=Math.abs(l[0]),s=Math.abs(l[1]),a=l[2],h=l[3],r=l[4],i=l[5],c=l[6];if(0===t||0===s)e.push({key:\"C\",data:[n,o,i,c,i,c]}),n=i,o=c;else if(n!==i||o!==c){x(n,o,i,c,t,s,a,h,r).forEach((function(t){e.push({key:\"C\",data:t})})),n=i,o=c}break}case\"Z\":e.push({key:\"Z\",data:[]}),n=a,o=h}s=c}return e}function w(t,e,s){return[t*Math.cos(s)-e*Math.sin(s),t*Math.sin(s)+e*Math.cos(s)]}function x(t,e,s,n,o,a,h,r,i,c){const l=(u=h,Math.PI*u/180);var u;let p=[],f=0,d=0,g=0,M=0;if(c)[f,d,g,M]=c;else{[t,e]=w(t,e,-l),[s,n]=w(s,n,-l);const h=(t-s)/2,c=(e-n)/2;let u=h*h/(o*o)+c*c/(a*a);u>1&&(u=Math.sqrt(u),o*=u,a*=u);const p=o*o,k=a*a,b=p*k-p*c*c-k*h*h,y=p*c*c+k*h*h,m=(r===i?-1:1)*Math.sqrt(Math.abs(b/y));g=m*o*c/a+(t+s)/2,M=m*-a*h/o+(e+n)/2,f=Math.asin(parseFloat(((e-M)/a).toFixed(9))),d=Math.asin(parseFloat(((n-M)/a).toFixed(9))),td&&(f-=2*Math.PI),!i&&d>f&&(d-=2*Math.PI)}let k=d-f;if(Math.abs(k)>120*Math.PI/180){const t=d,e=s,r=n;d=i&&d>f?f+120*Math.PI/180*1:f+120*Math.PI/180*-1,p=x(s=g+o*Math.cos(d),n=M+a*Math.sin(d),e,r,o,a,h,0,i,[d,t,g,M])}k=d-f;const b=Math.cos(f),y=Math.sin(f),m=Math.cos(d),P=Math.sin(d),v=Math.tan(k/4),S=4/3*o*v,O=4/3*a*v,L=[t,e],T=[t+S*y,e-O*b],D=[s+S*P,n-O*m],A=[s,n];if(T[0]=2*L[0]-T[0],T[1]=2*L[1]-T[1],c)return[T,D,A].concat(p);{p=[T,D,A].concat(p);const t=[];for(let e=0;e2){const o=[];for(let e=0;e2*Math.PI&&(f=0,d=2*Math.PI);const g=2*Math.PI/i.curveStepCount,M=Math.min(g/2,(d-f)/2),k=V(M,c,l,u,p,f,d,1,i);if(!i.disableMultiStroke){const t=V(M,c,l,u,p,f,d,1.5,i);k.push(...t)}return h&&(r?k.push(...$(c,l,c+u*Math.cos(f),l+p*Math.sin(f),i),...$(c,l,c+u*Math.cos(d),l+p*Math.sin(d),i)):k.push({op:\"lineTo\",data:[c,l]},{op:\"lineTo\",data:[c+u*Math.cos(f),l+p*Math.sin(f)]})),{type:\"path\",ops:k}}function _(t,e){const s=m(y(b(t))),n=[];let o=[0,0],a=[0,0];for(const{key:t,data:h}of s)switch(t){case\"M\":a=[h[0],h[1]],o=[h[0],h[1]];break;case\"L\":n.push(...$(a[0],a[1],h[0],h[1],e)),a=[h[0],h[1]];break;case\"C\":{const[t,s,o,r,i,c]=h;n.push(...Z(t,s,o,r,i,c,a,e)),a=[i,c];break}case\"Z\":n.push(...$(a[0],a[1],o[0],o[1],e)),a=[o[0],o[1]]}return{type:\"path\",ops:n}}function I(t,e){const s=[];for(const n of t)if(n.length){const t=e.maxRandomnessOffset||0,o=n.length;if(o>2){s.push({op:\"move\",data:[n[0][0]+G(t,e),n[0][1]+G(t,e)]});for(let a=1;a500?.4:-.0016668*i+1.233334;let l=o.maxRandomnessOffset||0;l*l*100>r&&(l=i/10);const u=l/2,p=.2+.2*W(o);let f=o.bowing*o.maxRandomnessOffset*(n-e)/200,d=o.bowing*o.maxRandomnessOffset*(t-s)/200;f=G(f,o,c),d=G(d,o,c);const g=[],M=()=>G(u,o,c),k=()=>G(l,o,c),b=o.preserveVertices;return a&&(h?g.push({op:\"move\",data:[t+(b?0:M()),e+(b?0:M())]}):g.push({op:\"move\",data:[t+(b?0:G(l,o,c)),e+(b?0:G(l,o,c))]})),h?g.push({op:\"bcurveTo\",data:[f+t+(s-t)*p+M(),d+e+(n-e)*p+M(),f+t+2*(s-t)*p+M(),d+e+2*(n-e)*p+M(),s+(b?0:M()),n+(b?0:M())]}):g.push({op:\"bcurveTo\",data:[f+t+(s-t)*p+k(),d+e+(n-e)*p+k(),f+t+2*(s-t)*p+k(),d+e+2*(n-e)*p+k(),s+(b?0:k()),n+(b?0:k())]}),g}function j(t,e,s){if(!t.length)return[];const n=[];n.push([t[0][0]+G(e,s),t[0][1]+G(e,s)]),n.push([t[0][0]+G(e,s),t[0][1]+G(e,s)]);for(let o=1;o3){const a=[],h=1-s.curveTightness;o.push({op:\"move\",data:[t[1][0],t[1][1]]});for(let e=1;e+21&&o.push(s)}else o.push(s);o.push(t[e+3])}else{const n=.5,a=t[e+0],h=t[e+1],r=t[e+2],i=t[e+3],c=J(a,h,n),l=J(h,r,n),u=J(r,i,n),p=J(c,l,n),f=J(l,u,n),d=J(p,f,n);K([a,c,p,d],0,s,o),K([d,f,u,i],0,s,o)}var a,h;return o}function U(t,e){return X(t,0,t.length,e)}function X(t,e,s,n,o){const a=o||[],h=t[e],r=t[s-1];let i=0,c=1;for(let n=e+1;ni&&(i=e,c=n)}return Math.sqrt(i)>n?(X(t,e,c+1,n,a),X(t,c,s,n,a)):(a.length||a.push(h),a.push(r)),a}function Y(t,e=.15,s){const n=[],o=(t.length-1)/3;for(let s=0;s0?X(n,0,n.length,s):n}const tt=\"none\";class et{constructor(t){this.defaultOptions={maxRandomnessOffset:2,roughness:1,bowing:1,stroke:\"#000\",strokeWidth:1,curveTightness:0,curveFitting:.95,curveStepCount:9,fillStyle:\"hachure\",fillWeight:-1,hachureAngle:-41,hachureGap:-1,dashOffset:-1,dashGap:-1,zigzagOffset:-1,seed:0,disableMultiStroke:!1,disableMultiStrokeFill:!1,preserveVertices:!1,fillShapeRoughnessGain:.8},this.config=t||{},this.config.options&&(this.defaultOptions=this._o(this.config.options))}static newSeed(){return Math.floor(Math.random()*2**31)}_o(t){return t?Object.assign({},this.defaultOptions,t):this.defaultOptions}_d(t,e,s){return{shape:t,sets:e||[],options:s||this.defaultOptions}}line(t,e,s,n,o){const a=this._o(o);return this._d(\"line\",[v(t,e,s,n,a)],a)}rectangle(t,e,s,n,o){const a=this._o(o),h=[],r=O(t,e,s,n,a);if(a.fill){const o=[[t,e],[t+s,e],[t+s,e+n],[t,e+n]];\"solid\"===a.fillStyle?h.push(I([o],a)):h.push(C([o],a))}return a.stroke!==tt&&h.push(r),this._d(\"rectangle\",h,a)}ellipse(t,e,s,n,o){const a=this._o(o),h=[],r=T(s,n,a),i=D(t,e,a,r);if(a.fill)if(\"solid\"===a.fillStyle){const s=D(t,e,a,r).opset;s.type=\"fillPath\",h.push(s)}else h.push(C([i.estimatedPoints],a));return a.stroke!==tt&&h.push(i.opset),this._d(\"ellipse\",h,a)}circle(t,e,s,n){const o=this.ellipse(t,e,s,s,n);return o.shape=\"circle\",o}linearPath(t,e){const s=this._o(e);return this._d(\"linearPath\",[S(t,!1,s)],s)}arc(t,e,s,n,o,a,h=!1,r){const i=this._o(r),c=[],l=A(t,e,s,n,o,a,h,!0,i);if(h&&i.fill)if(\"solid\"===i.fillStyle){const h=Object.assign({},i);h.disableMultiStroke=!0;const r=A(t,e,s,n,o,a,!0,!1,h);r.type=\"fillPath\",c.push(r)}else c.push(function(t,e,s,n,o,a,h){const r=t,i=e;let c=Math.abs(s/2),l=Math.abs(n/2);c+=G(.01*c,h),l+=G(.01*l,h);let u=o,p=a;for(;u<0;)u+=2*Math.PI,p+=2*Math.PI;p-u>2*Math.PI&&(u=0,p=2*Math.PI);const f=(p-u)/h.curveStepCount,d=[];for(let t=u;t<=p;t+=f)d.push([r+c*Math.cos(t),i+l*Math.sin(t)]);return d.push([r+c*Math.cos(p),i+l*Math.sin(p)]),d.push([r,i]),C([d],h)}(t,e,s,n,o,a,i));return i.stroke!==tt&&c.push(l),this._d(\"arc\",c,i)}curve(t,e){const s=this._o(e),n=[],o=L(t,s);if(s.fill&&s.fill!==tt)if(\"solid\"===s.fillStyle){const e=L(t,Object.assign(Object.assign({},s),{disableMultiStroke:!0,roughness:s.roughness?s.roughness+s.fillShapeRoughnessGain:0}));n.push({type:\"fillPath\",ops:this._mergedShape(e.ops)})}else{const e=[],o=t;if(o.length){const t=\"number\"==typeof o[0][0]?[o]:o;for(const n of t)n.length<3?e.push(...n):3===n.length?e.push(...Y(H([n[0],n[0],n[1],n[2]]),10,(1+s.roughness)/2)):e.push(...Y(H(n),10,(1+s.roughness)/2))}e.length&&n.push(C([e],s))}return s.stroke!==tt&&n.push(o),this._d(\"curve\",n,s)}polygon(t,e){const s=this._o(e),n=[],o=S(t,!0,s);return s.fill&&(\"solid\"===s.fillStyle?n.push(I([t],s)):n.push(C([t],s))),s.stroke!==tt&&n.push(o),this._d(\"polygon\",n,s)}path(t,e){const s=this._o(e),n=[];if(!t)return this._d(\"path\",n,s);t=(t||\"\").replace(/\\n/g,\" \").replace(/(-\\s)/g,\"-\").replace(\"/(ss)/g\",\" \");const o=s.fill&&\"transparent\"!==s.fill&&s.fill!==tt,a=s.stroke!==tt,h=!!(s.simplification&&s.simplification<1),r=function(t,e,s){const n=m(y(b(t))),o=[];let a=[],h=[0,0],r=[];const i=()=>{r.length>=4&&a.push(...Y(r,e)),r=[]},c=()=>{i(),a.length&&(o.push(a),a=[])};for(const{key:t,data:e}of n)switch(t){case\"M\":c(),h=[e[0],e[1]],a.push(h);break;case\"L\":i(),a.push([e[0],e[1]]);break;case\"C\":if(!r.length){const t=a.length?a[a.length-1]:h;r.push([t[0],t[1]])}r.push([e[0],e[1]]),r.push([e[2],e[3]]),r.push([e[4],e[5]]);break;case\"Z\":i(),a.push([h[0],h[1]])}if(c(),!s)return o;const l=[];for(const t of o){const e=U(t,s);e.length&&l.push(e)}return l}(t,1,h?4-4*(s.simplification||1):(1+s.roughness)/2),i=_(t,s);if(o)if(\"solid\"===s.fillStyle)if(1===r.length){const e=_(t,Object.assign(Object.assign({},s),{disableMultiStroke:!0,roughness:s.roughness?s.roughness+s.fillShapeRoughnessGain:0}));n.push({type:\"fillPath\",ops:this._mergedShape(e.ops)})}else n.push(I(r,s));else n.push(C(r,s));return a&&(h?r.forEach((t=>{n.push(S(t,!1,s))})):n.push(i)),this._d(\"path\",n,s)}opsToPath(t,e){let s=\"\";for(const n of t.ops){const t=\"number\"==typeof e&&e>=0?n.data.map((t=>+t.toFixed(e))):n.data;switch(n.op){case\"move\":s+=`M${t[0]} ${t[1]} `;break;case\"bcurveTo\":s+=`C${t[0]} ${t[1]}, ${t[2]} ${t[3]}, ${t[4]} ${t[5]} `;break;case\"lineTo\":s+=`L${t[0]} ${t[1]} `}}return s.trim()}toPaths(t){const e=t.sets||[],s=t.options||this.defaultOptions,n=[];for(const t of e){let e=null;switch(t.type){case\"path\":e={d:this.opsToPath(t),stroke:s.stroke,strokeWidth:s.strokeWidth,fill:tt};break;case\"fillPath\":e={d:this.opsToPath(t),stroke:tt,strokeWidth:0,fill:s.fill||tt};break;case\"fillSketch\":e=this.fillSketch(t,s)}e&&n.push(e)}return n}fillSketch(t,e){let s=e.fillWeight;return s<0&&(s=e.strokeWidth/2),{d:this.opsToPath(t),stroke:e.fill||tt,strokeWidth:s,fill:tt}}_mergedShape(t){return t.filter(((t,e)=>0===e||\"move\"!==t.op))}}class st{constructor(t,e){this.canvas=t,this.ctx=this.canvas.getContext(\"2d\"),this.gen=new et(e)}draw(t){const e=t.sets||[],s=t.options||this.getDefaultOptions(),n=this.ctx,o=t.options.fixedDecimalPlaceDigits;for(const a of e)switch(a.type){case\"path\":n.save(),n.strokeStyle=\"none\"===s.stroke?\"transparent\":s.stroke,n.lineWidth=s.strokeWidth,s.strokeLineDash&&n.setLineDash(s.strokeLineDash),s.strokeLineDashOffset&&(n.lineDashOffset=s.strokeLineDashOffset),this._drawToContext(n,a,o),n.restore();break;case\"fillPath\":{n.save(),n.fillStyle=s.fill||\"\";const e=\"curve\"===t.shape||\"polygon\"===t.shape||\"path\"===t.shape?\"evenodd\":\"nonzero\";this._drawToContext(n,a,o,e),n.restore();break}case\"fillSketch\":this.fillSketch(n,a,s)}}fillSketch(t,e,s){let n=s.fillWeight;n<0&&(n=s.strokeWidth/2),t.save(),s.fillLineDash&&t.setLineDash(s.fillLineDash),s.fillLineDashOffset&&(t.lineDashOffset=s.fillLineDashOffset),t.strokeStyle=s.fill||\"\",t.lineWidth=n,this._drawToContext(t,e,s.fixedDecimalPlaceDigits),t.restore()}_drawToContext(t,e,s,n=\"nonzero\"){t.beginPath();for(const n of e.ops){const e=\"number\"==typeof s&&s>=0?n.data.map((t=>+t.toFixed(s))):n.data;switch(n.op){case\"move\":t.moveTo(e[0],e[1]);break;case\"bcurveTo\":t.bezierCurveTo(e[0],e[1],e[2],e[3],e[4],e[5]);break;case\"lineTo\":t.lineTo(e[0],e[1])}}\"fillPath\"===e.type?t.fill(n):t.stroke()}get generator(){return this.gen}getDefaultOptions(){return this.gen.defaultOptions}line(t,e,s,n,o){const a=this.gen.line(t,e,s,n,o);return this.draw(a),a}rectangle(t,e,s,n,o){const a=this.gen.rectangle(t,e,s,n,o);return this.draw(a),a}ellipse(t,e,s,n,o){const a=this.gen.ellipse(t,e,s,n,o);return this.draw(a),a}circle(t,e,s,n){const o=this.gen.circle(t,e,s,n);return this.draw(o),o}linearPath(t,e){const s=this.gen.linearPath(t,e);return this.draw(s),s}polygon(t,e){const s=this.gen.polygon(t,e);return this.draw(s),s}arc(t,e,s,n,o,a,h=!1,r){const i=this.gen.arc(t,e,s,n,o,a,h,r);return this.draw(i),i}curve(t,e){const s=this.gen.curve(t,e);return this.draw(s),s}path(t,e){const s=this.gen.path(t,e);return this.draw(s),s}}const nt=\"http://www.w3.org/2000/svg\";class ot{constructor(t,e){this.svg=t,this.gen=new et(e)}draw(t){const e=t.sets||[],s=t.options||this.getDefaultOptions(),n=this.svg.ownerDocument||window.document,o=n.createElementNS(nt,\"g\"),a=t.options.fixedDecimalPlaceDigits;for(const h of e){let e=null;switch(h.type){case\"path\":e=n.createElementNS(nt,\"path\"),e.setAttribute(\"d\",this.opsToPath(h,a)),e.setAttribute(\"stroke\",s.stroke),e.setAttribute(\"stroke-width\",s.strokeWidth+\"\"),e.setAttribute(\"fill\",\"none\"),s.strokeLineDash&&e.setAttribute(\"stroke-dasharray\",s.strokeLineDash.join(\" \").trim()),s.strokeLineDashOffset&&e.setAttribute(\"stroke-dashoffset\",`${s.strokeLineDashOffset}`);break;case\"fillPath\":e=n.createElementNS(nt,\"path\"),e.setAttribute(\"d\",this.opsToPath(h,a)),e.setAttribute(\"stroke\",\"none\"),e.setAttribute(\"stroke-width\",\"0\"),e.setAttribute(\"fill\",s.fill||\"\"),\"curve\"!==t.shape&&\"polygon\"!==t.shape||e.setAttribute(\"fill-rule\",\"evenodd\");break;case\"fillSketch\":e=this.fillSketch(n,h,s)}e&&o.appendChild(e)}return o}fillSketch(t,e,s){let n=s.fillWeight;n<0&&(n=s.strokeWidth/2);const o=t.createElementNS(nt,\"path\");return o.setAttribute(\"d\",this.opsToPath(e,s.fixedDecimalPlaceDigits)),o.setAttribute(\"stroke\",s.fill||\"\"),o.setAttribute(\"stroke-width\",n+\"\"),o.setAttribute(\"fill\",\"none\"),s.fillLineDash&&o.setAttribute(\"stroke-dasharray\",s.fillLineDash.join(\" \").trim()),s.fillLineDashOffset&&o.setAttribute(\"stroke-dashoffset\",`${s.fillLineDashOffset}`),o}get generator(){return this.gen}getDefaultOptions(){return this.gen.defaultOptions}opsToPath(t,e){return this.gen.opsToPath(t,e)}line(t,e,s,n,o){const a=this.gen.line(t,e,s,n,o);return this.draw(a)}rectangle(t,e,s,n,o){const a=this.gen.rectangle(t,e,s,n,o);return this.draw(a)}ellipse(t,e,s,n,o){const a=this.gen.ellipse(t,e,s,n,o);return this.draw(a)}circle(t,e,s,n){const o=this.gen.circle(t,e,s,n);return this.draw(o)}linearPath(t,e){const s=this.gen.linearPath(t,e);return this.draw(s)}polygon(t,e){const s=this.gen.polygon(t,e);return this.draw(s)}arc(t,e,s,n,o,a,h=!1,r){const i=this.gen.arc(t,e,s,n,o,a,h,r);return this.draw(i)}curve(t,e){const s=this.gen.curve(t,e);return this.draw(s)}path(t,e){const s=this.gen.path(t,e);return this.draw(s)}}return{canvas:(t,e)=>new st(t,e),svg:(t,e)=>new ot(t,e),generator:t=>new et(t),newSeed:()=>et.newSeed()}}();\nvar Base64={_keyStr:\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\",encode:function(a,b){var c=\"\",d=0;for(null!=b&&b||(a=Base64._utf8_encode(a));d>2;e=(e&3)<<4|b>>4;var k=(b&15)<<2|f>>6;var l=f&63;isNaN(b)?k=l=64:isNaN(f)&&(l=64);c=c+this._keyStr.charAt(g)+this._keyStr.charAt(e)+this._keyStr.charAt(k)+this._keyStr.charAt(l)}return c},decode:function(a,b){b=null!=b?b:!1;var c=\"\",d=0;for(a=a.replace(/[^A-Za-z0-9\\+\\/=]/g,\n\"\");d>4;f=(f&15)<<4|g>>2;var l=(g&3)<<6|k;c+=String.fromCharCode(e);64!=g&&(c+=String.fromCharCode(f));64!=k&&(c+=String.fromCharCode(l))}b||(c=Base64._utf8_decode(c));return c},_utf8_encode:function(a){a=a.replace(/\\r\\n/g,\"\\n\");for(var b=\"\",c=0;c=d){var e=\na.charCodeAt(c+1);56320<=e&&57343>=e&&(d=((d&1023)<<10)+(e&1023)+65536,c++)}128>d?b+=String.fromCharCode(d):(2048>d?b+=String.fromCharCode(d>>6|192):(65536>d?b+=String.fromCharCode(d>>12|224):(b+=String.fromCharCode(d>>18|240),b+=String.fromCharCode(d>>12&63|128)),b+=String.fromCharCode(d>>6&63|128)),b+=String.fromCharCode(d&63|128))}return b},_utf8_decode:function(a){var b=\"\",c=0;for(c1=c2=0;cd)b+=String.fromCharCode(d),c++;else if(191d)c2=a.charCodeAt(c+\n1),b+=String.fromCharCode((d&31)<<6|c2&63),c+=2;else if(240>d)c2=a.charCodeAt(c+1),c3=a.charCodeAt(c+2),b+=String.fromCharCode((d&15)<<12|(c2&63)<<6|c3&63),c+=3;else{c2=a.charCodeAt(c+1);c3=a.charCodeAt(c+2);var e=a.charCodeAt(c+3);b+=String.fromCodePoint((d&7)<<18|(c2&63)<<12|(c3&63)<<6|e&63);c+=4}}return b}};window.urlParams=window.urlParams||{};if(null==window.mxIsElectron||\"1\"==urlParams.embed)window.mxIsElectron=!1;window.isLocalStorage=window.isLocalStorage||!1;window.mxLoadSettings=window.mxLoadSettings||\"1\"!=urlParams.configure;window.isSvgBrowser=!0;window.isMermaidEnabled=\"function\"===typeof structuredClone;\nwindow.DRAWIO_BASE_URL=window.DRAWIO_BASE_URL||(/.*\\.draw\\.io$/.test(window.location.hostname)||/.*\\.diagrams\\.net$/.test(window.location.hostname)?window.location.protocol+\"//\"+window.location.hostname:\"https://app.diagrams.net\");window.DRAWIO_SERVER_URL=window.DRAWIO_SERVER_URL||window.location.origin+window.location.pathname.substring(0,window.location.pathname.lastIndexOf(\"/\"))+\"/\";window.DRAWIO_LIGHTBOX_URL=window.DRAWIO_LIGHTBOX_URL||\"https://viewer.diagrams.net\";\nwindow.EXPORT_URL=window.EXPORT_URL||\"https://convert.diagrams.net/node/export\";window.PLANT_URL=window.PLANT_URL||\"https://plant-aws.diagrams.net\";window.DRAW_MATH_URL=window.DRAW_MATH_URL||\"math4/es5\";window.VSS_CONVERT_URL=window.VSS_CONVERT_URL||\"https://convert.diagrams.net/VsdConverter/api/converter\";window.REALTIME_URL=window.REALTIME_URL||window.DRAWIO_SERVER_URL+\"cache\";window.DRAWIO_GITLAB_URL=window.DRAWIO_GITLAB_URL||\"https://gitlab.com\";\nwindow.DRAWIO_GITLAB_ID=window.DRAWIO_GITLAB_ID||\"2b14debc5feeb18ba65358d863ec870e4cc9294b28c3c941cb3014eb4af9a9b4\";window.DRAWIO_GITHUB_URL=window.DRAWIO_GITHUB_URL||\"https://github.com\";window.DRAWIO_GITHUB_API_URL=window.DRAWIO_GITHUB_API_URL||\"https://api.github.com\";window.DRAWIO_GITHUB_ID=window.DRAWIO_GITHUB_ID||\"Iv1.98d62f0431e40543\";window.DRAWIO_DROPBOX_ID=window.DRAWIO_DROPBOX_ID||\"jg02tc0onwmhlgm\";window.SAVE_URL=window.SAVE_URL||window.DRAWIO_SERVER_URL+\"save\";\nwindow.OPEN_URL=window.OPEN_URL||window.DRAWIO_SERVER_URL+\"import\";window.PROXY_URL=window.PROXY_URL||window.DRAWIO_SERVER_URL+\"proxy\";window.DRAWIO_VIEWER_URL=window.DRAWIO_VIEWER_URL||null;window.NOTIFICATIONS_URL=window.NOTIFICATIONS_URL||(/.*\\.draw\\.io$/.test(window.location.hostname)||/.*\\.diagrams\\.net$/.test(window.location.hostname)?window.DRAWIO_SERVER_URL+\"notifications\":null);\nwindow.RT_WEBSOCKET_URL=window.RT_WEBSOCKET_URL||\"wss://\"+(\"test.draw.io\"==window.location.hostname?\"app.diagrams.net\":window.location.hostname)+\"/rt\";window.SHAPES_PATH=window.SHAPES_PATH||\"shapes\";window.GRAPH_IMAGE_PATH=window.GRAPH_IMAGE_PATH||\"img\";window.ICONSEARCH_PATH=window.ICONSEARCH_PATH||(urlParams.dev&&\"file:\"!=window.location.protocol?\"iconSearch2\":window.DRAWIO_SERVER_URL+\"iconSearch2\");window.TEMPLATE_PATH=window.TEMPLATE_PATH||\"templates\";\nwindow.NEW_DIAGRAM_CATS_PATH=window.NEW_DIAGRAM_CATS_PATH||\"newDiagramCats\";window.PLUGINS_BASE_PATH=window.PLUGINS_BASE_PATH||\"\";window.ALLOW_CUSTOM_PLUGINS=window.ALLOW_CUSTOM_PLUGINS||!1;window.RESOURCES_PATH=window.RESOURCES_PATH||\"resources\";window.RESOURCE_BASE=window.RESOURCE_BASE||RESOURCES_PATH+\"/dia\";window.DRAWIO_CONFIG=window.DRAWIO_CONFIG||null;window.mxLoadResources=window.mxLoadResources||!1;\nwindow.mxLanguage=window.mxLanguage||function(){var a=urlParams.lang;if(null==a&&\"undefined\"!=typeof JSON&&isLocalStorage)try{var b=localStorage.getItem(\".drawio-config\");null!=b&&(a=JSON.parse(b).language||null);if(!a&&window.mxIsElectron&&(a=urlParams.appLang,null!=a)){var c=a.indexOf(\"-\");0<=c&&(a=a.substring(0,c));a=a.toLowerCase()}}catch(d){isLocalStorage=!1}return a}();\nwindow.mxLanguageMap=window.mxLanguageMap||{i18n:\"\",id:\"Bahasa Indonesia\",ms:\"Bahasa Melayu\",bs:\"Bosanski\",bg:\"Bulgarian\",ca:\"Català\",cs:\"Čeština\",da:\"Dansk\",de:\"Deutsch\",et:\"Eesti\",en:\"English\",es:\"Español\",eu:\"Euskara\",fil:\"Filipino\",fr:\"Français\",gl:\"Galego\",it:\"Italiano\",hu:\"Magyar\",kl:\"Kalaallisut\",lt:\"Lietuvių\",lv:\"Latviešu\",nl:\"Nederlands\",no:\"Norsk\",pl:\"Polski\",\"pt-br\":\"Português (Brasil)\",pt:\"Português (Portugal)\",ro:\"Română\",fi:\"Suomi\",sv:\"Svenska\",vi:\"Tiếng Việt\",tr:\"Türkçe\",el:\"Ελληνικά\",\nru:\"Русский\",sr:\"Српски\",uk:\"Українська\",he:\"עברית\",ar:\"العربية\",fa:\"فارسی\",th:\"ไทย\",ta:\"தமிழ்\",ko:\"한국어\",ja:\"日本語\",zh:\"简体中文\",\"zh-tw\":\"繁體中文\"};\"undefined\"===typeof window.mxBasePath&&(window.mxBasePath=\"mxgraph\",window.mxImageBasePath=\"mxgraph/images\");\nif(null==window.mxLanguages){window.mxLanguages=[];for(var lang in mxLanguageMap)\"en\"!=lang&&window.mxLanguages.push(lang);if(null==window.mxLanguage&&(\"test.draw.io\"==window.location.hostname||\"www.draw.io\"==window.location.hostname||\"viewer.diagrams.net\"==window.location.hostname||\"embed.diagrams.net\"==window.location.hostname||\"app.diagrams.net\"==window.location.hostname||\"jgraph.github.io\"==window.location.hostname)&&(lang=navigator.language,null!=lang)){var dash=lang.indexOf(\"-\");0=window.location.hash.length)&&null!=urlParams.open&&(window.location.hash=urlParams.open);\"function\"!==typeof window.structuredClone&&(window.structuredClone=function(a){return a});window.urlParams=window.urlParams||{};window.DOM_PURIFY_CONFIG=window.DOM_PURIFY_CONFIG||{ADD_TAGS:[\"use\",\"foreignObject\"],FORBID_TAGS:[\"form\"],ALLOWED_URI_REGEXP:/^((?!javascript:).)*$/i,HTML_INTEGRATION_POINTS:{foreignobject:!0},ADD_ATTR:[\"target\",\"content\",\"pointer-events\",\"requiredFeatures\"]};window.MAX_REQUEST_SIZE=window.MAX_REQUEST_SIZE||10485760;window.MAX_AREA=window.MAX_AREA||225E6;window.EXPORT_URL=window.EXPORT_URL||\"/export\";window.SAVE_URL=window.SAVE_URL||\"/save\";\nwindow.OPEN_URL=window.OPEN_URL||\"/open\";window.RESOURCES_PATH=window.RESOURCES_PATH||\"resources\";window.RESOURCE_BASE=window.RESOURCE_BASE||window.RESOURCES_PATH+\"/grapheditor\";window.STENCIL_PATH=window.STENCIL_PATH||\"stencils\";window.IMAGE_PATH=window.IMAGE_PATH||\"images\";window.STYLE_PATH=window.STYLE_PATH||\"styles\";window.CSS_PATH=window.CSS_PATH||\"styles\";window.OPEN_FORM=window.OPEN_FORM||\"open.html\";window.mxBasePath=window.mxBasePath||\"mxgraph\";\nwindow.mxImageBasePath=window.mxImageBasePath||\"mxgraph/images\";window.mxLanguage=window.mxLanguage||urlParams.lang;window.mxLanguages=window.mxLanguages||[\"de\",\"se\"];var mxClient={VERSION:\"30.0.4\",IS_EDGE:null!=navigator.userAgent&&!!navigator.userAgent.match(/Edge\\//),IS_NS:null!=navigator.userAgent&&0<=navigator.userAgent.indexOf(\"Mozilla/\")&&0>navigator.userAgent.indexOf(\"MSIE\")&&0>navigator.userAgent.indexOf(\"Edge/\"),IS_OP:null!=navigator.userAgent&&(0<=navigator.userAgent.indexOf(\"Opera/\")||0<=navigator.userAgent.indexOf(\"OPR/\")),IS_OT:null!=navigator.userAgent&&0<=navigator.userAgent.indexOf(\"Presto/\")&&0>navigator.userAgent.indexOf(\"Presto/2.4.\")&&0>navigator.userAgent.indexOf(\"Presto/2.3.\")&&\n0>navigator.userAgent.indexOf(\"Presto/2.2.\")&&0>navigator.userAgent.indexOf(\"Presto/2.1.\")&&0>navigator.userAgent.indexOf(\"Presto/2.0.\")&&0>navigator.userAgent.indexOf(\"Presto/1.\"),IS_SF:/Apple Computer, Inc/.test(navigator.vendor),IS_ANDROID:0<=navigator.appVersion.indexOf(\"Android\"),IS_IOS:/iP(hone|od|ad)/.test(navigator.platform)||navigator.userAgent.match(/Mac/)&&navigator.maxTouchPoints&&2navigator.userAgent.indexOf(\"Firefox/1.\")&&0>navigator.userAgent.indexOf(\"Firefox/2.\")||0<=navigator.userAgent.indexOf(\"Iceweasel/\")&&0>navigator.userAgent.indexOf(\"Iceweasel/1.\")&&0>navigator.userAgent.indexOf(\"Iceweasel/2.\")||0<=navigator.userAgent.indexOf(\"SeaMonkey/\")&&\n0>navigator.userAgent.indexOf(\"SeaMonkey/1.\")||0<=navigator.userAgent.indexOf(\"Iceape/\")&&0>navigator.userAgent.indexOf(\"Iceape/1.\"),IS_SVG:\"MICROSOFT INTERNET EXPLORER\"!=navigator.appName.toUpperCase(),NO_FO:!document.createElementNS||\"[object SVGForeignObjectElement]\"!==document.createElementNS(\"http://www.w3.org/2000/svg\",\"foreignObject\").toString()||0<=navigator.userAgent.indexOf(\"Opera/\"),IS_WIN:0document.location.href.indexOf(\"http://\")&&0>document.location.href.indexOf(\"https://\"),defaultBundles:[],isBrowserSupported:function(){return mxClient.IS_SVG},link:function(a,b,c,d){c=c||document;var e=c.createElement(\"link\");e.setAttribute(\"rel\",a);e.setAttribute(\"href\",b);e.setAttribute(\"charset\",\"UTF-8\");e.setAttribute(\"type\",\n\"text/css\");d&&e.setAttribute(\"id\",d);c.getElementsByTagName(\"head\")[0].appendChild(e)},loadResources:function(a,b){function c(){0==--d&&a()}for(var d=mxClient.defaultBundles.length,e=0;e\\x3c/script>')}};\"undefined\"==typeof mxLoadResources&&(mxLoadResources=!0);\"undefined\"==typeof mxForceIncludes&&(mxForceIncludes=!1);\n\"undefined\"==typeof mxResourceExtension&&(mxResourceExtension=\".txt\");\"undefined\"==typeof mxLoadStylesheets&&(mxLoadStylesheets=!0);\"undefined\"!=typeof mxBasePath&&0=this.x&&a<=this.x+this.width&&b>=this.y&&b<=this.y+this.height};mxRectangle.prototype.grow=function(a){this.x-=a;this.y-=a;this.width+=2*a;this.height+=2*a;return this};mxRectangle.prototype.getPoint=function(){return new mxPoint(this.x,this.y)};mxRectangle.prototype.rotate90=function(){var a=(this.width-this.height)/2;this.x+=a;this.y-=a;a=this.width;this.width=this.height;this.height=a};\nmxRectangle.prototype.equals=function(a){return null!=a&&a.x==this.x&&a.y==this.y&&a.width==this.width&&a.height==this.height};mxRectangle.fromPoint=function(a){return new mxRectangle(a.x,a.y,0,0)};mxRectangle.fromRectangle=function(a){return new mxRectangle(a.x,a.y,a.width,a.height)};\nvar mxEffects={animateChanges:function(a,b,c){var d=0,e=function(){for(var g=!1,k=0;kd&&g?(d++,window.setTimeout(e,f)):null!=c&&c()},f=30;e()},cascadeOpacity:function(a,b,c){for(var d=a.model.getChildCount(b),e=0;emxUtils.indexOf(a,b[c])&&a.push(b[c]));return a},isNode:function(a,b,c,d){return null==a||a.constructor!==Element||null!=b&&a.nodeName.toLowerCase()!=b.toLowerCase()?!1:null==c||a.getAttribute(c)==d},isAncestorNode:function(a,b){for(;null!=b;){if(b==a)return!0;b=b.parentNode}return!1},visitNodes:function(a,b){if(a.nodeType==mxConstants.NODETYPE_ELEMENT)for(b(a),a=a.firstChild;null!=a;)mxUtils.visitNodes(a,b),a=a.nextSibling},getChildNodes:function(a,b){b=b||mxConstants.NODETYPE_ELEMENT;\nvar c=[];for(a=a.firstChild;null!=a;)a.nodeType==b&&c.push(a),a=a.nextSibling;return c},removeChildNodes:function(a){for(;null!=a.lastChild;)a.removeChild(a.lastChild)},importNode:function(a,b,c){return a.importNode(b,c)},importNodeImplementation:function(a,b,c){switch(b.nodeType){case 1:var d=a.createElement(b.nodeName);if(b.attributes&&0/g,\">\");if(null==c||c)a=a.replace(/\"/g,\""\"),a=a.replace(/'/g,\"'\");if(null==b||b)a=a.replace(/\\n/g,\" \");if(null==d||d)a=a.replace(/\\t/g,\" \");return a},decodeHtml:function(a){var b=document.createElement(\"textarea\");b.innerHTML=a;return b.value},getXml:function(a,b){a=(new XMLSerializer).serializeToString(a);return a.replace(/\\n/g,b||\" \")},getPrettyXml:function(a,b,c,d,e){var f=[];if(null!=a)if(b=null!=b?b:\" \",c=null!=c?c:\"\",d=null!=d?d:\"\\n\",null!=a.namespaceURI&&\na.namespaceURI!=e&&(e=a.namespaceURI,null==a.getAttribute(\"xmlns\")&&a.setAttribute(\"xmlns\",a.namespaceURI)),a.nodeType==mxConstants.NODETYPE_DOCUMENT)f.push(mxUtils.getPrettyXml(a.documentElement,b,c,d,e));else if(a.nodeType==mxConstants.NODETYPE_DOCUMENT_FRAGMENT){var g=a.firstChild;if(null!=g)for(;null!=g;)f.push(mxUtils.getPrettyXml(g,b,c,d,e)),g=g.nextSibling}else if(a.nodeType==mxConstants.NODETYPE_COMMENT)a=mxUtils.getTextContent(a),0\"+d);null!=g;)f.push(mxUtils.getPrettyXml(g,b,c+b,d,e)),g=g.nextSibling;\nf.push(c+\"\"+d)}else f.push(\" />\"+d)}return f.join(\"\")},extractTextWithWhitespace:function(a){function b(e){if(1!=e.length||\"BR\"!=e[0].nodeName&&\"\\n\"!=e[0].innerHTML)for(var f=0;f\"==g.innerHTML.toLowerCase()?d.push(\"\\n\"):(3===g.nodeType||4===g.nodeType?0k&&(a.style.left=Math.max(g+b,k-e)+\"px\");\nd=parseInt(a.offsetTop);e=parseInt(a.offsetHeight);c=f+c.height-b;d+e>c&&(a.style.top=Math.max(f+b,c-e)+\"px\")},load:function(a){a=new mxXmlRequest(a,null,\"GET\",!1);a.send();return a},get:function(a,b,c,d,e,f,g){a=new mxXmlRequest(a,null,\"GET\");var k=a.setRequestHeaders;g&&(a.setRequestHeaders=function(l,m){k.apply(this,arguments);for(var n in g)l.setRequestHeader(n,g[n])});null!=d&&a.setBinary(d);a.send(b,c,e,f);return a},getAll:function(a,b,c){for(var d=a.length,e=[],f=0,g=function(){0==f&&null!=\nc&&c();f++},k=0;kp||299.213*a.r+.715*a.g+.072*a.b:!1},parseLightDarkColor:function(a,b,c){var d=a,e=null;null!=a&&(a=a.match(mxUtils.lightDarkColorRegex))&&3===a.length&&(d=mxUtils.trim(a[1]),e=mxUtils.trim(a[2]));null==e&&(c||mxUtils.isVarColor(d))&&(e=d);e=null!=e?e:null!=b?b:mxUtils.getInverseColor(d);\nreturn{light:d,dark:e}},getInverseColor:function(a,b,c){function d(n){return Math.round(b/100*(255-n)+n*(1-b/100))}function e(n){return Math.round(Math.max(0,Math.min(255,n)))}c=null!=c?c:180;b=null!=b?b:93;var f=0,g=0,k=0,l=1;if(\"transparent\"==a.toLowerCase())return a;l=mxUtils.parseColor(a);if(null==l)return a;f=l.r;g=l.g;k=l.b;l=l.a;f=d(f);g=d(g);k=d(k);a=[1,0,0,0,1,0,0,0,1];var m=Math.cos(c*Math.PI/180);c=Math.sin(c*Math.PI/180);a[0]=.2126+.7874*m-.2126*c;a[1]=.7152-.7152*m-.7152*c;a[2]=.0722-\n.0722*m+.9278*c;a[3]=.2126-.2126*m+.143*c;a[4]=.7152+(1-.7152)*m+.14*c;a[5]=.0722-.0722*m-.283*c;a[6]=.2126-.2126*m-.7874*c;a[7]=.7152-.7152*m+.7152*c;a[8]=.0722+.9278*m+.0722*c;c=e(a[0]*f+a[1]*g+a[2]*k);m=e(a[3]*f+a[4]*g+a[5]*k);f=e(a[6]*f+a[7]*g+a[8]*k);return\"#\"+(16777216|c<<16|m<<8|f).toString(16).slice(1)+(1>l?(\"0\"+Math.round(255*l).toString(16)).slice(-2):\"\")},addAlphaToColor:function(a,b,c){null!=b&&null!=a&&\"transparent\"!=a&&(a=mxUtils.parseColor(a),a.a=c?b:a.a*b,a=\"rgba(\"+a.r+\",\"+a.g+\",\"+\na.b+\",\"+a.a+\")\");return a},getLightDarkColor:function(a,b,c,d){var e={light:\"transparent\",dark:\"transparent\",cssText:\"transparent\"};null!=a&&a!=mxConstants.NONE&&(\"string\"===typeof a?(a=mxUtils.parseLightDarkColor(a,c,d),e.light=mxUtils.addAlphaToColor(a.light,b),e.dark=mxUtils.addAlphaToColor(a.dark,b),e.cssText=mxUtils.lightDarkColorSupported?e.light==e.dark?e.light:\"light-dark(\"+e.light+\", \"+e.dark+\")\":mxUtils.preferDarkColor?e.dark:e.light):(e.light=a,e.dark=a,e.cssText=a));return e},invertLightDarkColor:function(a){var b=\n{};b.light=a.dark;b.dark=a.light;b.cssText=b.light==b.dark?b.light:\"light-dark(\"+b.light+\", \"+b.dark+\")\";return b},getColor:function(a,b,c){a=null!=a?a[b]:null;null==a?a=c:a==mxConstants.NONE&&(a=null);return a},isEmptyObject:function(a){for(var b in a)return!1;return!0},clone:function(a,b,c){c=null!=c?c:!1;var d=null;if(null!=a&&\"function\"==typeof a.constructor)if(a.constructor===Element)d=a.cloneNode(null!=c?!c:!1);else{d=new a.constructor;for(var e in a)e!=mxObjectIdentity.FIELD_NAME&&(null==b||\n0>mxUtils.indexOf(b,e))&&(d[e]=c||\"object\"!=typeof a[e]?a[e]:mxUtils.clone(a[e]))}return d},equalPoints:function(a,b){if(null==a&&null!=b||null!=a&&null==b||null!=a&&null!=b&&a.length!=b.length)return!1;if(null!=a&&null!=b)for(var c=0;c [Function]\\n\";else if(\"object\"==typeof a[c]){var d=mxUtils.getFunctionName(a[c].constructor);b+=c+\" => [\"+d+\"]\\n\"}else b+=c+\" = \"+a[c]+\"\\n\"}catch(e){b+=c+\"=\"+e.message}return b},toRadians:function(a){return Math.PI*a/180},toDegree:function(a){return 180*a/Math.PI},arcToCurves:function(a,b,c,d,e,f,g,k,l){k-=a;l-=b;if(0===c||0===d)return A;c=Math.abs(c);d=Math.abs(d);var m=-k/2,n=-l/2,p=Math.cos(e*Math.PI/180);A=Math.sin(e*Math.PI/180);e=p*m+A*n;m=-1*A*m+p*\nn;n=e*e;var q=m*m,r=c*c,t=d*d,u=n/r+q/t;1e&&(e+=2*Math.PI);g=2*e/Math.PI;g=Math.ceil(0>g?-1*g:g);e/=g;m=8/3*Math.sin(e/4)*Math.sin(e/4)/Math.sin(e/2);n=p*c;p*=d;c*=A;d*=A;\nvar v=Math.cos(f),y=Math.sin(f);q=-m*(n*y+d*v);r=-m*(c*y-p*v);for(var A=[],z=0;zc&&(a=3,-135>=c&&(a=2));\nif(0<=d.indexOf(mxConstants.DIRECTION_NORTH))switch(a){case 0:b|=mxConstants.DIRECTION_MASK_NORTH;break;case 1:b|=mxConstants.DIRECTION_MASK_EAST;break;case 2:b|=mxConstants.DIRECTION_MASK_SOUTH;break;case 3:b|=mxConstants.DIRECTION_MASK_WEST}if(0<=d.indexOf(mxConstants.DIRECTION_WEST))switch(a){case 0:b|=mxConstants.DIRECTION_MASK_WEST;break;case 1:b|=mxConstants.DIRECTION_MASK_NORTH;break;case 2:b|=mxConstants.DIRECTION_MASK_EAST;break;case 3:b|=mxConstants.DIRECTION_MASK_SOUTH}if(0<=d.indexOf(mxConstants.DIRECTION_SOUTH))switch(a){case 0:b|=\nmxConstants.DIRECTION_MASK_SOUTH;break;case 1:b|=mxConstants.DIRECTION_MASK_WEST;break;case 2:b|=mxConstants.DIRECTION_MASK_NORTH;break;case 3:b|=mxConstants.DIRECTION_MASK_EAST}if(0<=d.indexOf(mxConstants.DIRECTION_EAST))switch(a){case 0:b|=mxConstants.DIRECTION_MASK_EAST;break;case 1:b|=mxConstants.DIRECTION_MASK_SOUTH;break;case 2:b|=mxConstants.DIRECTION_MASK_WEST;break;case 3:b|=mxConstants.DIRECTION_MASK_NORTH}return b},reversePortConstraints:function(a){var b=(a&mxConstants.DIRECTION_MASK_WEST)<<\n3;b|=(a&mxConstants.DIRECTION_MASK_NORTH)<<1;b|=(a&mxConstants.DIRECTION_MASK_SOUTH)>>1;return b|(a&mxConstants.DIRECTION_MASK_EAST)>>3},findNearestSegment:function(a,b,c){var d=-1;if(0f.distSq)&&(d=f)}}return null!=d?d.p:null},intersectsPoints:function(a,b){for(var c=0;cc.x&&(a=c.x,k=b.x);k>g&&(k=g);ak)return!1;e=b.y;g=c.y;var l=c.x-b.x;1E-7g&&(b=g,g=e,e=b);g>f&&(g=f);eg?!1:!0},contains:function(a,b,c){return a.x<=b&&a.x+a.width>=b&&a.y<=c&&a.y+a.height>=c},intersects:function(a,b,c){var d=a.width,e=a.height,f=b.width,g=b.height;if(!c&&(0>=f||0>=g||0>=d||0>=e))return!1;c=a.x;a=a.y;var k=b.x;b=b.y;f+=k;g+=b;d+=c;e+=a;return(fc)&&(ga)&&(dk)&&(eb)},intersectsHotspot:function(a,b,c,d,e,f){d=null!=d?d:1;e=null!=e?e:0;f=null!=f?f:0;if(0a.toLowerCase().indexOf(\"0x\"))},isInteger:function(a){return String(parseInt(a))===String(a)},mod:function(a,b){return(a%b+b)%b},intersection:function(a,b,c,d,e,f,g,k){var l=(k-f)*(c-a)-(g-e)*(d-b);g=((g-e)*(b-f)-(k-f)*(a-e))/l;e=((c-a)*(b-f)-(d-b)*(a-e))/l;return-1E-6<=g&&\n1.000001>=g&&-1E-6<=e&&1.000001>=e?new mxPoint(a+g*(c-a),b+g*(d-b)):null},ptSegDistSq:function(a,b,c,d,e,f){c-=a;d-=b;e-=a;f-=b;0>=e*c+f*d?c=0:(e=c-e,f=d-f,a=e*c+f*d,c=0>=a?0:a*a/(c*c+d*d));e=e*e+f*f-c;0>e&&(e=0);return e},ptLineDist:function(a,b,c,d,e,f){return Math.abs((d-b)*e-(c-a)*f+c*b-d*a)/Math.sqrt((d-b)*(d-b)+(c-a)*(c-a))},relativeCcw:function(a,b,c,d,e,f){c-=a;d-=b;e-=a;f-=b;a=e*d-f*c;0==a&&(a=e*c+f*d,0a&&(a=0)));return 0>a?-1:0a.indexOf(\"=\"))?a:\"\"},getStylenames:function(a){var b=[];if(null!=a){a=a.split(\";\");for(var c=0;ca[c].indexOf(\"=\")&&\nb.push(a[c])}return b},indexOfStylename:function(a,b){if(null!=a&&null!=b){a=a.split(\";\");for(var c=0,d=0;dmxUtils.indexOfStylename(a,b)&&(null==a?a=\"\":0b.a?(\"0\"+Math.round(255*b.a).toString(16)).slice(-2):\"\"):a},color2hex:function(a,b){b=null!=b?b:\"#000000\";try{null!=a&&(a=String(a).replace(/\\s/g,\"\"),a.toLowerCase()==\nmxUtils.normalizedValidInvalidColor?b=\"#000000\":(mxUtils.canvasContext.fillStyle=mxUtils.validInvalidColor,mxUtils.canvasContext.fillStyle=a,mxUtils.canvasContext.fillStyle!=mxUtils.validInvalidColor?(b=mxUtils.canvasContext.fillStyle,mxUtils.isRgbColor(b)&&(b=mxUtils.rgba2hex(b))):1e?\";\":a.substring(e)):0>e||e==a.length-1?\"\":a.substring(e+1)}else{var f=a.indexOf(\";\"+b+\"=\");0>f?d&&(d=\";\"==a.charAt(a.length-1)?\"\":\";\",a=a+d+b+\"=\"+c+\";\"):(e=a.indexOf(\";\",f+1),a=d?a.substring(0,f+1)+b+\"=\"+c+(0>e?\";\":a.substring(e)):a.substring(0,f)+(0>e?\";\":a.substring(e)))}return a},\nsetCellStyleFlags:function(a,b,c,d,e){if(null!=b&&0e)e=\";\"==a.charAt(a.length-1)?\"\":\";\",a=d||null==d?a+e+b+\"=\"+c:a+e+b+\"=0\";else{var f=a.indexOf(\";\",e),g=0>f?a.substring(e+b.length+1):a.substring(e+\nb.length+1,f);g=null==d?parseInt(g)^c:d?parseInt(g)|c:parseInt(g)&~c;a=a.substring(0,e)+b+\"=\"+g+(0<=f?a.substring(f):\"\")}}return a},getAlignmentAsPoint:function(a,b){var c=-.5,d=-.5;a==mxConstants.ALIGN_LEFT?c=0:a==mxConstants.ALIGN_RIGHT&&(c=-1);b==mxConstants.ALIGN_TOP?d=0:b==mxConstants.ALIGN_BOTTOM&&(d=-1);return new mxPoint(c,d)},_measureDiv:null,_measureCache:null,getSizeForString:function(a,b,c,d,e){b=null!=b?b:mxConstants.DEFAULT_FONTSIZE;c=null!=c?c:mxConstants.DEFAULT_FONTFAMILY;e=null!=\ne?e:0;if(null==mxUtils._measureDiv){var f=document.createElement(\"div\");f.style.position=\"absolute\";f.style.visibility=\"hidden\";f.style.display=\"inline-block\";f.style.zoom=\"1\";document.body.appendChild(f);mxUtils._measureDiv=f;mxUtils._measureCache={}}f=mxUtils._measureDiv;var g=mxUtils._measureCache,k=Math.round(b)+\"px\";b=mxConstants.ABSOLUTE_LINE_HEIGHT?b*mxConstants.LINE_HEIGHT+\"px\":mxConstants.LINE_HEIGHT*mxSvgCanvas2D.prototype.lineHeightCorrection;var l=(e&mxConstants.FONT_BOLD)==mxConstants.FONT_BOLD?\n\"bold\":\"\",m=(e&mxConstants.FONT_ITALIC)==mxConstants.FONT_ITALIC?\"italic\":\"\",n=\"\";(e&mxConstants.FONT_UNDERLINE)==mxConstants.FONT_UNDERLINE&&(n=\"underline\");(e&mxConstants.FONT_STRIKETHROUGH)==mxConstants.FONT_STRIKETHROUGH&&(n=0a)return 1;c=null!=c?c:mxConstants.PAGE_FORMAT_A4_PORTRAIT;d=null!=d?d:0;var e=c.width-2*d;c=c.height-2*d;d=mxRectangle.fromRectangle(b.getGraphBounds());b=b.getView().getScale();d.width/=b;d.height/=b;b=d.width;var f=Math.sqrt(a);d=Math.sqrt(b/d.height/(e/c));\nc=f*d;d=f/d;if(1>c&&d>a){var g=d/a;d=a;c/=g}1>d&&c>a&&(g=c/a,c=a,d/=g);g=Math.ceil(c)*Math.ceil(d);for(f=0;g>a;){g=Math.floor(c)/c;var k=Math.floor(d)/d;1==g&&(g=Math.floor(c-1)/c);1==k&&(k=Math.floor(d-1)/d);g=g>k?g:k;c*=g;d*=g;g=Math.ceil(c)*Math.ceil(d);f++;if(10\");d=document.getElementsByTagName(\"base\");for(c=0;c');b.close();c=b.createElement(\"div\");c.position=\n\"absolute\";c.overflow=\"hidden\";c.style.width=e+\"px\";c.style.height=f+\"px\";e=b.createElement(\"div\");e.style.position=\"absolute\";e.style.left=k+\"px\";e.style.top=l+\"px\";f=a.container.firstChild;for(d=null;null!=f;)g=f.cloneNode(!0),f==a.view.drawPane.ownerSVGElement?(c.appendChild(g),d=g):e.appendChild(g),f=f.nextSibling;b.body.appendChild(c);null!=e.firstChild&&b.body.appendChild(e);null!=d&&(d.style.minWidth=\"\",d.style.minHeight=\"\",d.firstChild.setAttribute(\"transform\",\"translate(\"+k+\",\"+l+\")\"));mxUtils.removeCursors(b.body);\nreturn b},printScreen:function(a){var b=window.open();mxUtils.show(a,b.document);a=function(){b.focus();b.print();b.close()};mxClient.IS_GC?b.setTimeout(a,500):a()},popup:function(a,b){if(b){var c=document.createElement(\"div\");c.style.overflow=\"scroll\";c.style.width=\"636px\";c.style.height=\"460px\";b=document.createElement(\"pre\");b.innerHTML=mxUtils.htmlEntities(a,!1).replace(/\\n/g,\"
\").replace(/ /g,\" \");c.appendChild(b);c=new mxWindow(\"Popup Window\",c,document.body.clientWidth/2-320,Math.max(document.body.clientHeight||\n0,document.documentElement.clientHeight)/2-240,640,480,!1,!0);c.setClosable(!0);c.setVisible(!0)}else mxClient.IS_NS?(c=window.open(),c.document.writeln(\"
\"+mxUtils.htmlEntities(a)+\"\").replace(/ /g,\" \"),c.document.body.appendChild(b))},alert:function(a){alert(a)},prompt:function(a,b){return prompt(a,null!=b?b:\"\")},confirm:function(a){return confirm(a)},\nerror:function(a,b,c,d){var e=document.createElement(\"div\");e.style.padding=\"20px\";var f=document.createElement(\"img\");f.setAttribute(\"src\",d||mxUtils.errorImage);f.setAttribute(\"valign\",\"bottom\");f.style.verticalAlign=\"middle\";e.appendChild(f);e.appendChild(document.createTextNode(\" \"));e.appendChild(document.createTextNode(\" \"));e.appendChild(document.createTextNode(\" \"));mxUtils.write(e,a);a=document.body.clientWidth;d=document.body.clientHeight||document.documentElement.clientHeight;var g=new mxWindow(mxResources.get(mxUtils.errorResource)||\nmxUtils.errorResource,e,(a-b)/2,d/4,b,null,!1,!0);c&&(mxUtils.br(e),b=document.createElement(\"p\"),c=document.createElement(\"button\"),c.setAttribute(\"style\",\"float:right\"),mxEvent.addListener(c,\"click\",function(k){g.destroy()}),mxUtils.write(c,mxResources.get(mxUtils.closeResource)||mxUtils.closeResource),b.appendChild(c),e.appendChild(b),mxUtils.br(e),g.setClosable(!0));g.setVisible(!0);return g},makeDraggable:function(a,b,c,d,e,f,g,k,l,m){a=new mxDragSource(a,c);a.dragOffset=new mxPoint(null!=e?\ne:0,null!=f?f:mxConstants.TOOLTIP_VERTICAL_OFFSET);a.autoscroll=g;a.setGuidesEnabled(!1);null!=l&&(a.highlightDropTargets=l);null!=m&&(a.getDropTarget=m);a.getGraphForEvent=function(n){return\"function\"==typeof b?b(n):b};null!=d&&(a.createDragElement=function(){return d.cloneNode(!0)},k&&(a.createPreviewElement=function(n){var p=d.cloneNode(!0),q=parseInt(p.style.width),r=parseInt(p.style.height);p.style.width=Math.round(q*n.view.scale)+\"px\";p.style.height=Math.round(r*n.view.scale)+\"px\";return p}));\nreturn a},format:function(a){return parseFloat(parseFloat(a).toFixed(2))}},mxConstants={DEFAULT_HOTSPOT:.3,MIN_HOTSPOT_SIZE:8,MAX_HOTSPOT_SIZE:0,RENDERING_HINT_EXACT:\"exact\",RENDERING_HINT_FASTER:\"faster\",RENDERING_HINT_FASTEST:\"fastest\",DIALECT_SVG:\"svg\",DIALECT_MIXEDHTML:\"mixedHtml\",DIALECT_PREFERHTML:\"preferHtml\",DIALECT_STRICTHTML:\"strictHtml\",NS_SVG:\"http://www.w3.org/2000/svg\",NS_XHTML:\"http://www.w3.org/1999/xhtml\",NS_XLINK:\"http://www.w3.org/1999/xlink\",SHADOWCOLOR:\"#808080\",VML_SHADOWCOLOR:\"#808080\",\nSHADOW_OFFSET_X:2,SHADOW_OFFSET_Y:3,SHADOW_BLUR:2,SHADOW_OPACITY:1,NODETYPE_ELEMENT:1,NODETYPE_ATTRIBUTE:2,NODETYPE_TEXT:3,NODETYPE_CDATA:4,NODETYPE_ENTITY_REFERENCE:5,NODETYPE_ENTITY:6,NODETYPE_PROCESSING_INSTRUCTION:7,NODETYPE_COMMENT:8,NODETYPE_DOCUMENT:9,NODETYPE_DOCUMENTTYPE:10,NODETYPE_DOCUMENT_FRAGMENT:11,NODETYPE_NOTATION:12,TOOLTIP_VERTICAL_OFFSET:16,DEFAULT_VALID_COLOR:\"#00FF00\",DEFAULT_INVALID_COLOR:\"#FF0000\",OUTLINE_HIGHLIGHT_COLOR:\"#00FF00\",OUTLINE_HIGHLIGHT_STROKEWIDTH:5,HIGHLIGHT_STROKEWIDTH:3,\nHIGHLIGHT_SIZE:2,HIGHLIGHT_OPACITY:100,CURSOR_MOVABLE_VERTEX:\"move\",CURSOR_MOVABLE_EDGE:\"move\",CURSOR_LABEL_HANDLE:\"default\",CURSOR_TERMINAL_HANDLE:\"pointer\",CURSOR_BEND_HANDLE:\"crosshair\",CURSOR_VIRTUAL_BEND_HANDLE:\"crosshair\",CURSOR_CONNECT:\"pointer\",HIGHLIGHT_COLOR:\"#00FF00\",CONNECT_TARGET_COLOR:\"#0000FF\",INVALID_CONNECT_TARGET_COLOR:\"#FF0000\",DROP_TARGET_COLOR:\"#0000FF\",VALID_COLOR:\"#00FF00\",INVALID_COLOR:\"#FF0000\",EDGE_SELECTION_COLOR:\"#00FF00\",VERTEX_SELECTION_COLOR:\"#00FF00\",VERTEX_SELECTION_STROKEWIDTH:1,\nEDGE_SELECTION_STROKEWIDTH:1,VERTEX_SELECTION_DASHED:!0,EDGE_SELECTION_DASHED:!0,GUIDE_COLOR:\"#FF0000\",GUIDE_STROKEWIDTH:1,OUTLINE_COLOR:\"#0099FF\",OUTLINE_STROKEWIDTH:3,HANDLE_SIZE:6,LABEL_HANDLE_SIZE:4,HANDLE_FILLCOLOR:\"#00FF00\",HANDLE_STROKECOLOR:\"black\",LABEL_HANDLE_FILLCOLOR:\"yellow\",CONNECT_HANDLE_FILLCOLOR:\"#0000FF\",LOCKED_HANDLE_FILLCOLOR:\"#FF0000\",OUTLINE_HANDLE_FILLCOLOR:\"#00FFFF\",OUTLINE_HANDLE_STROKECOLOR:\"#0033FF\",DEFAULT_FONTFAMILY:\"Arial,Helvetica\",DEFAULT_FONTSIZE:11,DEFAULT_TEXT_DIRECTION:\"\",\nLINE_HEIGHT:1.2,WORD_WRAP:\"normal\",ABSOLUTE_LINE_HEIGHT:!1,DEFAULT_FONTSTYLE:0,DEFAULT_STARTSIZE:40,DEFAULT_MARKERSIZE:6,DEFAULT_IMAGESIZE:24,ENTITY_SEGMENT:30,RECTANGLE_ROUNDING_FACTOR:.15,LINE_ARCSIZE:20,ARROW_SPACING:0,ARROW_WIDTH:30,ARROW_SIZE:30,PAGE_FORMAT_A4_PORTRAIT:new mxRectangle(0,0,827,1169),PAGE_FORMAT_A4_LANDSCAPE:new mxRectangle(0,0,1169,827),PAGE_FORMAT_LETTER_PORTRAIT:new mxRectangle(0,0,850,1100),PAGE_FORMAT_LETTER_LANDSCAPE:new mxRectangle(0,0,1100,850),NONE:\"none\",STYLE_PERIMETER:\"perimeter\",\nSTYLE_SOURCE_PORT:\"sourcePort\",STYLE_TARGET_PORT:\"targetPort\",STYLE_PORT_CONSTRAINT:\"portConstraint\",STYLE_PORT_CONSTRAINT_ROTATION:\"portConstraintRotation\",STYLE_SOURCE_PORT_CONSTRAINT:\"sourcePortConstraint\",STYLE_TARGET_PORT_CONSTRAINT:\"targetPortConstraint\",STYLE_OPACITY:\"opacity\",STYLE_FILL_OPACITY:\"fillOpacity\",STYLE_FILL_STYLE:\"fillStyle\",STYLE_STROKE_OPACITY:\"strokeOpacity\",STYLE_TEXT_OPACITY:\"textOpacity\",STYLE_TEXT_DIRECTION:\"textDirection\",STYLE_OVERFLOW:\"overflow\",STYLE_BLOCK_SPACING:\"blockSpacing\",\nSTYLE_ORTHOGONAL:\"orthogonal\",STYLE_EXIT_X:\"exitX\",STYLE_EXIT_Y:\"exitY\",STYLE_EXIT_DX:\"exitDx\",STYLE_EXIT_DY:\"exitDy\",STYLE_EXIT_PERIMETER:\"exitPerimeter\",STYLE_ENTRY_X:\"entryX\",STYLE_ENTRY_Y:\"entryY\",STYLE_ENTRY_DX:\"entryDx\",STYLE_ENTRY_DY:\"entryDy\",STYLE_ENTRY_PERIMETER:\"entryPerimeter\",STYLE_WHITE_SPACE:\"whiteSpace\",STYLE_ROTATION:\"rotation\",STYLE_FILLCOLOR:\"fillColor\",STYLE_POINTER_EVENTS:\"pointerEvents\",STYLE_SWIMLANE_FILLCOLOR:\"swimlaneFillColor\",STYLE_MARGIN:\"margin\",STYLE_GRADIENTCOLOR:\"gradientColor\",\nSTYLE_GRADIENT_DIRECTION:\"gradientDirection\",STYLE_STROKECOLOR:\"strokeColor\",STYLE_SEPARATORCOLOR:\"separatorColor\",STYLE_STROKEWIDTH:\"strokeWidth\",STYLE_ALIGN:\"align\",STYLE_VERTICAL_ALIGN:\"verticalAlign\",STYLE_LABEL_WIDTH:\"labelWidth\",STYLE_LABEL_POSITION:\"labelPosition\",STYLE_VERTICAL_LABEL_POSITION:\"verticalLabelPosition\",STYLE_IMAGE_ASPECT:\"imageAspect\",STYLE_IMAGE_ALIGN:\"imageAlign\",STYLE_IMAGE_VERTICAL_ALIGN:\"imageVerticalAlign\",STYLE_GLASS:\"glass\",STYLE_IMAGE:\"image\",STYLE_IMAGE_WIDTH:\"imageWidth\",\nSTYLE_IMAGE_HEIGHT:\"imageHeight\",STYLE_IMAGE_BACKGROUND:\"imageBackground\",STYLE_IMAGE_BORDER:\"imageBorder\",STYLE_FLIPH:\"flipH\",STYLE_FLIPV:\"flipV\",STYLE_NOLABEL:\"noLabel\",STYLE_NOEDGESTYLE:\"noEdgeStyle\",STYLE_LABEL_BACKGROUNDCOLOR:\"labelBackgroundColor\",STYLE_LABEL_BORDERCOLOR:\"labelBorderColor\",STYLE_LABEL_PADDING:\"labelPadding\",STYLE_INDICATOR_SHAPE:\"indicatorShape\",STYLE_INDICATOR_IMAGE:\"indicatorImage\",STYLE_INDICATOR_COLOR:\"indicatorColor\",STYLE_INDICATOR_STROKECOLOR:\"indicatorStrokeColor\",STYLE_INDICATOR_GRADIENTCOLOR:\"indicatorGradientColor\",\nSTYLE_INDICATOR_SPACING:\"indicatorSpacing\",STYLE_INDICATOR_WIDTH:\"indicatorWidth\",STYLE_INDICATOR_HEIGHT:\"indicatorHeight\",STYLE_INDICATOR_DIRECTION:\"indicatorDirection\",STYLE_SHADOW:\"shadow\",STYLE_TEXT_SHADOW:\"textShadow\",STYLE_SHADOW_OFFSET_X:\"shadowOffsetX\",STYLE_SHADOW_OFFSET_Y:\"shadowOffsetY\",STYLE_SHADOW_BLUR:\"shadowBlur\",STYLE_SHADOWCOLOR:\"shadowColor\",STYLE_SHADOW_OPACITY:\"shadowOpacity\",STYLE_SEGMENT:\"segment\",STYLE_ENDARROW:\"endArrow\",STYLE_STARTARROW:\"startArrow\",STYLE_ENDSIZE:\"endSize\",\nSTYLE_STARTSIZE:\"startSize\",STYLE_FIXED_HEADER:\"fixedHeader\",STYLE_SWIMLANE_LINE:\"swimlaneLine\",STYLE_SWIMLANE_HEAD:\"swimlaneHead\",STYLE_SWIMLANE_BODY:\"swimlaneBody\",STYLE_ENDFILL:\"endFill\",STYLE_ENDFILLCOLOR:\"endFillColor\",STYLE_STARTFILL:\"startFill\",STYLE_STARTFILLCOLOR:\"startFillColor\",STYLE_DASHED:\"dashed\",STYLE_DASH_PATTERN:\"dashPattern\",STYLE_FIX_DASH:\"fixDash\",STYLE_ROUNDED:\"rounded\",STYLE_CURVED:\"curved\",STYLE_BEZIER:\"bezier\",STYLE_ARCSIZE:\"arcSize\",STYLE_ABSOLUTE_ARCSIZE:\"absoluteArcSize\",\nSTYLE_SOURCE_PERIMETER_SPACING:\"sourcePerimeterSpacing\",STYLE_TARGET_PERIMETER_SPACING:\"targetPerimeterSpacing\",STYLE_PERIMETER_SPACING:\"perimeterSpacing\",STYLE_SPACING:\"spacing\",STYLE_SPACING_TOP:\"spacingTop\",STYLE_SPACING_LEFT:\"spacingLeft\",STYLE_SPACING_BOTTOM:\"spacingBottom\",STYLE_SPACING_RIGHT:\"spacingRight\",STYLE_HORIZONTAL:\"horizontal\",STYLE_DIRECTION:\"direction\",STYLE_ANCHOR_POINT_DIRECTION:\"anchorPointDirection\",STYLE_ELBOW:\"elbow\",STYLE_FONTCOLOR:\"fontColor\",STYLE_FONTFAMILY:\"fontFamily\",\nSTYLE_FONTSIZE:\"fontSize\",STYLE_FONTSTYLE:\"fontStyle\",STYLE_ASPECT:\"aspect\",STYLE_AUTOSIZE:\"autosize\",STYLE_AUTOSIZE_GRID:\"autosizeGrid\",STYLE_FIXED_WIDTH:\"fixedWidth\",STYLE_FOLDABLE:\"foldable\",STYLE_GROUP_PADDING:\"groupPadding\",STYLE_EDITABLE:\"editable\",STYLE_BACKGROUND_OUTLINE:\"backgroundOutline\",STYLE_BENDABLE:\"bendable\",STYLE_MOVABLE:\"movable\",STYLE_RESIZABLE:\"resizable\",STYLE_RESIZE_WIDTH:\"resizeWidth\",STYLE_RESIZE_HEIGHT:\"resizeHeight\",STYLE_ROTATABLE:\"rotatable\",STYLE_CLONEABLE:\"cloneable\",\nSTYLE_DELETABLE:\"deletable\",STYLE_SHAPE:\"shape\",STYLE_EDGE:\"edgeStyle\",STYLE_JETTY_SIZE:\"jettySize\",STYLE_SOURCE_JETTY_SIZE:\"sourceJettySize\",STYLE_TARGET_JETTY_SIZE:\"targetJettySize\",STYLE_LOOP:\"loopStyle\",STYLE_ORTHOGONAL_LOOP:\"orthogonalLoop\",STYLE_ROUTING_CENTER_X:\"routingCenterX\",STYLE_ROUTING_CENTER_Y:\"routingCenterY\",STYLE_CLIP_PATH:\"clipPath\",FONT_BOLD:1,FONT_ITALIC:2,FONT_UNDERLINE:4,FONT_STRIKETHROUGH:8,SHAPE_RECTANGLE:\"rectangle\",SHAPE_ELLIPSE:\"ellipse\",SHAPE_DOUBLE_ELLIPSE:\"doubleEllipse\",\nSHAPE_RHOMBUS:\"rhombus\",SHAPE_LINE:\"line\",SHAPE_IMAGE:\"image\",SHAPE_ARROW:\"arrow\",SHAPE_ARROW_CONNECTOR:\"arrowConnector\",SHAPE_LABEL:\"label\",SHAPE_CYLINDER:\"cylinder\",SHAPE_SWIMLANE:\"swimlane\",SHAPE_CONNECTOR:\"connector\",SHAPE_ACTOR:\"actor\",SHAPE_CLOUD:\"cloud\",SHAPE_TRIANGLE:\"triangle\",SHAPE_HEXAGON:\"hexagon\",ARROW_CLASSIC:\"classic\",ARROW_CLASSIC_THIN:\"classicThin\",ARROW_BLOCK:\"block\",ARROW_BLOCK_THIN:\"blockThin\",ARROW_OPEN:\"open\",ARROW_OPEN_THIN:\"openThin\",ARROW_OVAL:\"oval\",ARROW_DIAMOND:\"diamond\",\nARROW_DIAMOND_THIN:\"diamondThin\",ALIGN_LEFT:\"left\",ALIGN_CENTER:\"center\",ALIGN_RIGHT:\"right\",ALIGN_TOP:\"top\",ALIGN_MIDDLE:\"middle\",ALIGN_BOTTOM:\"bottom\",DIRECTION_NORTH:\"north\",DIRECTION_SOUTH:\"south\",DIRECTION_EAST:\"east\",DIRECTION_WEST:\"west\",DIRECTION_RADIAL:\"radial\",TEXT_DIRECTION_DEFAULT:\"\",TEXT_DIRECTION_AUTO:\"auto\",TEXT_DIRECTION_LTR:\"ltr\",TEXT_DIRECTION_RTL:\"rtl\",TEXT_DIRECTION_VERTICAL_LR:\"vertical-lr\",TEXT_DIRECTION_VERTICAL_RL:\"vertical-rl\",DIRECTION_MASK_NONE:0,DIRECTION_MASK_WEST:1,DIRECTION_MASK_NORTH:2,\nDIRECTION_MASK_SOUTH:4,DIRECTION_MASK_EAST:8,DIRECTION_MASK_ALL:15,ELBOW_VERTICAL:\"vertical\",ELBOW_HORIZONTAL:\"horizontal\",EDGESTYLE_ELBOW:\"elbowEdgeStyle\",EDGESTYLE_ENTITY_RELATION:\"entityRelationEdgeStyle\",EDGESTYLE_LOOP:\"loopEdgeStyle\",EDGESTYLE_SIDETOSIDE:\"sideToSideEdgeStyle\",EDGESTYLE_TOPTOBOTTOM:\"topToBottomEdgeStyle\",EDGESTYLE_ORTHOGONAL:\"orthogonalEdgeStyle\",EDGESTYLE_SEGMENT:\"segmentEdgeStyle\",PERIMETER_ELLIPSE:\"ellipsePerimeter\",PERIMETER_RECTANGLE:\"rectanglePerimeter\",PERIMETER_RHOMBUS:\"rhombusPerimeter\",\nPERIMETER_HEXAGON:\"hexagonPerimeter\",PERIMETER_TRIANGLE:\"trianglePerimeter\"};function mxEventObject(a){this.name=a;this.properties=[];for(var b=1;bk,!0),c=g.scale)});mxEvent.addListener(b,\"gestureend\",function(g){mxEvent.consume(g)})}else{var d=\n[],e=0,f=0;mxEvent.addGestureListeners(b,mxUtils.bind(this,function(g){mxEvent.isMouseEvent(g)||null==g.pointerId||d.push(g)}),mxUtils.bind(this,function(g){if(!mxEvent.isMouseEvent(g)&&2==d.length){for(var k=0;kmxEvent.PINCH_THRESHOLD||m>mxEvent.PINCH_THRESHOLD)a(d[0],l>m?g>e:k>f,!0,d[0].clientX+(d[1].clientX-d[0].clientX)/\n2,d[0].clientY+(d[1].clientY-d[0].clientY)/2),e=g,f=k}}),mxUtils.bind(this,function(g){d=[];f=e=0}))}mxEvent.addListener(b,\"wheel\",function(g){null==g&&(g=window.event);g.ctrlKey&&g.preventDefault();(.5=navigator.maxTouchPoints?!0:null!=a.pointerType?\"mouse\"==a.pointerType||a.pointerType===a.MSPOINTER_TYPE_MOUSE:null!=a.type&&0==a.type.indexOf(\"mouse\")},isLeftMouseButton:function(a){return\"buttons\"in a&&(\"mousedown\"==a.type||\"mousemove\"==a.type)?1==a.buttons:\"which\"in a?1===a.which:1===a.button},isMiddleMouseButton:function(a){return\"which\"in a?2===a.which:4===a.button},isRightMouseButton:function(a){return\"which\"in a?3===a.which:2===a.button},isPopupTrigger:function(a){return mxEvent.isRightMouseButton(a)||\nmxClient.IS_MAC&&mxEvent.isControlDown(a)&&!mxEvent.isShiftDown(a)&&!mxEvent.isMetaDown(a)&&!mxEvent.isAltDown(a)},isShiftDown:function(a){return null!=a?a.shiftKey:!1},isAltDown:function(a){return null!=a?a.altKey:!1},isControlDown:function(a){return null!=a?a.ctrlKey:!1},isMetaDown:function(a){return null!=a?a.metaKey:!1},getMainEvent:function(a){\"touchstart\"!=a.type&&\"touchmove\"!=a.type||null==a.touches||null==a.touches[0]?\"touchend\"==a.type&&null!=a.changedTouches&&null!=a.changedTouches[0]&&\n(a=a.changedTouches[0]):a=a.touches[0];return a},getClientX:function(a){return mxEvent.getMainEvent(a).clientX},getClientY:function(a){return mxEvent.getMainEvent(a).clientY},consume:function(a,b,c){c=null!=c?c:!0;if(null!=b?b:1)a.preventDefault?(c&&a.stopPropagation(),a.preventDefault()):c&&(a.cancelBubble=!0);a.isConsumed=!0;a.preventDefault||(a.returnValue=!1)},preventDefault:function(a){mxEvent.addListener(a,mxClient.IS_POINTER?\"pointerdown\":\"mousedown\",mxUtils.bind(this,function(b){b.preventDefault()}))},\nLABEL_HANDLE:-1,ROTATION_HANDLE:-2,CONNECT_HANDLE:-3,CUSTOM_HANDLE:-100,VIRTUAL_HANDLE:-1E5,MOUSE_DOWN:\"mouseDown\",MOUSE_MOVE:\"mouseMove\",MOUSE_UP:\"mouseUp\",ACTIVATE:\"activate\",RESIZE_START:\"resizeStart\",RESIZE:\"resize\",RESIZE_END:\"resizeEnd\",MOVE_START:\"moveStart\",MOVE:\"move\",MOVE_END:\"moveEnd\",DOCK:\"dock\",UNDOCK:\"undock\",PAN_START:\"panStart\",PAN:\"pan\",PAN_END:\"panEnd\",MINIMIZE:\"minimize\",NORMALIZE:\"normalize\",MAXIMIZE:\"maximize\",HIDE:\"hide\",SHOW:\"show\",CLOSE:\"close\",DESTROY:\"destroy\",REFRESH:\"refresh\",\nSIZE:\"size\",SELECT:\"select\",FIRED:\"fired\",FIRE_MOUSE_EVENT:\"fireMouseEvent\",CONSUME_MOUSE_EVENT:\"consumeMouseEvent\",GESTURE:\"gesture\",TAP_AND_HOLD:\"tapAndHold\",GET:\"get\",RECEIVE:\"receive\",CONNECT:\"connect\",DISCONNECT:\"disconnect\",SUSPEND:\"suspend\",RESUME:\"resume\",MARK:\"mark\",ROOT:\"root\",POST:\"post\",OPEN:\"open\",SAVE:\"save\",BEFORE_ADD_VERTEX:\"beforeAddVertex\",ADD_VERTEX:\"addVertex\",AFTER_ADD_VERTEX:\"afterAddVertex\",DONE:\"done\",EXECUTE:\"execute\",EXECUTED:\"executed\",BEGIN_UPDATE:\"beginUpdate\",START_EDIT:\"startEdit\",\nEND_UPDATE:\"endUpdate\",END_EDIT:\"endEdit\",BEFORE_UNDO:\"beforeUndo\",UNDO:\"undo\",REDO:\"redo\",CHANGE:\"change\",NOTIFY:\"notify\",LAYOUT_CELLS:\"layoutCells\",CLICK:\"click\",SCALE:\"scale\",TRANSLATE:\"translate\",SCALE_AND_TRANSLATE:\"scaleAndTranslate\",UP:\"up\",DOWN:\"down\",ADD:\"add\",REMOVE:\"remove\",CLEAR:\"clear\",ADD_CELLS:\"addCells\",CELLS_ADDED:\"cellsAdded\",MOVE_CELLS:\"moveCells\",CELLS_MOVED:\"cellsMoved\",RESIZE_CELLS:\"resizeCells\",CELLS_RESIZED:\"cellsResized\",TOGGLE_CELLS:\"toggleCells\",CELLS_TOGGLED:\"cellsToggled\",\nORDER_CELLS:\"orderCells\",CELLS_ORDERED:\"cellsOrdered\",REMOVE_CELLS:\"removeCells\",CELLS_REMOVED:\"cellsRemoved\",GROUP_CELLS:\"groupCells\",UNGROUP_CELLS:\"ungroupCells\",REMOVE_CELLS_FROM_PARENT:\"removeCellsFromParent\",FOLD_CELLS:\"foldCells\",CELLS_FOLDED:\"cellsFolded\",ALIGN_CELLS:\"alignCells\",LABEL_CHANGED:\"labelChanged\",CONNECT_CELL:\"connectCell\",CELL_CONNECTED:\"cellConnected\",SPLIT_EDGE:\"splitEdge\",FLIP_EDGE:\"flipEdge\",START_EDITING:\"startEditing\",EDITING_STARTED:\"editingStarted\",EDITING_STOPPED:\"editingStopped\",\nADD_OVERLAY:\"addOverlay\",REMOVE_OVERLAY:\"removeOverlay\",UPDATE_CELL_SIZE:\"updateCellSize\",ESCAPE:\"escape\",DOUBLE_CLICK:\"doubleClick\",START:\"start\",UPDATE:\"update\",RESET:\"reset\",PINCH_THRESHOLD:10};function mxXmlRequest(a,b,c,d,e,f){this.url=a;this.params=b;this.method=c||\"POST\";this.async=null!=d?d:!0;this.username=e;this.password=f}mxXmlRequest.prototype.url=null;mxXmlRequest.prototype.params=null;mxXmlRequest.prototype.method=null;mxXmlRequest.prototype.async=null;\nmxXmlRequest.prototype.binary=!1;mxXmlRequest.prototype.withCredentials=!1;mxXmlRequest.prototype.username=null;mxXmlRequest.prototype.password=null;mxXmlRequest.prototype.request=null;mxXmlRequest.prototype.decodeSimulateValues=!1;mxXmlRequest.prototype.acceptResponse=!0;mxXmlRequest.prototype.isBinary=function(){return this.binary};mxXmlRequest.prototype.setBinary=function(a){this.binary=a};mxXmlRequest.prototype.getText=function(){return this.request.responseText};\nmxXmlRequest.prototype.isReady=function(){return 4==this.request.readyState};mxXmlRequest.prototype.getDocumentElement=function(){var a=this.getXml();return null!=a?a.documentElement:null};mxXmlRequest.prototype.getXml=function(){var a=this.request.responseXML;if(9<=document.documentMode||null==a||null==a.documentElement)a=mxUtils.parseXml(this.request.responseText);return a};mxXmlRequest.prototype.getStatus=function(){return null!=this.request?this.request.status:null};\nmxXmlRequest.prototype.create=function(){if(window.XMLHttpRequest)return function(){var a=new XMLHttpRequest;this.isBinary()&&a.overrideMimeType&&a.overrideMimeType(\"text/plain; charset=x-user-defined\");return a};if(\"undefined\"!=typeof ActiveXObject)return function(){return new ActiveXObject(\"Microsoft.XMLHTTP\")}}();\nmxXmlRequest.prototype.send=function(a,b,c,d){this.request=this.create();null!=this.request&&(null!=a&&(this.request.onreadystatechange=mxUtils.bind(this,function(){this.isReady()&&this.acceptResponse&&(this.acceptResponse=!1,a(this),this.request.onreadystatechange=null)})),this.request.open(this.method,this.url,this.async,this.username,this.password),this.setRequestHeaders(this.request,this.params),window.XMLHttpRequest&&this.withCredentials&&(this.request.withCredentials=\"true\"),null!=b&&(this.request.onerror=\nmxUtils.bind(this,function(e){this.acceptResponse&&(this.acceptResponse=!1,b(this,e))})),(null==document.documentMode||9navigator.userAgent.indexOf(\"Presto/2.5\"))this.contentWrapper.style.overflow=a?\"auto\":\"hidden\"};\nmxWindow.prototype.activate=function(){if(mxWindow.activeWindow!=this){var a=mxUtils.getCurrentStyle(this.getElement());a=null!=a?a.zIndex:3;if(mxWindow.activeWindow){var b=mxWindow.activeWindow.getElement();null!=b&&null!=b.style&&(b.style.zIndex=a)}b=mxWindow.activeWindow;this.getElement().style.zIndex=parseInt(a)+1;mxWindow.activeWindow=this;this.fireEvent(new mxEventObject(mxEvent.ACTIVATE,\"previousWindow\",b))}};mxWindow.prototype.getElement=function(){return this.div};\nmxWindow.prototype.fit=function(){mxUtils.fit(this.div)};mxWindow.prototype.isResizable=function(){return null!=this.resize?\"none\"!=this.resize.style.display:!1};\nmxWindow.prototype.setResizable=function(a){if(a)if(null==this.resize){this.resize=document.createElement(\"img\");this.resize.style.position=\"absolute\";this.resize.style.bottom=\"0px\";this.resize.style.right=\"0px\";this.resize.style.zIndex=\"2\";this.resize.setAttribute(\"src\",this.resizeImage);this.resize.style.cursor=\"nwse-resize\";var b=null,c=null,d=null,e=null;a=mxUtils.bind(this,function(k){this.activate();b=mxEvent.getClientX(k);c=mxEvent.getClientY(k);d=this.div.offsetWidth;e=this.div.offsetHeight;\nmxEvent.addGestureListeners(document,null,f,g);this.fireEvent(new mxEventObject(mxEvent.RESIZE_START,\"event\",k));mxEvent.consume(k)});var f=mxUtils.bind(this,function(k){if(null!=b&&null!=c){var l=mxEvent.getClientX(k)-b,m=mxEvent.getClientY(k)-c;this.setSize(d+l,e+m);this.fireEvent(new mxEventObject(mxEvent.RESIZE,\"event\",k));mxEvent.consume(k)}}),g=mxUtils.bind(this,function(k){null!=b&&null!=c&&(c=b=null,mxEvent.removeGestureListeners(document,null,f,g),this.fireEvent(new mxEventObject(mxEvent.RESIZE_END,\n\"event\",k)),mxEvent.consume(k))});mxEvent.addGestureListeners(this.resize,a,f,g);this.div.appendChild(this.resize)}else this.resize.style.display=\"inline\";else null!=this.resize&&(this.resize.style.display=\"none\")};\nmxWindow.prototype.setSize=function(a,b){a=Math.max(this.minimumSize.width,a);b=Math.max(this.minimumSize.height,b);this.div.style.width=a+\"px\";this.div.style.height=b+\"px\";this.table.style.width=a+\"px\";this.table.style.height=b+\"px\";this.contentWrapper.style.height=this.div.offsetHeight-this.title.offsetHeight-this.contentHeightCorrection+\"px\"};mxWindow.prototype.setMinimizable=function(a){this.minimizeImg.style.display=a?\"\":\"none\"};\nmxWindow.prototype.getMinimumSize=function(){return new mxRectangle(0,0,0,this.title.offsetHeight)};\nmxWindow.prototype.toggleMinimized=function(a){this.activate();if(this.minimized)this.minimized=!1,this.minimizeImg.setAttribute(\"src\",this.minimizeImage),this.minimizeImg.setAttribute(\"title\",mxResources.get(\"minimize\",null,\"Minimize\")),this.contentWrapper.style.display=\"\",this.maximize.style.display=this.maxDisplay,this.div.style.height=this.height,this.table.style.height=this.height,null!=this.resize&&(this.resize.style.visibility=\"\"),this.fireEvent(new mxEventObject(mxEvent.NORMALIZE,\"event\",\na));else{this.minimized=!0;this.minimizeImg.setAttribute(\"src\",this.normalizeImage);this.minimizeImg.setAttribute(\"title\",mxResources.get(\"restore\",null,\"Restore\"));this.contentWrapper.style.display=\"none\";this.maxDisplay=this.maximize.style.display;this.maximize.style.display=\"none\";this.height=this.table.style.height;var b=this.getMinimumSize();0=e.x-f.x&&d>=e.y-f.y&&c<=e.x-f.x+a.container.offsetWidth&&d<=e.y-f.y+a.container.offsetHeight};\nmxDragSource.prototype.mouseMove=function(a){var b=this.getGraphForEvent(a);null==b||this.graphContainsEvent(b,a)||(b=null);b!=this.currentGraph&&(null!=this.currentGraph&&this.dragExit(this.currentGraph,a),this.currentGraph=b,null!=this.currentGraph&&this.dragEnter(this.currentGraph,a));null!=this.currentGraph&&this.dragOver(this.currentGraph,a);if(null==this.dragElement||null!=this.previewElement&&\"visible\"==this.previewElement.style.visibility)null!=this.dragElement&&mxUtils.setOpacity(this.dragElement,\n0);else{b=mxEvent.getClientX(a);var c=mxEvent.getClientY(a);null==this.dragElement.parentNode&&document.body.appendChild(this.dragElement);mxUtils.setOpacity(this.dragElement,this.dragElementOpacity);null!=this.dragOffset&&(b+=this.dragOffset.x,c+=this.dragOffset.y);var d=mxUtils.getDocumentScrollOrigin(document);this.dragElement.style.left=b+d.x+\"px\";this.dragElement.style.top=c+d.y+\"px\"}mxEvent.consume(a)};\nmxDragSource.prototype.mouseUp=function(a){if(null!=this.currentGraph){if(null!=this.currentPoint&&(null==this.previewElement||\"hidden\"!=this.previewElement.style.visibility)){var b=this.currentGraph.view.scale,c=this.currentGraph.view.translate;this.drop(this.currentGraph,a,this.currentDropTarget,this.currentPoint.x/b-c.x,this.currentPoint.y/b-c.y)}this.dragExit(this.currentGraph);this.currentGraph=null}this.stopDrag();this.removeListeners();mxEvent.consume(a)};\nmxDragSource.prototype.removeListeners=function(){null!=this.eventSource&&(mxEvent.removeGestureListeners(this.eventSource,null,this.mouseMoveHandler,this.mouseUpHandler),this.eventSource=null);mxEvent.removeGestureListeners(document,null,this.mouseMoveHandler,this.mouseUpHandler);this.mouseUpHandler=this.mouseMoveHandler=null};\nmxDragSource.prototype.dragEnter=function(a,b){a.isMouseDown=!0;a.isMouseTrigger=mxEvent.isMouseEvent(b);this.previewElement=this.createPreviewElement(a);null!=this.previewElement&&this.checkEventSource&&mxClient.IS_SVG&&(this.previewElement.style.pointerEvents=\"none\");this.isGuidesEnabled()&&null!=this.previewElement&&(this.currentGuide=new mxGuide(a,a.graphHandler.getGuideStates()));this.highlightDropTargets&&(this.currentHighlight=new mxCellHighlight(a,mxConstants.DROP_TARGET_COLOR));a.addListener(mxEvent.FIRE_MOUSE_EVENT,\nthis.eventConsumer)};mxDragSource.prototype.dragExit=function(a,b){this.currentPoint=this.currentDropTarget=null;a.isMouseDown=!1;a.removeListener(this.eventConsumer);null!=this.previewElement&&(null!=this.previewElement.parentNode&&this.previewElement.parentNode.removeChild(this.previewElement),this.previewElement=null);null!=this.currentGuide&&(this.currentGuide.destroy(),this.currentGuide=null);null!=this.currentHighlight&&(this.currentHighlight.destroy(),this.currentHighlight=null)};\nmxDragSource.prototype.dragOver=function(a,b){var c=mxUtils.getOffset(a.container),d=mxUtils.getScrollOrigin(a.container),e=mxEvent.getClientX(b)-c.x+d.x-a.panDx;c=mxEvent.getClientY(b)-c.y+d.y-a.panDy;a.autoScroll&&(null==this.autoscroll||this.autoscroll)&&a.scrollPointToVisible(e,c,a.autoExtend);null!=this.currentHighlight&&a.isDropEnabled()&&(this.currentDropTarget=this.getDropTarget(a,e,c,b),d=a.getView().getState(this.currentDropTarget),this.currentHighlight.highlight(d));if(null!=this.previewElement){null==\nthis.previewElement.parentNode&&(a.container.appendChild(this.previewElement),this.previewElement.style.zIndex=\"3\",this.previewElement.style.position=\"absolute\");d=this.isGridEnabled()&&a.isGridEnabledEvent(b);var f=!0;if(null!=this.currentGuide&&this.currentGuide.isEnabledForEvent(b))a=parseInt(this.previewElement.style.width),f=parseInt(this.previewElement.style.height),a=new mxRectangle(0,0,a,f),c=new mxPoint(e,c),c=this.currentGuide.move(a,c,d,!0),f=!1,e=c.x,c=c.y;else if(d){d=a.view.scale;b=\na.view.translate;var g=a.gridSize/2;e=(a.snap(e/d-b.x-g)+b.x)*d;c=(a.snap(c/d-b.y-g)+b.y)*d}null!=this.currentGuide&&f&&this.currentGuide.hide();null!=this.previewOffset&&(e+=this.previewOffset.x,c+=this.previewOffset.y);this.previewElement.style.left=Math.round(e)+\"px\";this.previewElement.style.top=Math.round(c)+\"px\";this.previewElement.style.visibility=\"visible\"}this.currentPoint=new mxPoint(e,c)};\nmxDragSource.prototype.drop=function(a,b,c,d,e){this.dropHandler.apply(this,arguments);\"hidden\"!=a.container.style.visibility&&a.container.focus()};function mxToolbar(a){this.container=a}mxToolbar.prototype=new mxEventSource;mxToolbar.prototype.constructor=mxToolbar;mxToolbar.prototype.container=null;mxToolbar.prototype.enabled=!0;mxToolbar.prototype.noReset=!1;mxToolbar.prototype.updateDefaultMode=!0;\nmxToolbar.prototype.addItem=function(a,b,c,d,e,f){var g=document.createElement(null!=b?\"img\":\"button\"),k=e||(null!=f?\"mxToolbarMode\":\"mxToolbarItem\");g.className=k;g.setAttribute(\"src\",b);null!=a&&(null!=b?g.setAttribute(\"title\",a):mxUtils.write(g,a));this.container.appendChild(g);null!=c&&(mxEvent.addListener(g,\"click\",c),mxClient.IS_TOUCH&&mxEvent.addListener(g,\"touchend\",c));a=mxUtils.bind(this,function(l){null!=d?g.setAttribute(\"src\",b):g.style.backgroundColor=\"\"});mxEvent.addGestureListeners(g,\nmxUtils.bind(this,function(l){null!=d?g.setAttribute(\"src\",d):g.style.backgroundColor=\"gray\";if(null!=f){null==this.menu&&(this.menu=new mxPopupMenu,this.menu.init());var m=this.currentImg;this.menu.isMenuShowing()&&this.menu.hideMenu();m!=g&&(this.currentImg=g,this.menu.factoryMethod=f,m=new mxPoint(g.offsetLeft,g.offsetTop+g.offsetHeight),this.menu.popup(m.x,m.y,null,l),this.menu.isMenuShowing()&&(g.className=k+\"Selected\",this.menu.hideMenu=function(){mxPopupMenu.prototype.hideMenu.apply(this);\ng.className=k;this.currentImg=null}))}}),null,a);mxEvent.addListener(g,\"mouseout\",a);return g};mxToolbar.prototype.addCombo=function(a){var b=document.createElement(\"div\");b.style.display=\"inline\";b.className=\"mxToolbarComboContainer\";var c=document.createElement(\"select\");c.className=a||\"mxToolbarCombo\";b.appendChild(c);this.container.appendChild(b);return c};\nmxToolbar.prototype.addActionCombo=function(a,b){var c=document.createElement(\"select\");c.className=b||\"mxToolbarCombo\";this.addOption(c,a,null);mxEvent.addListener(c,\"change\",function(d){var e=c.options[c.selectedIndex];c.selectedIndex=0;null!=e.funct&&e.funct(d)});this.container.appendChild(c);return c};mxToolbar.prototype.addOption=function(a,b,c){var d=document.createElement(\"option\");mxUtils.writeln(d,b);\"function\"==typeof c?d.funct=c:d.setAttribute(\"value\",c);a.appendChild(d);return d};\nmxToolbar.prototype.addSwitchMode=function(a,b,c,d,e){var f=document.createElement(\"img\");f.initialClassName=e||\"mxToolbarMode\";f.className=f.initialClassName;f.setAttribute(\"src\",b);f.altIcon=d;null!=a&&f.setAttribute(\"title\",a);mxEvent.addListener(f,\"click\",mxUtils.bind(this,function(g){g=this.selectedMode.altIcon;null!=g?(this.selectedMode.altIcon=this.selectedMode.getAttribute(\"src\"),this.selectedMode.setAttribute(\"src\",g)):this.selectedMode.className=this.selectedMode.initialClassName;this.updateDefaultMode&&\n(this.defaultMode=f);this.selectedMode=f;g=f.altIcon;null!=g?(f.altIcon=f.getAttribute(\"src\"),f.setAttribute(\"src\",g)):f.className=f.initialClassName+\"Selected\";this.fireEvent(new mxEventObject(mxEvent.SELECT));c()}));this.container.appendChild(f);null==this.defaultMode&&(this.defaultMode=f,this.selectMode(f),c());return f};\nmxToolbar.prototype.addMode=function(a,b,c,d,e,f){f=null!=f?f:!0;var g=document.createElement(null!=b?\"img\":\"button\");g.initialClassName=e||\"mxToolbarMode\";g.className=g.initialClassName;g.setAttribute(\"src\",b);g.altIcon=d;null!=a&&g.setAttribute(\"title\",a);this.enabled&&f&&(mxEvent.addListener(g,\"click\",mxUtils.bind(this,function(k){this.selectMode(g,c);this.noReset=!1})),mxEvent.addListener(g,\"dblclick\",mxUtils.bind(this,function(k){this.selectMode(g,c);this.noReset=!0})),null==this.defaultMode&&\n(this.defaultMode=g,this.defaultFunction=c,this.selectMode(g,c)));this.container.appendChild(g);return g};\nmxToolbar.prototype.selectMode=function(a,b){if(this.selectedMode!=a){if(null!=this.selectedMode){var c=this.selectedMode.altIcon;null!=c?(this.selectedMode.altIcon=this.selectedMode.getAttribute(\"src\"),this.selectedMode.setAttribute(\"src\",c)):this.selectedMode.className=this.selectedMode.initialClassName}this.selectedMode=a;c=this.selectedMode.altIcon;null!=c?(this.selectedMode.altIcon=this.selectedMode.getAttribute(\"src\"),this.selectedMode.setAttribute(\"src\",c)):this.selectedMode.className=this.selectedMode.initialClassName+\n\"Selected\";this.fireEvent(new mxEventObject(mxEvent.SELECT,\"function\",b))}};mxToolbar.prototype.resetMode=function(a){!a&&this.noReset||this.selectedMode==this.defaultMode||this.selectMode(this.defaultMode,this.defaultFunction)};mxToolbar.prototype.addSeparator=function(a){return this.addItem(null,a,null)};mxToolbar.prototype.addBreak=function(){mxUtils.br(this.container)};\nmxToolbar.prototype.addLine=function(){var a=document.createElement(\"hr\");a.style.marginRight=\"6px\";a.setAttribute(\"size\",\"1\");this.container.appendChild(a)};mxToolbar.prototype.destroy=function(){mxEvent.release(this.container);this.selectedMode=this.defaultFunction=this.defaultMode=this.container=null;null!=this.menu&&this.menu.destroy()};function mxUndoableEdit(a,b){this.source=a;this.changes=[];this.significant=null!=b?b:!0}mxUndoableEdit.prototype.source=null;\nmxUndoableEdit.prototype.changes=null;mxUndoableEdit.prototype.significant=null;mxUndoableEdit.prototype.undone=!1;mxUndoableEdit.prototype.redone=!1;mxUndoableEdit.prototype.isEmpty=function(){return 0==this.changes.length};mxUndoableEdit.prototype.isSignificant=function(){return this.significant};mxUndoableEdit.prototype.add=function(a){this.changes.push(a)};mxUndoableEdit.prototype.notify=function(){};mxUndoableEdit.prototype.die=function(){};\nmxUndoableEdit.prototype.undo=function(){if(!this.undone){this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT));for(var a=this.changes.length-1;0<=a;a--){var b=this.changes[a];null!=b.execute?b.execute():null!=b.undo&&b.undo();this.source.fireEvent(new mxEventObject(mxEvent.EXECUTED,\"change\",b))}this.undone=!0;this.redone=!1;this.source.fireEvent(new mxEventObject(mxEvent.END_EDIT))}this.notify()};\nmxUndoableEdit.prototype.redo=function(){if(!this.redone){this.source.fireEvent(new mxEventObject(mxEvent.START_EDIT));for(var a=this.changes.length,b=0;bthis.indexOfNextAdd)for(var a=this.history.splice(this.indexOfNextAdd,this.history.length-this.indexOfNextAdd),b=0;bthis.dx&&Math.abs(this.dx)<\nthis.border?this.border+this.dx:this.handleMouseOut?Math.max(this.dx,0):0;0==this.dx&&(this.dx=c-g.scrollLeft,this.dx=0this.dy&&Math.abs(this.dy)e.x+(document.body.clientWidth||f.clientWidth)&&(b.div.style.left=Math.max(0,a.div.offsetLeft-d+16)+\"px\");b.div.style.overflowY=\"auto\";b.div.style.overflowX=\n\"hidden\";b.div.style.maxHeight=Math.max(document.body.clientHeight,document.documentElement.clientHeight)-10+\"px\";mxUtils.fit(b.div)}};\nmxPopupMenu.prototype.addSeparator=function(a,b){a=a||this;if(this.smartSeparators&&!b)a.willAddSeparator=!0;else if(null!=a.tbody){a.willAddSeparator=!1;b=document.createElement(\"tr\");var c=document.createElement(\"td\");c.className=\"mxPopupMenuIcon\";c.style.padding=\"0 0 0 0px\";b.appendChild(c);c=document.createElement(\"td\");c.style.padding=\"0 0 0 0px\";c.setAttribute(\"colSpan\",\"2\");var d=document.createElement(\"hr\");d.setAttribute(\"size\",\"1\");c.appendChild(d);b.appendChild(c);a.tbody.appendChild(b)}};\nmxPopupMenu.prototype.popup=function(a,b,c,d){if(null!=this.div&&null!=this.tbody&&null!=this.factoryMethod){this.div.style.left=a+\"px\";for(this.div.style.top=b+\"px\";null!=this.tbody.firstChild;)mxEvent.release(this.tbody.firstChild),this.tbody.removeChild(this.tbody.firstChild);this.itemCount=0;this.factoryMethod(this,c,d);0this.autoSaveDelay||this.ignoredChanges>=this.autoSaveThreshold&&a>this.autoSaveThrottle?(this.save(),this.reset()):this.ignoredChanges++};\nmxAutoSaveManager.prototype.reset=function(){this.lastSnapshot=(new Date).getTime();this.ignoredChanges=0};mxAutoSaveManager.prototype.destroy=function(){this.setGraph(null)};function mxAnimation(a){this.delay=null!=a?a:20}mxAnimation.prototype=new mxEventSource;mxAnimation.prototype.constructor=mxAnimation;mxAnimation.prototype.delay=null;mxAnimation.prototype.thread=null;mxAnimation.prototype.isRunning=function(){return null!=this.thread};\nmxAnimation.prototype.startAnimation=function(){null==this.thread&&(this.thread=window.setInterval(mxUtils.bind(this,this.updateAnimation),this.delay))};mxAnimation.prototype.updateAnimation=function(){this.fireEvent(new mxEventObject(mxEvent.EXECUTE))};mxAnimation.prototype.stopAnimation=function(){null!=this.thread&&(window.clearInterval(this.thread),this.thread=null,this.fireEvent(new mxEventObject(mxEvent.DONE)))};\nfunction mxMorphing(a,b,c,d){mxAnimation.call(this,d);this.graph=a;this.steps=null!=b?b:6;this.ease=null!=c?c:1.5}mxMorphing.prototype=new mxAnimation;mxMorphing.prototype.constructor=mxMorphing;mxMorphing.prototype.graph=null;mxMorphing.prototype.steps=null;mxMorphing.prototype.step=0;mxMorphing.prototype.ease=null;mxMorphing.prototype.cells=null;\nmxMorphing.prototype.updateAnimation=function(){mxAnimation.prototype.updateAnimation.apply(this,arguments);var a=new mxCellStatePreview(this.graph);if(null!=this.cells)for(var b=0;b=this.steps)&&this.stopAnimation()};mxMorphing.prototype.show=function(a){a.show()};\nmxMorphing.prototype.animateCell=function(a,b,c){var d=this.graph.getView().getState(a),e=null;if(null!=d&&(e=this.getDelta(d),this.graph.getModel().isVertex(a)&&(0!=e.x||0!=e.y))){var f=this.graph.view.getTranslate(),g=this.graph.view.getScale();e.x+=f.x*g;e.y+=f.y*g;b.moveState(d,-e.x/this.ease,-e.y/this.ease)}if(c&&!this.stopRecursion(d,e))for(d=this.graph.getModel().getChildCount(a),e=0;ea.alpha||1>a.fillAlpha)&&this.node.setAttribute(\"fill-opacity\",a.alpha*a.fillAlpha);var b=!1;if(null!=a.fillColor)if(null!=a.gradientColor&&a.gradientColor!=mxConstants.NONE){b=!0;var c=this.getSvgGradient(String(a.fillColor),String(a.gradientColor),a.gradientFillAlpha,a.gradientAlpha,a.gradientDirection);if(this.root.ownerDocument==document&&this.useAbsoluteIds){var d=this.getBaseUrl().replace(/([\\(\\)])/g,\"\\\\$1\");c=\"url(\"+d+\"#\"+c+\n\")\"}else c=\"url(#\"+c+\")\";d=c}else c=this.getLightDarkColor(String(a.fillColor).toLowerCase()),d=c.light,c=c.cssText;var e=Math.max(this.minStrokeWidth,Math.max(.01,this.format(a.strokeWidth)));a=null==a.fillStyle||\"auto\"==a.fillStyle||\"solid\"==a.fillStyle?null:this.getFillPattern(a.fillStyle,e,c,a.scale);b||null==a?(this.node.setAttribute(\"fill\",d),this.node.style.fill=c):this.root.ownerDocument==document&&this.useAbsoluteIds?(d=this.getBaseUrl().replace(/([\\(\\)])/g,\"\\\\$1\"),this.node.setAttribute(\"fill\",\n\"url(\"+d+\"#\"+a+\")\")):this.node.setAttribute(\"fill\",\"url(#\"+a+\")\")};mxSvgCanvas2D.prototype.getCurrentStrokeWidth=function(){return Math.max(this.minStrokeWidth,Math.max(.01,this.format(this.state.strokeWidth*this.state.scale)))};\nmxSvgCanvas2D.prototype.updateStroke=function(){var a=this.state,b=this.getLightDarkColor(String(a.strokeColor).toLowerCase());this.node.setAttribute(\"stroke\",b.light);this.node.style.stroke=b.cssText;(1>a.alpha||1>a.strokeAlpha)&&this.node.setAttribute(\"stroke-opacity\",a.alpha*a.strokeAlpha);b=this.getCurrentStrokeWidth();1!=b&&this.node.setAttribute(\"stroke-width\",b);\"path\"==this.node.nodeName&&this.updateStrokeAttributes();a.dashed&&this.node.setAttribute(\"stroke-dasharray\",this.createDashPattern((a.fixDash?\n1:a.strokeWidth)*a.scale))};mxSvgCanvas2D.prototype.updateStrokeAttributes=function(){var a=this.state;null!=a.lineJoin&&\"miter\"!=a.lineJoin&&this.node.setAttribute(\"stroke-linejoin\",a.lineJoin);if(null!=a.lineCap){var b=a.lineCap;\"flat\"==b&&(b=\"butt\");\"butt\"!=b&&this.node.setAttribute(\"stroke-linecap\",b)}null==a.miterLimit||this.styleEnabled&&10==a.miterLimit||this.node.setAttribute(\"stroke-miterlimit\",a.miterLimit)};\nmxSvgCanvas2D.prototype.createDashPattern=function(a){var b=[];if(\"string\"===typeof this.state.dashPattern){var c=this.state.dashPattern.split(\" \");if(0m.alpha||1>m.fillAlpha)&&n.setAttribute(\"opacity\",m.alpha*m.fillAlpha);e=this.state.transform||\"\";if(g||k){var p=f=1,q=0,r=0;g&&(f=-1,q=-c-2*a);k&&(p=-1,r=-d-2*b);e+=\"scale(\"+f+\",\"+p+\")translate(\"+q*m.scale+\",\"+r*m.scale+\")\"}0\",5)+1)),\"\"==a.substring(a.length-7,a.length)&&(a=a.substring(0,a.length-7)))}else{if(null!=document.implementation&&null!=document.implementation.createDocument){b=document.implementation.createDocument(\"http://www.w3.org/1999/xhtml\",\"html\",null);var c=b.createElement(\"body\");\nb.documentElement.appendChild(c);var d=document.createElement(\"div\");d.innerHTML=a;for(a=d.firstChild;null!=a;)d=a.nextSibling,c.appendChild(b.adoptNode(a)),a=d;return c.innerHTML}b=document.createElement(\"textarea\");b.innerHTML=a.replace(/&/g,\"&amp;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(/</g,\"&lt;\").replace(/>/g,\"&gt;\").replace(//g,\">\");a=b.value.replace(/&/g,\"&\").replace(/&lt;/g,\"<\").replace(/&gt;/g,\">\").replace(/&amp;/g,\n\"&\").replace(/
/g,\"
\").replace(/
/g,\"
\").replace(/(]+)>/gm,\"$1 />\")}return a};\nmxSvgCanvas2D.prototype.createDiv=function(a){mxUtils.isNode(a)||(a=\"
\"+this.convertHtml(a)+\"
\");if(document.createElementNS){var b=document.createElementNS(\"http://www.w3.org/1999/xhtml\",\"div\");if(mxUtils.isNode(a)){var c=document.createElement(\"div\"),d=c.cloneNode(!1);this.root.ownerDocument!=document?c.appendChild(a.cloneNode(!0)):c.appendChild(a);d.appendChild(c);b.appendChild(d)}else b.innerHTML=a;return b}mxUtils.isNode(a)&&(a=\"
\"+mxUtils.getXml(a)+\"
\");\nreturn mxUtils.parseXml('
'+a+\"
\").documentElement};\nmxSvgCanvas2D.prototype.updateText=function(a,b,c,d,e,f,g,k,l,m,n,p){if(null!=p&&null!=p.firstChild)if(null!=p.firstChild.firstChild&&\"foreignObject\"==p.firstChild.firstChild.nodeName)this.updateTextNodes(a,b,c,d,e,f,g,k,l,m,n,p.firstChild);else if(\"g\"==p.nodeName){p=p.firstChild;var q=p.getAttribute(\"data-blockTextHeight\");if(null!=q){var r=p.getAttribute(\"data-htmlContentWidth\");r=null!=r?parseFloat(r):null;this.plainText(a,b,c,d,\"\",e,f,g,k,l,m,n,null,p,parseFloat(q),null==r||isNaN(r)?null:r)}else this.plainText(a,\nb,c,d,\"\",e,f,g,k,l,m,n,null,p)}};\nmxSvgCanvas2D.prototype.addForeignObject=function(a,b,c,d,e,f,g,k,l,m,n,p,q,r,t){var u=this.addTitle(this.createElement(\"g\")),v=this.createElement(\"foreignObject\");this.setCssText(v,\"overflow: visible; text-align: left;\");v.setAttribute(\"pointer-events\",\"none\");r.ownerDocument!=document&&(r=mxUtils.importNodeImplementation(v.ownerDocument,r,!0));v.appendChild(r);u.appendChild(v);this.updateTextNodes(a,b,c,d,f,g,k,m,n,p,q,u);this.root.ownerDocument!=document&&(a=this.createAlternateContent(v,a,b,c,\nd,e,f,g,k,l,m,n,p),null!=a&&(v.setAttribute(\"requiredFeatures\",\"http://www.w3.org/TR/SVG11/feature#Extensibility\"),b=this.createElement(\"switch\"),b.appendChild(v),b.appendChild(a),u.appendChild(b)));t.appendChild(u)};\nmxSvgCanvas2D.prototype.updateTextNodes=function(a,b,c,d,e,f,g,k,l,m,n,p){var q=this.state.scale,r=\"\",t=\"\";null!=n&&\"vertical-\"==n.substring(0,9)?(r=\"-rl\"==n.substring(n.length-3),t=e==mxConstants.ALIGN_LEFT?r?\"flex-end\":\"flex-start\":e==mxConstants.ALIGN_RIGHT?r?\"flex-start\":\"flex-end\":\"center\",r=f==mxConstants.ALIGN_TOP?\"flex-start\":f==mxConstants.ALIGN_BOTTOM?\"flex-end\":\"center\"):(t=f==mxConstants.ALIGN_TOP?\"flex-start\":f==mxConstants.ALIGN_BOTTOM?\"flex-end\":\"center\",r=e==mxConstants.ALIGN_LEFT?\n\"flex-start\":e==mxConstants.ALIGN_RIGHT?\"flex-end\":\"center\");var u=null!=this.state.fontBackgroundColor?this.getLightDarkColor(this.state.fontBackgroundColor):null,v=null!=this.state.fontBorderColor?this.getLightDarkColor(this.state.fontBorderColor):null;mxSvgCanvas2D.createCss(c+this.foreignObjectPadding,d,e,f,g,k,l,n,null!=u?u.cssText:null,null!=v?v.cssText:null,\"display: flex; align-items: unsafe \"+t+\"; justify-content: unsafe \"+r+\"; \"+(null!=n&&\"vertical-\"==n.substring(0,9)?\"writing-mode: \"+n+\n\";\":\"\"),this.getTextCss(),q,mxUtils.bind(this,function(y,A,z,x,C){a+=this.state.dx;b+=this.state.dy;var B=p.firstChild;\"title\"==B.nodeName&&(B=B.nextSibling);x+=\"color: \"+this.getLightDarkColor(this.state.fontColor).light+\"; \";x+=null!=u?\"background-color: \"+u.light+\"; \":\"\";x+=null!=v?\"border-color: \"+v.light+\"; \":\"\";var E=B.firstChild,D=E.firstChild,F=(this.rotateHtml?this.state.rotation:0)+(null!=m?m:0),G=(0!=this.foOffset?\"translate(\"+this.foOffset+\" \"+this.foOffset+\")\":\"\")+(1!=q?\"scale(\"+q+\")\":\n\"\");this.setCssText(D.firstChild,C);this.setCssText(D,x);B.setAttribute(\"width\",Math.ceil(1/Math.min(1,q)*100)+\"%\");B.setAttribute(\"height\",Math.ceil(1/Math.min(1,q)*100)+\"%\");A=Math.round(b+A);0>A?(B.setAttribute(\"y\",A),z+=\"padding-top: 0; \"):(B.removeAttribute(\"y\"),z+=\"padding-top: \"+A+\"px; \");this.setCssText(E,z+\"margin-left: \"+Math.round(a+y)+\"px;\");G+=0!=F?\"rotate(\"+F+\" \"+a+\" \"+b+\")\":\"\";\"\"!=G?p.setAttribute(\"transform\",G):p.removeAttribute(\"transform\");1!=this.state.alpha?p.setAttribute(\"opacity\",\nthis.state.alpha):p.removeAttribute(\"opacity\")}))};\nmxSvgCanvas2D.createCss=function(a,b,c,d,e,f,g,k,l,m,n,p,q,r){k=null!=k&&\"vertical-\"==k.substring(0,9);q=\"box-sizing: border-box; font-size: 0; \";q=k?q+(\"text-align: \"+(d==mxConstants.ALIGN_TOP?\"left\":d==mxConstants.ALIGN_BOTTOM?\"right\":\"center\")+\"; \"):q+(\"text-align: \"+(c==mxConstants.ALIGN_LEFT?\"left\":c==mxConstants.ALIGN_RIGHT?\"right\":\"center\")+\"; \");var t=mxUtils.getAlignmentAsPoint(c,d);c=\"overflow: hidden; \";var u=\"width: 1px; \",v=\"height: 1px; \",y=t.x*a;t=t.y*b;g?(u=\"width: \"+Math.round(a)+\n\"px; \",q+=\"max-height: \"+Math.round(b)+\"px; \",t=0):\"fill\"==f?(u=\"width: \"+Math.round(a)+\"px; \",v=\"height: \"+Math.round(b)+\"px; \",p+=\"width: 100%; height: 100%; \",q+=\"width: \"+Math.round(a-2)+\"px; \"+v):\"width\"==f?(u=\"width: \"+Math.round(a-2)+\"px; \",p+=\"width: 100%; \",q+=u,t=0,0c.subDyPx)c.subDyPx=n,c.subFontSize=q}else\"I\"==m.nodeName?l.setAttribute(\"font-style\",\"italic\"):\"B\"==m.nodeName?l.setAttribute(\"font-weight\",\"bold\"):\"STRIKE\"==m.nodeName?l.setAttribute(\"text-decoration\",\"line-through\"):\"U\"==m.nodeName&&l.setAttribute(\"text-decoration\",\"underline\");\"\"!=f&&\"smaller\"!=f&&(\"px\"==f.slice(-2)?q=parseFloat(f):\"em\"==f.slice(-2)?\nq=parseFloat(f)*e:\"%\"==f.slice(-1)&&(q=parseFloat(f)/100*e));\"px\"==f.slice(-2)&&(l.style.fontSize=parseFloat(f)*p/this.state.fontSize+\"em\");f=this.convertHtmlInlineToSvg(m,l,c,p,q)}m=n;0==g&&0==m||l.setAttribute(\"dy\",Math.round(100*(m-g))/100);g=m;b.appendChild(l)}}return f};\nmxSvgCanvas2D.prototype.getSupSubLineExpansion=function(a,b,c,d,e){var f=0;null!=b&&0>b&&(f=Math.max(0,(d||a/1.2)-a-b-a*(mxConstants.LINE_HEIGHT-1)/2));null!=c&&0b&&0=g.length)return null;b=[];for(a=q=0;ax&&(x=u,E=e(v[r].segIdx)));0>z&&(A+=this.getSupSubLineExpansion(y,z,null,C,null));0u.y&&\ng==mxConstants.ALIGN_TOP?t.style.transform=\"translateY(\"+-u.y+\"em)\":0m)d=m;if(null==f||fm)e=m;if(null==g||gk.alpha&&r.setAttribute(\"opacity\",k.alpha);y=e.split(\"\\n\");var A=Math.round(v*mxConstants.LINE_HEIGHT),z=null!=t?t:v+(y.length-1)*A;n=null!=t?b:b+v-1;if(g==mxConstants.ALIGN_MIDDLE)if(\"fill\"==l)n-=d/2;else{var x=(this.matchHtmlAlignment&&m&&0d){var k=this.format((b+(g==mxConstants.ALIGN_CENTER?(f-d)/2:f-d))*e)+this.textOffset,l=this.format(c*e)+this.textOffset;a.setAttribute(\"transform\",\"translate(\"+k+\",\"+l+\") scale(\"+e+\")\")}}catch(m){}};\nmxSvgCanvas2D.prototype.updateFont=function(a){var b=this.state,c=this.getLightDarkColor(b.fontColor);a.setAttribute(\"fill\",c.light);a.style.fill=c.cssText;this.styleEnabled&&b.fontFamily==mxConstants.DEFAULT_FONTFAMILY||a.setAttribute(\"font-family\",mxUtils.parseCssFontFamily(b.fontFamily));(b.fontStyle&mxConstants.FONT_BOLD)==mxConstants.FONT_BOLD&&a.setAttribute(\"font-weight\",\"bold\");(b.fontStyle&mxConstants.FONT_ITALIC)==mxConstants.FONT_ITALIC&&a.setAttribute(\"font-style\",\"italic\");c=[];(b.fontStyle&\nmxConstants.FONT_UNDERLINE)==mxConstants.FONT_UNDERLINE&&c.push(\"underline\");(b.fontStyle&mxConstants.FONT_STRIKETHROUGH)==mxConstants.FONT_STRIKETHROUGH&&c.push(\"line-through\");0\"),m.fontSize,m.fontFamily,null,m.fontStyle),e=n.width,f=n.height,g==mxConstants.ALIGN_CENTER?c-=e/2:g==mxConstants.ALIGN_RIGHT&&(c-=e),k==mxConstants.ALIGN_MIDDLE?d-=f/2:k==mxConstants.ALIGN_BOTTOM&&(d-=f),n=new mxRectangle((c+1)*m.scale,(d+2)*m.scale,e*m.scale,(f+1)*m.scale);null!=n&&(c=this.createElement(\"rect\"),d=null!=m.fontBackgroundColor&&m.fontBackgroundColor!=\nmxConstants.NONE?this.getLightDarkColor(m.fontBackgroundColor):null,e=null!=m.fontBorderColor&&m.fontBorderColor!=mxConstants.NONE?this.getLightDarkColor(m.fontBorderColor):null,c.setAttribute(\"fill\",null!=d?d.light:\"none\"),c.setAttribute(\"stroke\",null!=e?e.light:\"none\"),c.setAttribute(\"x\",Math.floor(n.x-1)),c.setAttribute(\"y\",Math.floor(n.y-1)),c.setAttribute(\"width\",Math.ceil(n.width+2)),c.setAttribute(\"height\",Math.ceil(n.height)),null!=d&&(c.style.fill=d.cssText),e&&(c.style.stroke=e.cssText),\nm=null!=m.fontBorderColor?Math.max(1,this.format(m.scale)):0,c.setAttribute(\"stroke-width\",m),this.root.ownerDocument==document&&1==mxUtils.mod(m,2)&&c.setAttribute(\"transform\",\"translate(0.5, 0.5)\"),a.insertBefore(c,a.firstChild))}};mxSvgCanvas2D.prototype.stroke=function(){this.addNode(!1,!0)};mxSvgCanvas2D.prototype.fill=function(){this.addNode(!0,!1)};mxSvgCanvas2D.prototype.fillAndStroke=function(){this.addNode(!0,!0)};function mxGuide(a,b){this.graph=a;this.setStates(b)}\nmxGuide.prototype.graph=null;mxGuide.prototype.states=null;mxGuide.prototype.horizontal=!0;mxGuide.prototype.vertical=!0;mxGuide.prototype.guideX=null;mxGuide.prototype.guideY=null;mxGuide.prototype.rounded=!1;mxGuide.prototype.tolerance=2;mxGuide.prototype.setStates=function(a){this.states=a};mxGuide.prototype.isEnabledForEvent=function(a){return!0};mxGuide.prototype.getGuideTolerance=function(a){return a&&this.graph.gridEnabled?this.graph.gridSize/2:this.tolerance};\nmxGuide.prototype.createGuideShape=function(a){a=new mxPolyline([],mxConstants.GUIDE_COLOR,mxConstants.GUIDE_STROKEWIDTH);a.isDashed=!0;return a};mxGuide.prototype.isStateIgnored=function(a){return!1};\nmxGuide.prototype.move=function(a,b,c,d){if(null!=this.states&&(this.horizontal||this.vertical)&&null!=a&&null!=b){d=function(C,B,E){var D=!1;E&&Math.abs(C-x)this.opacity&&(b+=\"alpha(opacity=\"+this.opacity+\")\");this.isShadow&&(b+=\"progid:DXImageTransform.Microsoft.dropShadow (OffX='\"+Math.round(mxConstants.SHADOW_OFFSET_X*this.scale)+\"', OffY='\"+Math.round(mxConstants.SHADOW_OFFSET_Y*this.scale)+\"', Color='\"+mxConstants.VML_SHADOWCOLOR+\"')\");if(null!=this.fill&&this.fill!=mxConstants.NONE&&this.gradient&&this.gradient!=mxConstants.NONE){var c=this.fill,d=this.gradient,e=\"0\",f={east:0,south:1,\nwest:2,north:3},g=null!=this.direction?f[this.direction]:0;null!=this.gradientDirection&&(g=mxUtils.mod(g+f[this.gradientDirection]-1,4));1==g?(e=\"1\",f=c,c=d,d=f):2==g?(f=c,c=d,d=f):3==g&&(e=\"1\");b+=\"progid:DXImageTransform.Microsoft.gradient(startColorStr='\"+c+\"', endColorStr='\"+d+\"', gradientType='\"+e+\"')\"}a.style.filter=b};\nmxShape.prototype.updateHtmlColors=function(a){var b=this.stroke;null!=b&&b!=mxConstants.NONE?(a.style.borderColor=mxUtils.getLightDarkColor(b).cssText,this.isDashed?a.style.borderStyle=\"dashed\":0=d||Math.abs(e.y-g.y)>=d)&&b.push(new mxPoint(g.x/c,g.y/c));e=g}}return b};\nmxShape.prototype.configureCanvas=function(a,b,c,d,e){var f=null;null!=this.style&&(f=this.style.dashPattern);a.setAlpha(this.opacity/100);a.setFillAlpha(this.fillOpacity/100);a.setStrokeAlpha(this.strokeOpacity/100);null!=this.isShadow&&a.setShadow(this.isShadow,this.shadowStyle);null!=this.isDashed&&a.setDashed(this.isDashed,null!=this.style?1==mxUtils.getValue(this.style,mxConstants.STYLE_FIX_DASH,!1):!1);null!=f&&a.setDashPattern(f);null!=this.fill&&this.fill!=mxConstants.NONE&&this.gradient&&\nthis.gradient!=mxConstants.NONE?(b=this.getGradientBounds(a,b,c,d,e),a.setGradient(this.fill,this.gradient,b.x,b.y,b.width,b.height,this.gradientDirection)):(a.setFillColor(this.fill),a.setFillStyle(this.fillStyle));null!=this.style&&(null!=this.style.linecap&&a.setLineCap(this.style.linecap),null!=this.style.linejoin&&a.setLineJoin(this.style.linejoin),null!=this.style.miterlimit&&a.setMiterLimit(this.style.miterlimit));a.setStrokeColor(this.stroke);this.configurePointerEvents(a)};\nmxShape.prototype.configurePointerEvents=function(a){null==this.style||mxShape.forceFilledPointerEvents&&null!=this.fill&&this.fill!=mxConstants.NONE&&0!=this.opacity&&0!=this.fillOpacity||\"0\"!=mxUtils.getValue(this.style,mxConstants.STYLE_POINTER_EVENTS,\"1\")||(a.pointerEvents=!1)};mxShape.prototype.getGradientBounds=function(a,b,c,d,e){return new mxRectangle(b,c,d,e)};\nmxShape.prototype.updateTransform=function(a,b,c,d,e){a.scale(this.scale);a.rotate(this.getShapeRotation(),this.flipH,this.flipV,b+d/2,c+e/2)};mxShape.prototype.paintVertexShape=function(a,b,c,d,e){this.paintBackground(a,b,c,d,e);this.outline&&null!=this.style&&0!=mxUtils.getValue(this.style,mxConstants.STYLE_BACKGROUND_OUTLINE,0)||(a.setShadow(!1),this.paintForeground(a,b,c,d,e))};mxShape.prototype.paintBackground=function(a,b,c,d,e){};mxShape.prototype.paintForeground=function(a,b,c,d,e){};\nmxShape.prototype.paintEdgeShape=function(a,b){};mxShape.prototype.getArcSize=function(a,b){if(\"1\"==mxUtils.getValue(this.style,mxConstants.STYLE_ABSOLUTE_ARCSIZE,0))a=Math.min(a/2,Math.min(b/2,mxUtils.getValue(this.style,mxConstants.STYLE_ARCSIZE,mxConstants.LINE_ARCSIZE)/2));else{var c=mxUtils.getValue(this.style,mxConstants.STYLE_ARCSIZE,100*mxConstants.RECTANGLE_ROUNDING_FACTOR)/100;a=Math.min(a*c,b*c)}return a};\nmxShape.prototype.paintGlassEffect=function(a,b,c,d,e,f){var g=Math.ceil(this.strokewidth/2);a.setGradient(\"#ffffff\",\"#ffffff\",b,c,d,.6*e,\"south\",.9,.1);a.begin();f+=2*g;this.isRounded?(a.moveTo(b-g+f,c-g),a.quadTo(b-g,c-g,b-g,c-g+f),a.lineTo(b-g,c+.4*e),a.quadTo(b+.5*d,c+.7*e,b+d+g,c+.4*e),a.lineTo(b+d+g,c-g+f),a.quadTo(b+d+g,c-g,b+d+g-f,c-g)):(a.moveTo(b-g,c-g),a.lineTo(b-g,c+.4*e),a.quadTo(b+.5*d,c+.7*e,b+d+g,c+.4*e),a.lineTo(b+d+g,c-g));a.close();a.fill()};\nmxShape.prototype.addPoints=function(a,b,c,d,e,f,g){if(null!=b&&0mxUtils.indexOf(f,l-1))){var p=Math.sqrt(n*n+m*m);a.lineTo(g.x+n*Math.min(d,p/2)/p,g.y+m*Math.min(d,p/2)/p);for(m=b[mxUtils.mod(l+\n1,b.length)];lb.dx&&(a.x+=b.dx,a.width-=b.dx);0>b.dy&&(a.y+=b.dy,a.height-=b.dy);a.grow(Math.max(b.blur,0)*this.scale*2);a.width+=Math.ceil(Math.max(b.dx,0)*this.scale);a.height+=Math.ceil(Math.max(b.dy,0)*this.scale)}null!=this.stroke&&a.grow(this.strokewidth*this.scale/2)};\nmxShape.prototype.updateBoundingBox=function(){var a=this.useSvgBoundingBox?this.getSvgBoundingBox():null;null==a&&(a=this.getShapeBoundingBox());this.boundingBox=a};mxShape.prototype.isPaintBoundsInverted=function(){return null==this.stencil&&(this.direction==mxConstants.DIRECTION_NORTH||this.direction==mxConstants.DIRECTION_SOUTH)};mxShape.prototype.getRotation=function(){return null!=this.rotation?this.rotation:0};\nmxShape.prototype.getTextRotation=function(){var a=this.getRotation();1!=mxUtils.getValue(this.style,mxConstants.STYLE_HORIZONTAL,1)&&(a+=mxText.prototype.verticalTextRotation);return a};mxShape.prototype.getShapeRotation=function(){var a=this.getRotation();null!=this.direction&&(this.direction==mxConstants.DIRECTION_NORTH?a+=270:this.direction==mxConstants.DIRECTION_WEST?a+=180:this.direction==mxConstants.DIRECTION_SOUTH&&(a+=90));return a};\nmxShape.prototype.createTransparentSvgRectangle=function(a,b,c,d){var e=document.createElementNS(mxConstants.NS_SVG,\"rect\");e.setAttribute(\"x\",a);e.setAttribute(\"y\",b);e.setAttribute(\"width\",c);e.setAttribute(\"height\",d);e.setAttribute(\"fill\",\"none\");e.setAttribute(\"stroke\",\"none\");e.setAttribute(\"pointer-events\",\"all\");return e};mxShape.prototype.setTransparentBackgroundImage=function(a){a.style.backgroundImage=\"url('\"+mxClient.imageBasePath+\"/transparent.gif')\"};\nmxShape.prototype.intersectsRectangle=function(a,b){return null!=a&&(b||null!=this.node&&\"none\"!=this.node.style.display&&\"hidden\"!=this.node.style.visibility)&&mxUtils.intersects(this.bounds,a,!0)};mxShape.prototype.releaseSvgGradients=function(a){if(null!=a)for(var b in a){var c=a[b];null!=c&&(c.mxRefCount=(c.mxRefCount||0)-1,0==c.mxRefCount&&null!=c.parentNode&&c.parentNode.removeChild(c))}};\nmxShape.prototype.releaseSvgFillPatterns=function(a){if(null!=a)for(var b in a){var c=a[b];null!=c&&(c.mxRefCount=(c.mxRefCount||0)-1,0==c.mxRefCount&&null!=c.parentNode&&c.parentNode.removeChild(c))}};\nmxShape.prototype.destroy=function(){null!=this.node&&(mxEvent.release(this.node),null!=this.node.parentNode&&this.node.parentNode.removeChild(this.node),this.node=null);this.releaseSvgGradients(this.oldGradients);this.releaseSvgFillPatterns(this.oldFillPatterns);this.oldFillPatterns=this.oldGradients=null};function mxStencil(a){this.desc=a;this.parseDescription();this.parseConstraints()}mxUtils.extend(mxStencil,mxShape);mxStencil.defaultLocalized=!1;mxStencil.allowEval=!1;\nmxStencil.prototype.desc=null;mxStencil.prototype.constraints=null;mxStencil.prototype.aspect=null;mxStencil.prototype.w0=null;mxStencil.prototype.h0=null;mxStencil.prototype.bgNode=null;mxStencil.prototype.fgNode=null;mxStencil.prototype.strokewidth=null;\nmxStencil.prototype.parseDescription=function(){this.fgNode=this.desc.getElementsByTagName(\"foreground\")[0];this.bgNode=this.desc.getElementsByTagName(\"background\")[0];this.w0=Number(this.desc.getAttribute(\"w\")||100);this.h0=Number(this.desc.getAttribute(\"h\")||100);var a=this.desc.getAttribute(\"aspect\");this.aspect=null!=a?a:\"variable\";a=this.desc.getAttribute(\"strokewidth\");this.strokewidth=null!=a?a:\"1\"};\nmxStencil.prototype.parseConstraints=function(){var a=this.desc.getElementsByTagName(\"connections\")[0];if(null!=a&&(a=mxUtils.getChildNodes(a),null!=a&&0c)){a.begin();a.moveTo(b[0].x,b[0].y);if(2==c)a.lineTo(b[1].x,b[1].y);else if(0==(c-1)%3)for(var d=1;d+2
\"));l=!mxUtils.isNode(this.value)&&this.replaceLinefeeds&&\"html\"==k?l.replace(/\\n/g,\"
\"):l;a.text(d,e,f,g,l,this.align,this.valign,this.wrap,k,this.overflow,this.clipped,this.getTextRotation(),c)}};\nmxText.prototype.redraw=function(){if(this.wrap&&this.convertToSvg&&null!=this.style&&\"wrap\"==this.style.svgWhiteSpace&&null!=this.bounds){var a=this.bounds.width;null!=this.lastWrapWidth&&this.lastWrapWidth!=a&&(this.lastValue=null);this.lastWrapWidth=a}this.visible&&this.checkBounds()&&this.cacheEnabled&&this.lastValue==this.value&&(mxUtils.isNode(this.value)||this.dialect==mxConstants.DIALECT_STRICTHTML)?\"DIV\"==this.node.nodeName?mxClient.IS_SVG?this.redrawHtmlShapeWithCss3():(this.updateSize(this.node),\nthis.updateHtmlTransform()):(a=this.createCanvas(),null!=a&&null!=a.updateText?(a.pointerEvents=this.pointerEvents,this.paint(a,!0),this.destroyCanvas(a)):mxShape.prototype.redraw.apply(this,arguments)):(mxShape.prototype.redraw.apply(this,arguments),mxUtils.isNode(this.value)||this.dialect==mxConstants.DIALECT_STRICTHTML?this.lastValue=this.value:this.lastValue=null)};\nmxText.prototype.resetStyles=function(){mxShape.prototype.resetStyles.apply(this,arguments);this.color=\"black\";this.align=mxConstants.ALIGN_CENTER;this.valign=mxConstants.ALIGN_MIDDLE;this.family=mxConstants.DEFAULT_FONTFAMILY;this.size=mxConstants.DEFAULT_FONTSIZE;this.fontStyle=mxConstants.DEFAULT_FONTSTYLE;this.spacingLeft=this.spacingBottom=this.spacingRight=this.spacingTop=this.spacing=2;this.horizontal=!0;delete this.background;delete this.border;this.textDirection=mxConstants.DEFAULT_TEXT_DIRECTION;\ndelete this.margin};\nmxText.prototype.apply=function(a){var b=this.spacing;mxShape.prototype.apply.apply(this,arguments);null!=this.style&&(this.fontStyle=mxUtils.getValue(this.style,mxConstants.STYLE_FONTSTYLE,this.fontStyle),this.family=mxUtils.getValue(this.style,mxConstants.STYLE_FONTFAMILY,this.family),this.size=mxUtils.getValue(this.style,mxConstants.STYLE_FONTSIZE,this.size),this.color=mxUtils.getValue(this.style,mxConstants.STYLE_FONTCOLOR,this.color),this.align=mxUtils.getValue(this.style,mxConstants.STYLE_ALIGN,\nthis.align),this.valign=mxUtils.getValue(this.style,mxConstants.STYLE_VERTICAL_ALIGN,this.valign),this.spacing=parseInt(mxUtils.getValue(this.style,mxConstants.STYLE_SPACING,this.spacing)),this.spacingTop=parseInt(mxUtils.getValue(this.style,mxConstants.STYLE_SPACING_TOP,this.spacingTop-b))+this.spacing,this.spacingRight=parseInt(mxUtils.getValue(this.style,mxConstants.STYLE_SPACING_RIGHT,this.spacingRight-b))+this.spacing,this.spacingBottom=parseInt(mxUtils.getValue(this.style,mxConstants.STYLE_SPACING_BOTTOM,\nthis.spacingBottom-b))+this.spacing,this.spacingLeft=parseInt(mxUtils.getValue(this.style,mxConstants.STYLE_SPACING_LEFT,this.spacingLeft-b))+this.spacing,this.horizontal=mxUtils.getValue(this.style,mxConstants.STYLE_HORIZONTAL,this.horizontal),this.background=mxUtils.getValue(this.style,mxConstants.STYLE_LABEL_BACKGROUNDCOLOR,this.background),this.border=mxUtils.getValue(this.style,mxConstants.STYLE_LABEL_BORDERCOLOR,this.border),this.textDirection=mxUtils.getValue(this.style,mxConstants.STYLE_TEXT_DIRECTION,\nmxConstants.DEFAULT_TEXT_DIRECTION),this.opacity=mxUtils.getValue(this.style,mxConstants.STYLE_TEXT_OPACITY,100),this.updateMargin());this.flipH=this.flipV=null};mxText.prototype.getAutoDirection=function(){var a=/[A-Za-z\\u05d0-\\u065f\\u066a-\\u06ef\\u06fa-\\u07ff\\ufb1d-\\ufdff\\ufe70-\\ufefc]/.exec(this.value);return null!=a&&0
\");return this.replaceLinefeeds?a.replace(/\\n/g,\"
\"):a};\nmxText.prototype.getTextCss=function(){var a=\"display: inline-block; font-size: \"+this.size+\"px; font-family: \"+this.family+\"; color: \"+this.color+\"; line-height: \"+(mxConstants.ABSOLUTE_LINE_HEIGHT?this.size*mxConstants.LINE_HEIGHT+\"px\":mxConstants.LINE_HEIGHT)+\"; pointer-events: \"+(this.pointerEvents?\"all\":\"none\")+\"; \";(this.fontStyle&mxConstants.FONT_BOLD)==mxConstants.FONT_BOLD&&(a+=\"font-weight: bold; \");(this.fontStyle&mxConstants.FONT_ITALIC)==mxConstants.FONT_ITALIC&&(a+=\"font-style: italic; \");\nvar b=[];(this.fontStyle&mxConstants.FONT_UNDERLINE)==mxConstants.FONT_UNDERLINE&&b.push(\"underline\");(this.fontStyle&mxConstants.FONT_STRIKETHROUGH)==mxConstants.FONT_STRIKETHROUGH&&b.push(\"line-through\");0this.opacity&&(m+=\"opacity: \"+this.opacity/100+\"; \");this.node.setAttribute(\"style\",k);k=mxUtils.isNode(this.value)?this.value.outerHTML:this.getHtmlValue();null==this.node.firstChild&&(this.node.innerHTML=\"
\"+k+\"
\");this.node.firstChild.firstChild.setAttribute(\"style\",m);this.node.firstChild.setAttribute(\"style\",l)}))};\nmxText.prototype.updateHtmlTransform=function(){var a=this.getTextRotation(),b=this.node.style,c=this.margin.x,d=this.margin.y;0!=a?(mxUtils.setPrefixedStyle(b,\"transformOrigin\",100*-c+\"% \"+100*-d+\"%\"),mxUtils.setPrefixedStyle(b,\"transform\",\"translate(\"+100*c+\"%,\"+100*d+\"%) scale(\"+this.scale+\") rotate(\"+a+\"deg)\")):(mxUtils.setPrefixedStyle(b,\"transformOrigin\",\"0% 0%\"),mxUtils.setPrefixedStyle(b,\"transform\",\"scale(\"+this.scale+\") translate(\"+100*c+\"%,\"+100*d+\"%)\"));b.left=Math.round(this.bounds.x-\nMath.ceil(c*(\"fill\"!=this.overflow&&\"width\"!=this.overflow?3:1)))+\"px\";b.top=Math.round(this.bounds.y-d*(\"fill\"!=this.overflow?3:1))+\"px\";b.opacity=100>this.opacity?this.opacity/100:\"\"};\nmxText.prototype.updateInnerHtml=function(a){if(mxUtils.isNode(this.value))a.innerHTML=this.value.outerHTML;else{var b=this.value;this.dialect!=mxConstants.DIALECT_STRICTHTML&&(b=mxUtils.htmlEntities(b,!1));b=mxUtils.replaceTrailingNewlines(b,\"
 
\");b=this.replaceLinefeeds?b.replace(/\\n/g,\"
\"):b;a.innerHTML='
'+b+\"
\"}};\nmxText.prototype.updateValue=function(){if(mxUtils.isNode(this.value))this.node.innerText=\"\",this.node.appendChild(this.value);else{var a=this.value;this.dialect!=mxConstants.DIALECT_STRICTHTML&&(a=mxUtils.htmlEntities(a,!1));a=mxUtils.replaceTrailingNewlines(a,\"

\");a=this.replaceLinefeeds?a.replace(/\\n/g,\"
\"):a;var b=null!=this.background&&this.background!=mxConstants.NONE?this.background:null,c=null!=this.border&&this.border!=mxConstants.NONE?this.border:null;if(\"fill\"==this.overflow||\n\"width\"==this.overflow)null!=b&&(this.node.style.backgroundColor=b),null!=c&&(this.node.style.border=\"1px solid \"+c);else{var d=\"\";null!=b&&(d+=\"background-color:\"+mxUtils.htmlEntities(b)+\";\");null!=c&&(d+=\"border:1px solid \"+mxUtils.htmlEntities(c)+\";\");a='
'+a+\"
\"}this.node.innerHTML=\na;a=this.node.getElementsByTagName(\"div\");0g.indexOf(\"round\")?g=g.replace(\")\",k+\")\"):null==g&&(g=\"inset(0% 0% 0% 0%\"+k+\")\")}a.image(b,c,d,e,this.getImageDataUri(),\nthis.preserveImageAspect,!1,!1,g);null!=this.imageBorder&&(a.setShadow(!1),a.setStrokeColor(this.imageBorder),0this.opacity?\"alpha(opacity=\"+this.opacity+\")\":\"\";this.node.style.filter=b;this.flipH&&this.flipV?b+=\"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)\":this.flipH?b+=\"progid:DXImageTransform.Microsoft.BasicImage(mirror=1)\":this.flipV&&(b+=\"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)\");a.style.filter!=b&&(a.style.filter=b);\"image\"==\na.nodeName?a.style.rotation=this.rotation:0!=this.rotation?mxUtils.setPrefixedStyle(a.style,\"transform\",\"rotate(\"+this.rotation+\"deg)\"):mxUtils.setPrefixedStyle(a.style,\"transform\",\"\");a.style.width=this.node.style.width;a.style.height=this.node.style.height;this.node.style.backgroundImage=\"\";this.node.appendChild(a)}else this.setTransparentBackgroundImage(this.node)};function mxLabel(a,b,c,d){mxRectangleShape.call(this,a,b,c,d)}mxUtils.extend(mxLabel,mxRectangleShape);\nmxLabel.prototype.imageSize=mxConstants.DEFAULT_IMAGESIZE;mxLabel.prototype.spacing=2;mxLabel.prototype.indicatorSize=10;mxLabel.prototype.indicatorSpacing=2;mxLabel.prototype.init=function(a){mxShape.prototype.init.apply(this,arguments);null!=this.indicatorShape&&(this.indicator=new this.indicatorShape,this.indicator.dialect=this.dialect,this.indicator.init(this.node))};\nmxLabel.prototype.redraw=function(){null!=this.indicator&&(this.indicator.fill=this.indicatorColor,this.indicator.stroke=this.indicatorStrokeColor,this.indicator.gradient=this.indicatorGradientColor,this.indicator.direction=this.indicatorDirection,this.indicator.redraw());mxShape.prototype.redraw.apply(this,arguments)};mxLabel.prototype.isHtmlAllowed=function(){return mxRectangleShape.prototype.isHtmlAllowed.apply(this,arguments)&&null==this.indicatorColor&&null==this.indicatorShape};\nmxLabel.prototype.paintForeground=function(a,b,c,d,e){this.paintImage(a,b,c,d,e);this.paintIndicator(a,b,c,d,e);mxRectangleShape.prototype.paintForeground.apply(this,arguments)};mxLabel.prototype.paintImage=function(a,b,c,d,e){null!=this.image&&(b=this.getImageBounds(b,c,d,e),c=mxUtils.getValue(this.style,mxConstants.STYLE_CLIP_PATH,null),a.image(b.x,b.y,b.width,b.height,this.image,!1,!1,!1,c))};\nmxLabel.prototype.getImageBounds=function(a,b,c,d){var e=mxUtils.getValue(this.style,mxConstants.STYLE_IMAGE_ALIGN,mxConstants.ALIGN_LEFT),f=mxUtils.getValue(this.style,mxConstants.STYLE_IMAGE_VERTICAL_ALIGN,mxConstants.ALIGN_MIDDLE),g=mxUtils.getNumber(this.style,mxConstants.STYLE_IMAGE_WIDTH,mxConstants.DEFAULT_IMAGESIZE),k=mxUtils.getNumber(this.style,mxConstants.STYLE_IMAGE_HEIGHT,mxConstants.DEFAULT_IMAGESIZE),l=mxUtils.getNumber(this.style,mxConstants.STYLE_SPACING,this.spacing)+5;a=e==mxConstants.ALIGN_CENTER?\na+(c-g)/2:e==mxConstants.ALIGN_RIGHT?a+(c-g-l):a+l;b=f==mxConstants.ALIGN_TOP?b+l:f==mxConstants.ALIGN_BOTTOM?b+(d-k-l):b+(d-k)/2;return new mxRectangle(a,b,g,k)};mxLabel.prototype.paintIndicator=function(a,b,c,d,e){null!=this.indicator?(this.indicator.bounds=this.getIndicatorBounds(b,c,d,e),this.indicator.paint(a)):null!=this.indicatorImage&&(b=this.getIndicatorBounds(b,c,d,e),a.image(b.x,b.y,b.width,b.height,this.indicatorImage,!1,!1,!1))};\nmxLabel.prototype.getIndicatorBounds=function(a,b,c,d){var e=mxUtils.getValue(this.style,mxConstants.STYLE_IMAGE_ALIGN,mxConstants.ALIGN_LEFT),f=mxUtils.getValue(this.style,mxConstants.STYLE_IMAGE_VERTICAL_ALIGN,mxConstants.ALIGN_MIDDLE),g=mxUtils.getNumber(this.style,mxConstants.STYLE_INDICATOR_WIDTH,this.indicatorSize),k=mxUtils.getNumber(this.style,mxConstants.STYLE_INDICATOR_HEIGHT,this.indicatorSize),l=this.spacing+5;a=e==mxConstants.ALIGN_RIGHT?a+(c-g-l):e==mxConstants.ALIGN_CENTER?a+(c-g)/\n2:a+l;b=f==mxConstants.ALIGN_BOTTOM?b+(d-k-l):f==mxConstants.ALIGN_TOP?b+l:b+(d-k)/2;return new mxRectangle(a,b,g,k)};\nmxLabel.prototype.redrawHtmlShape=function(){for(mxRectangleShape.prototype.redrawHtmlShape.apply(this,arguments);this.node.hasChildNodes();)this.node.removeChild(this.node.lastChild);if(null!=this.image){var a=document.createElement(\"img\");a.style.position=\"relative\";a.setAttribute(\"border\",\"0\");var b=this.getImageBounds(this.bounds.x,this.bounds.y,this.bounds.width,this.bounds.height);b.x-=this.bounds.x;b.y-=this.bounds.y;a.style.left=Math.round(b.x)+\"px\";a.style.top=Math.round(b.y)+\"px\";a.style.width=\nMath.round(b.width)+\"px\";a.style.height=Math.round(b.height)+\"px\";a.src=this.image;this.node.appendChild(a)}};function mxCylinder(a,b,c,d){mxShape.call(this);this.bounds=a;this.fill=b;this.stroke=c;this.strokewidth=null!=d?d:1}mxUtils.extend(mxCylinder,mxShape);mxCylinder.prototype.maxHeight=40;\nmxCylinder.prototype.paintVertexShape=function(a,b,c,d,e){a.translate(b,c);a.begin();this.redrawPath(a,b,c,d,e,!1);a.fillAndStroke();this.outline&&null!=this.style&&0!=mxUtils.getValue(this.style,mxConstants.STYLE_BACKGROUND_OUTLINE,0)||(a.setShadow(!1),a.begin(),this.redrawPath(a,b,c,d,e,!0),a.stroke())};mxCylinder.prototype.getCylinderSize=function(a,b,c,d){return Math.min(this.maxHeight,Math.round(d/5))};\nmxCylinder.prototype.redrawPath=function(a,b,c,d,e,f){b=this.getCylinderSize(b,c,d,e);if(f&&null!=this.fill||!f&&null==this.fill)a.moveTo(0,b),a.curveTo(0,2*b,d,2*b,d,b),f||(a.stroke(),a.begin());f||(a.moveTo(0,b),a.curveTo(0,-b/3,d,-b/3,d,b),a.lineTo(d,e-b),a.curveTo(d,e+b/3,0,e+b/3,0,e-b),a.close())};function mxConnector(a,b,c){mxPolyline.call(this,a,b,c)}mxUtils.extend(mxConnector,mxPolyline);\nmxConnector.prototype.updateBoundingBox=function(){this.useSvgBoundingBox=null!=this.style&&1==this.style[mxConstants.STYLE_CURVED];mxShape.prototype.updateBoundingBox.apply(this,arguments)};\nmxConnector.prototype.paintEdgeShape=function(a,b){var c=this.createMarker(a,b,!0),d=this.createMarker(a,b,!1);mxPolyline.prototype.paintEdgeShape.apply(this,arguments);a.setShadow(!1);a.setDashed(!1);null!=c&&(a.setFillColor(mxUtils.getValue(this.style,mxConstants.STYLE_STARTFILLCOLOR,this.stroke)),c());null!=d&&(a.setFillColor(mxUtils.getValue(this.style,mxConstants.STYLE_ENDFILLCOLOR,this.stroke)),d())};\nmxConnector.prototype.createMarker=function(a,b,c){var d=null,e=b.length,f=mxUtils.getValue(this.style,c?mxConstants.STYLE_STARTARROW:mxConstants.STYLE_ENDARROW),g=c?b[1]:b[e-2];b=c?b[0]:b[e-1];if(null!=f&&null!=g&&null!=b){d=b.x-g.x;e=b.y-g.y;var k=Math.sqrt(d*d+e*e);g=d/k;d=e/k;e=mxUtils.getNumber(this.style,c?mxConstants.STYLE_STARTSIZE:mxConstants.STYLE_ENDSIZE,mxConstants.DEFAULT_MARKERSIZE);d=mxMarker.createMarker(a,this,f,b,g,d,e,c,this.strokewidth,0!=this.style[c?mxConstants.STYLE_STARTFILL:\nmxConstants.STYLE_ENDFILL])}return d};\nmxConnector.prototype.augmentBoundingBox=function(a){mxShape.prototype.augmentBoundingBox.apply(this,arguments);var b=0;mxUtils.getValue(this.style,mxConstants.STYLE_STARTARROW,mxConstants.NONE)!=mxConstants.NONE&&(b=mxUtils.getNumber(this.style,mxConstants.STYLE_STARTSIZE,mxConstants.DEFAULT_MARKERSIZE)+1);mxUtils.getValue(this.style,mxConstants.STYLE_ENDARROW,mxConstants.NONE)!=mxConstants.NONE&&(b=Math.max(b,mxUtils.getNumber(this.style,mxConstants.STYLE_ENDSIZE,mxConstants.DEFAULT_MARKERSIZE))+\n1);a.grow(b*this.scale)};function mxSwimlane(a,b,c,d){mxShape.call(this);this.bounds=a;this.fill=b;this.stroke=c;this.strokewidth=null!=d?d:1}mxUtils.extend(mxSwimlane,mxShape);mxSwimlane.prototype.imageSize=16;mxSwimlane.prototype.fixedHeaderDefault=!0;mxSwimlane.prototype.apply=function(a){mxShape.prototype.apply.apply(this,arguments);null!=this.style&&(this.laneFill=mxUtils.getValue(this.style,mxConstants.STYLE_SWIMLANE_FILLCOLOR,mxConstants.NONE))};mxSwimlane.prototype.isRoundable=function(){return!0};\nmxSwimlane.prototype.getTitleSize=function(){return Math.max(0,mxUtils.getValue(this.style,mxConstants.STYLE_STARTSIZE,mxConstants.DEFAULT_STARTSIZE))};\nmxSwimlane.prototype.getLabelBounds=function(a){var b=this.getTitleSize(),c=mxUtils.getValue(this.style,mxConstants.STYLE_FIXED_HEADER,this.fixedHeaderDefault);if(0==b&&!c)return mxShape.prototype.getLabelBounds.apply(this,arguments);var d=1==mxUtils.getValue(this.style,mxConstants.STYLE_FLIPH,0),e=1==mxUtils.getValue(this.style,mxConstants.STYLE_FLIPV,0);c=new mxRectangle(a.x,a.y,a.width,a.height);var f=this.isHorizontal(),g=this.direction==mxConstants.DIRECTION_NORTH||this.direction==mxConstants.DIRECTION_SOUTH;\nf=f==!g;d=!f&&d!=(this.direction==mxConstants.DIRECTION_SOUTH||this.direction==mxConstants.DIRECTION_WEST);e=f&&e!=(this.direction==mxConstants.DIRECTION_SOUTH||this.direction==mxConstants.DIRECTION_WEST);if(g){b=Math.min(c.width,b*this.scale);if(d||e)c.x+=c.width-b;c.width=b}else{b=Math.min(c.height,b*this.scale);if(d||e)c.y+=c.height-b;c.height=b}return c};\nmxSwimlane.prototype.getGradientBounds=function(a,b,c,d,e){a=this.getTitleSize();return this.isHorizontal()?new mxRectangle(b,c,d,Math.min(a,e)):new mxRectangle(b,c,Math.min(a,d),e)};\nmxSwimlane.prototype.getSwimlaneArcSize=function(a,b,c){if(\"1\"==mxUtils.getValue(this.style,mxConstants.STYLE_ABSOLUTE_ARCSIZE,0))return Math.min(a/2,Math.min(b/2,mxUtils.getValue(this.style,mxConstants.STYLE_ARCSIZE,mxConstants.LINE_ARCSIZE)/2));a=mxUtils.getValue(this.style,mxConstants.STYLE_ARCSIZE,100*mxConstants.RECTANGLE_ROUNDING_FACTOR)/100;return c*a*3};mxSwimlane.prototype.isHorizontal=function(){return 1==mxUtils.getValue(this.style,mxConstants.STYLE_HORIZONTAL,1)};\nmxSwimlane.prototype.paintVertexShape=function(a,b,c,d,e){if(!this.outline){var f=this.getTitleSize(),g=mxUtils.getValue(this.style,mxConstants.STYLE_FIXED_HEADER,this.fixedHeaderDefault);if(0!=f||g){g=0;f=this.isHorizontal()?Math.min(f,e):Math.min(f,d);a.translate(b,c);this.isRounded?(g=this.getSwimlaneArcSize(d,e,f),g=Math.min((this.isHorizontal()?e:d)-f,Math.min(f,g)),this.paintRoundedSwimlane(a,b,c,d,e,f,g)):this.paintSwimlane(a,b,c,d,e,f);var k=mxUtils.getValue(this.style,mxConstants.STYLE_SEPARATORCOLOR,\nmxConstants.NONE);this.paintSeparator(a,b,c,d,e,f,k);null!=this.image&&(e=this.getImageBounds(b,c,d,e),k=mxUtils.getValue(this.style,mxConstants.STYLE_CLIP_PATH,null),a.image(e.x-b,e.y-c,e.width,e.height,this.image,!1,!1,!1,k));this.glass&&(a.setShadow(!1),this.paintGlassEffect(a,0,0,d,f,g))}}};\nmxSwimlane.prototype.configurePointerEvents=function(a){var b=!0,c=!0,d=!0;null!=this.style&&(b=\"1\"==mxUtils.getValue(this.style,mxConstants.STYLE_POINTER_EVENTS,\"1\"),c=1==mxUtils.getValue(this.style,mxConstants.STYLE_SWIMLANE_HEAD,1),d=1==mxUtils.getValue(this.style,mxConstants.STYLE_SWIMLANE_BODY,1));(b||c||d)&&mxShape.prototype.configurePointerEvents.apply(this,arguments)};\nmxSwimlane.prototype.paintSwimlane=function(a,b,c,d,e,f){var g=this.laneFill,k=!0,l=!0,m=!0,n=!0;null!=this.style&&(k=\"1\"==mxUtils.getValue(this.style,mxConstants.STYLE_POINTER_EVENTS,\"1\"),l=1==mxUtils.getValue(this.style,mxConstants.STYLE_SWIMLANE_LINE,1),m=1==mxUtils.getValue(this.style,mxConstants.STYLE_SWIMLANE_HEAD,1),n=1==mxUtils.getValue(this.style,mxConstants.STYLE_SWIMLANE_BODY,1));this.isHorizontal()?(a.begin(),a.moveTo(0,f),a.lineTo(0,0),a.lineTo(d,0),a.lineTo(d,f),m?a.fillAndStroke():\na.fill(),fa.weightedValue?-1:b.weightedValuec)break;g=l}}f=e.getIndex(a);f=Math.max(0,b-(b>f?1:0));d.add(e,a,f)}};\nmxStackLayout.prototype.getParentSize=function(a){var b=this.graph.getModel(),c=b.getGeometry(a);null!=this.graph.container&&(null==c&&b.isLayer(a)||a==this.graph.getView().currentRoot)&&(c=new mxRectangle(0,0,this.graph.container.offsetWidth-1,this.graph.container.offsetHeight-1));return c};\nmxStackLayout.prototype.getLayoutCells=function(a){for(var b=this.graph.getModel(),c=b.getChildCount(a),d=[],e=0;ek.x>0?1:-1:g.y==k.y?0:g.y>k.y>0?1:-1}));return d};\nmxStackLayout.prototype.snap=function(a){if(null!=this.gridSize&&0this.gridSize/2?this.gridSize-b:-b}return a};\nmxStackLayout.prototype.execute=function(a){if(null!=a){var b=this.getParentSize(a),c=this.isHorizontal(),d=this.graph.getModel(),e=null;null!=b&&(e=c?b.height-this.marginTop-this.marginBottom:b.width-this.marginLeft-this.marginRight);e-=2*this.border;var f=this.x0+this.border+this.marginLeft,g=this.y0+this.border+this.marginTop;if(this.graph.isSwimlane(a)){var k=this.graph.getCellStyle(a),l=mxUtils.getNumber(k,mxConstants.STYLE_STARTSIZE,mxConstants.DEFAULT_STARTSIZE);k=1==mxUtils.getValue(k,mxConstants.STYLE_HORIZONTAL,\n!0);null!=b&&(l=k?Math.min(l,b.height):Math.min(l,b.width));c==k&&(e-=l);k?g+=l:f+=l}d.beginUpdate();try{l=0;k=null;for(var m=0,n=null,p=this.getLayoutCells(a),q=0;qthis.wrap||!c&&k.y+k.height+t.height+2*this.spacing>this.wrap)&&(k=null,c?g+=l+this.spacing:f+=l+this.spacing,l=0);l=Math.max(l,c?t.height:t.width);var u=0;if(!this.borderCollapse){var v=this.graph.getCellStyle(r);\nu=mxUtils.getNumber(v,mxConstants.STYLE_STROKEWIDTH,1)}if(null!=k){var y=m+this.spacing+Math.floor(u/2);c?t.x=this.snap((this.allowGaps?Math.max(y,t.x):y)-this.marginLeft)+this.marginLeft:t.y=this.snap((this.allowGaps?Math.max(y,t.y):y)-this.marginTop)+this.marginTop}else this.keepFirstLocation||(c?t.x=this.allowGaps&&t.x>f?Math.max(this.snap(t.x-this.marginLeft)+this.marginLeft,f):f:t.y=this.allowGaps&&t.y>g?Math.max(this.snap(t.y-this.marginTop)+this.marginTop,g):g);c?t.y=g:t.x=f;this.fill&&null!=\ne&&(c?t.height=e:t.width=e);c?t.width=this.snap(t.width):t.height=this.snap(t.height);this.setChildGeometry(r,t);n=r;k=t;m=c?k.x+k.width+Math.floor(u/2):k.y+k.height+Math.floor(u/2)}}this.resizeParent&&null!=b&&null!=k&&!this.graph.isCellCollapsed(a)?this.updateParentGeometry(a,b,k):this.resizeLast&&null!=b&&null!=k&&null!=n&&(c?k.width=b.width-k.x-this.spacing-this.marginRight-this.marginLeft:k.height=b.height-k.y-this.spacing-this.marginBottom,this.setChildGeometry(n,k))}finally{d.endUpdate()}}};\nmxStackLayout.prototype.setChildGeometry=function(a,b){var c=this.graph.getCellGeometry(a);null!=c&&b.x==c.x&&b.y==c.y&&b.width==c.width&&b.height==c.height||this.graph.getModel().setGeometry(a,b)};\nmxStackLayout.prototype.updateParentGeometry=function(a,b,c){var d=this.isHorizontal(),e=this.graph.getModel(),f=b.clone();d?(c=c.x+c.width+this.marginRight+this.border,f.width=this.resizeParentMax?Math.max(f.width,c):c):(c=c.y+c.height+this.marginBottom+this.border,f.height=this.resizeParentMax?Math.max(f.height,c):c);b.x==f.x&&b.y==f.y&&b.width==f.width&&b.height==f.height||e.setGeometry(a,f)};\nfunction mxPartitionLayout(a,b,c,d){mxGraphLayout.call(this,a);this.horizontal=null!=b?b:!0;this.spacing=c||0;this.border=d||0}mxPartitionLayout.prototype=new mxGraphLayout;mxPartitionLayout.prototype.constructor=mxPartitionLayout;mxPartitionLayout.prototype.horizontal=null;mxPartitionLayout.prototype.spacing=null;mxPartitionLayout.prototype.border=null;mxPartitionLayout.prototype.resizeVertices=!0;mxPartitionLayout.prototype.isHorizontal=function(){return this.horizontal};\nmxPartitionLayout.prototype.moveCell=function(a,b,c){c=this.graph.getModel();var d=c.getParent(a);if(null!=a&&null!=d){var e,f=0,g=c.getChildCount(d);for(e=0;eb)break;f=k}}b=d.getIndex(a);b=Math.max(0,e-(e>b?1:0));c.add(d,a,b)}};\nmxPartitionLayout.prototype.execute=function(a){var b=this.isHorizontal(),c=this.graph.getModel(),d=c.getGeometry(a);null!=this.graph.container&&(null==d&&c.isLayer(a)||a==this.graph.getView().currentRoot)&&(d=new mxRectangle(0,0,this.graph.container.offsetWidth-1,this.graph.container.offsetHeight-1));if(null!=d){for(var e=[],f=c.getChildCount(a),g=0;gg.x&&(d=Math.abs(f-g.x));0>g.y&&(k=Math.abs(b-g.y));0==d&&0==k||this.moveNode(this.node,d,k);this.resizeParent&&this.adjustParents();this.edgeRouting&&this.localEdgeProcessing(this.node)}null!=this.parentX&&null!=this.parentY&&(e=this.graph.getCellGeometry(a),null!=e&&(e=e.clone(),e.x=this.parentX,e.y=this.parentY,c.setGeometry(a,e)))}}finally{c.endUpdate()}}};\nmxCompactTreeLayout.prototype.moveNode=function(a,b,c){a.x+=b;a.y+=c;this.apply(a);for(a=a.child;null!=a;)this.moveNode(a,b,c),a=a.next};\nmxCompactTreeLayout.prototype.sortOutgoingEdges=function(a,b){var c=new mxDictionary;b.sort(function(d,e){var f=d.getTerminal(d.getTerminal(!1)==a);d=c.get(f);null==d&&(d=mxCellPath.create(f).split(mxCellPath.PATH_SEPARATOR),c.put(f,d));e=e.getTerminal(e.getTerminal(!1)==a);f=c.get(e);null==f&&(f=mxCellPath.create(e).split(mxCellPath.PATH_SEPARATOR),c.put(e,f));return mxCellPath.compare(d,f)})};\nmxCompactTreeLayout.prototype.findRankHeights=function(a,b){if(null==this.maxRankHeight[b]||this.maxRankHeight[b]a.height&&(a.height=this.maxRankHeight[b]);for(a=a.child;null!=a;)this.setCellHeights(a,b+1),a=a.next};\nmxCompactTreeLayout.prototype.dfs=function(a,b){var c=mxCellPath.create(a),d=null;if(null!=a&&null==this.visited[c]&&!this.isVertexIgnored(a)){this.visited[c]=a;d=this.createNode(a);c=this.graph.getModel();var e=null,f=this.graph.getEdges(a,b,this.invert,!this.invert,!1,!0),g=this.graph.getView();this.sortEdges&&this.sortOutgoingEdges(a,f);for(a=0;a=a+c)return 0;a=0a?a*d/c-b:0a+c?(c+a)*f/e-(b+d):f-(b+d);return 0g+2*this.prefHozEdgeSep&&(f-=2*this.prefHozEdgeSep);a=f/d;b=a/2;f>g+2*this.prefHozEdgeSep&&(b+=this.prefHozEdgeSep);f=this.minEdgeJetty-this.prefVertEdgeOff;g=this.getVertexBounds(c);for(var k=0;kd/2&&(f-=this.prefVertEdgeOff);b+=a}};\nfunction mxRadialTreeLayout(a){mxCompactTreeLayout.call(this,a,!1)}mxUtils.extend(mxRadialTreeLayout,mxCompactTreeLayout);mxRadialTreeLayout.prototype.angleOffset=.5;mxRadialTreeLayout.prototype.rootx=0;mxRadialTreeLayout.prototype.rooty=0;mxRadialTreeLayout.prototype.levelDistance=120;mxRadialTreeLayout.prototype.nodeDistance=10;mxRadialTreeLayout.prototype.autoRadius=!1;mxRadialTreeLayout.prototype.sortEdges=!1;mxRadialTreeLayout.prototype.rowMinX=[];mxRadialTreeLayout.prototype.rowMaxX=[];\nmxRadialTreeLayout.prototype.rowMinCenX=[];mxRadialTreeLayout.prototype.rowMaxCenX=[];mxRadialTreeLayout.prototype.rowRadi=[];mxRadialTreeLayout.prototype.row=[];mxRadialTreeLayout.prototype.isVertexIgnored=function(a){return mxGraphLayout.prototype.isVertexIgnored.apply(this,arguments)||0==this.graph.getConnections(a).length};\nmxRadialTreeLayout.prototype.execute=function(a,b){this.parent=a;this.edgeRouting=this.useBoundingBox=!1;mxCompactTreeLayout.prototype.execute.apply(this,arguments);var c=null,d=this.getVertexBounds(this.root);this.centerX=d.x+d.width/2;this.centerY=d.y+d.height/2;for(var e in this.visited){var f=this.getVertexBounds(this.visited[e]);c=null!=c?c:f.clone();c.add(f)}this.calcRowDims([this.node],0);var g=0,k=0;for(c=0;c\nd.theta&&ethis.forceConstant&&(this.forceConstant=\n.001);this.forceConstantSquared=this.forceConstant*this.forceConstant;for(d=0;db&&(b=.001);var c=this.dispX[a]/b*Math.min(b,this.temperature);b=this.dispY[a]/b*Math.min(b,this.temperature);this.dispX[a]=0;this.dispY[a]=0;this.cellLocation[a][0]+=c;this.cellLocation[a][1]+=b}};\nmxFastOrganicLayout.prototype.calcAttraction=function(){for(var a=0;athis.maxDistanceLimit||(gb?b+\"-\"+c:c+\"-\"+b)+d}return null};\nmxParallelEdgeLayout.prototype.layout=function(a){var b=a[0],c=this.graph.view.getState(b);if(null!=c&&null!=c.absolutePoints){var d=c.absolutePoints[0];c=c.absolutePoints[c.absolutePoints.length-1];if(null!=d&&null!=c){var e=this.graph.getView(),f=this.graph.getModel(),g=f.getGeometry(e.getVisibleTerminal(b,!0));b=f.getGeometry(e.getVisibleTerminal(b,!1));if(g==b)for(c=g.x+g.width+this.spacing,d=g.y+g.height/2,g=0;gmxUtils.indexOf(l.connectsAsTarget,g)&&l.connectsAsTarget.push(g))}}c[d].temp[0]=1}}mxGraphHierarchyModel.prototype.maxRank=null;mxGraphHierarchyModel.prototype.vertexMapper=null;mxGraphHierarchyModel.prototype.edgeMapper=null;mxGraphHierarchyModel.prototype.ranks=null;mxGraphHierarchyModel.prototype.roots=null;mxGraphHierarchyModel.prototype.parent=null;\nmxGraphHierarchyModel.prototype.dfsCount=0;mxGraphHierarchyModel.prototype.SOURCESCANSTARTRANK=1E8;mxGraphHierarchyModel.prototype.tightenToSource=!1;\nmxGraphHierarchyModel.prototype.createInternalCells=function(a,b,c){for(var d=a.getGraph(),e=0;e=l.length){k=\nnew mxGraphHierarchyEdge(l);for(var m=0;mmxUtils.indexOf(c[e].connectsAsSource,k)&&c[e].connectsAsSource.push(k)}}}c[e].temp[0]=0}};\nmxGraphHierarchyModel.prototype.initialRank=function(){var a=[];if(null!=this.roots)for(var b=0;bg.maxRank&&0>g.minRank&&(a[g.temp[0]].push(g),g.maxRank=g.temp[0],g.minRank=g.temp[0],g.temp[0]=a[g.maxRank].length-1);if(null!=f&&null!=k&&1mxUtils.indexOf(l.connectsAsTarget,g)&&l.connectsAsTarget.push(g))}}c[d].temp[0]=1}}mxSwimlaneModel.prototype.maxRank=null;mxSwimlaneModel.prototype.vertexMapper=null;mxSwimlaneModel.prototype.edgeMapper=null;mxSwimlaneModel.prototype.ranks=null;mxSwimlaneModel.prototype.roots=null;mxSwimlaneModel.prototype.parent=null;mxSwimlaneModel.prototype.dfsCount=0;\nmxSwimlaneModel.prototype.SOURCESCANSTARTRANK=1E8;mxSwimlaneModel.prototype.tightenToSource=!1;mxSwimlaneModel.prototype.ranksPerGroup=null;\nmxSwimlaneModel.prototype.createInternalCells=function(a,b,c){for(var d=a.getGraph(),e=a.swimlanes,f=0;f=m.length){l=new mxGraphHierarchyEdge(m);for(var n=0;nmxUtils.indexOf(c[f].connectsAsSource,l)&&c[f].connectsAsSource.push(l)}}}c[f].temp[0]=0}};\nmxSwimlaneModel.prototype.initialRank=function(){this.ranksPerGroup=[];var a=[],b={};if(null!=this.roots)for(var c=0;cb[d.swimlaneIndex]&&(k=b[d.swimlaneIndex]);d.temp[0]=k;if(null!=f)for(c=0;cg.maxRank&&0>g.minRank&&(a[g.temp[0]].push(g),g.maxRank=g.temp[0],g.minRank=g.temp[0],g.temp[0]=a[g.maxRank].length-1);if(null!=f&&null!=k&&1>1,++e[f];return c};\nmxMedianHybridCrossingReduction.prototype.transpose=function(a,b){for(var c=!0,d=0;c&&10>d++;){var e=1==a%2&&1==d%2;c=!1;for(var f=0;fn&&(n=l);k[n]=m}var p=null,q=null,r=null,t=null,u=null;for(l=0;lr[B]&&x++,y[z]t[B]&&x++,A[z]a.medianValue?-1:b.medianValuev+1&&(m==d[l].length-1?(e.setGeneralPurposeVariable(l,y),p=!0):(m=d[l][m+1],v=m.getGeneralPurposeVariable(l),v=v-m.width/2-this.intraCellSpacing-e.width/2,v>y?(e.setGeneralPurposeVariable(l,\ny),p=!0):v>e.getGeneralPurposeVariable(l)+1&&(e.setGeneralPurposeVariable(l,v),p=!0)));if(p){for(e=0;e=k&&l<=q?g.setGeneralPurposeVariable(a,l):lq&&(g.setGeneralPurposeVariable(a,q),this.currentXDelta+=l-q);d[f].visited=!0}};mxCoordinateAssignment.prototype.calculatedWeightedValue=function(a,b){for(var c=0,d=0;dthis.widestRankValue&&(this.widestRankValue=g,this.widestRank=d);this.rankWidths[d]=g}1==k&&mxLog.warn(\"At least one cell has no bounds\");this.rankY[d]=a;g=e/2+c/2+this.interRankCellSpacing;c=e;a=this.orientation==mxConstants.DIRECTION_NORTH||this.orientation==mxConstants.DIRECTION_WEST?a+g:a-\ng;for(l=0;ld.maxRank-d.minRank-1)){for(var e=d.getGeneralPurposeVariable(d.minRank+1),f=!0,g=0,k=d.minRank+2;kd.minRank+1;k--)p=d.getX(k-1),n==p?(m[k-d.minRank-2]=n,f++):this.repositionValid(b,d,k-1,n)?(m[k-d.minRank-2]=n,f++):(m[k-d.minRank-2]=d.getX(k-1),n=p);if(f>g||e>g)if(f>=e)for(k=d.maxRank-2;k>d.minRank;k--)d.setX(k,m[k-d.minRank-1]);else if(e>f)for(k=d.minRank+2;ke)return!1;f=b.getGeneralPurposeVariable(c);if(df){if(e==a.length-1)return!0;a=a[e+1];c=a.getGeneralPurposeVariable(c);c=c-a.width/2-this.intraCellSpacing-b.width/2;if(!(c>=d))return!1}return!0};\nmxCoordinateAssignment.prototype.setCellLocations=function(a,b){this.rankTopY=[];this.rankBottomY=[];for(a=0;ak;k++){if(-1(f+1)*this.prefHozEdgeSep+2*this.prefHozEdgeSep&&(n+=this.prefHozEdgeSep,p-=this.prefHozEdgeSep);l=(p-n)/f;n+=l/2;p=this.minEdgeJetty-this.prefVertEdgeOff;for(m=0;mf/2&&(p-=this.prefVertEdgeOff),t=0;te&&(e=k,d=g)}}0==c.length&&null!=d&&c.push(d)}return c};\nmxHierarchicalLayout.prototype.getEdges=function(a){var b=this.edgesCache.get(a);if(null!=b)return b;var c=this.graph.model;b=[];for(var d=this.graph.isCellCollapsed(a),e=c.getChildCount(a),f=0;fb.length)){null==a&&(a=c.getParent(b[0]));this.parentY=this.parentX=null;if(a!=this.root&&null!=c.isVertex(a)&&this.maintainParentLocation){var d=this.graph.getCellGeometry(a);null!=d&&(this.parentX=d.x,this.parentY=d.y)}this.swimlanes=b;for(var e=[],f=0;ff&&(f=l,e=k)}}0==c.length&&null!=e&&c.push(e)}return c};\nmxSwimlaneLayout.prototype.getEdges=function(a){var b=this.edgesCache.get(a);if(null!=b)return b;var c=this.graph.model;b=[];for(var d=this.graph.isCellCollapsed(a),e=c.getChildCount(a),f=0;f=this.swimlanes.length||!(q>k||(!b||p)&&q==k)||(e=this.traverse(n,\nb,m[c],d,e,f,g,q))}}else if(null==e[l])for(c=0;cmxUtils.indexOf(this.edges,a))&&(null==this.edges&&(this.edges=[]),this.edges.push(a));return a};mxCell.prototype.removeEdge=function(a,b){if(null!=a){if(a.getTerminal(!b)!=this&&null!=this.edges){var c=this.getEdgeIndex(a);0<=c&&this.edges.splice(c,1)}a.setTerminal(null,b)}return a};\nmxCell.prototype.removeFromTerminal=function(a){var b=this.getTerminal(a);null!=b&&b.removeEdge(this,a)};mxCell.prototype.hasAttribute=function(a){var b=this.getValue();return null!=b&&b.nodeType==mxConstants.NODETYPE_ELEMENT&&b.hasAttribute?b.hasAttribute(a):null!=b.getAttribute(a)};mxCell.prototype.getAttribute=function(a,b){var c=this.getValue();a=null!=c&&c.nodeType==mxConstants.NODETYPE_ELEMENT?c.getAttribute(a):null;return null!=a?a:b};\nmxCell.prototype.setAttribute=function(a,b){var c=this.getValue();null!=c&&c.nodeType==mxConstants.NODETYPE_ELEMENT&&c.setAttribute(a,b)};mxCell.prototype.clone=function(){var a=mxUtils.clone(this,this.mxTransient);a.setValue(this.cloneValue());return a};mxCell.prototype.cloneValue=function(a){a=null!=a?a:this.getValue();null!=a&&(\"function\"==typeof a.clone?a=a.clone():isNaN(a.nodeType)||(a=a.cloneNode(!0)));return a};function mxGeometry(a,b,c,d){mxRectangle.call(this,a,b,c,d)}\nmxGeometry.prototype=new mxRectangle;mxGeometry.prototype.constructor=mxGeometry;mxGeometry.prototype.TRANSLATE_CONTROL_POINTS=!0;mxGeometry.prototype.alternateBounds=null;mxGeometry.prototype.sourcePoint=null;mxGeometry.prototype.targetPoint=null;mxGeometry.prototype.points=null;mxGeometry.prototype.offset=null;mxGeometry.prototype.relative=!1;\nmxGeometry.prototype.swap=function(){if(null!=this.alternateBounds){var a=new mxRectangle(this.x,this.y,this.width,this.height);this.x=this.alternateBounds.x;this.y=this.alternateBounds.y;this.width=this.alternateBounds.width;this.height=this.alternateBounds.height;this.alternateBounds=a}};mxGeometry.prototype.getTerminalPoint=function(a){return a?this.sourcePoint:this.targetPoint};mxGeometry.prototype.setTerminalPoint=function(a,b){b?this.sourcePoint=a:this.targetPoint=a;return a};\nmxGeometry.prototype.rotate=function(a,b){var c=mxUtils.toRadians(a);a=Math.cos(c);c=Math.sin(c);if(!this.relative){var d=new mxPoint(this.getCenterX(),this.getCenterY());d=mxUtils.getRotatedPoint(d,a,c,b);this.x=Math.round(d.x-this.width/2);this.y=Math.round(d.y-this.height/2)}null!=this.sourcePoint&&(d=mxUtils.getRotatedPoint(this.sourcePoint,a,c,b),this.sourcePoint.x=Math.round(d.x),this.sourcePoint.y=Math.round(d.y));null!=this.targetPoint&&(d=mxUtils.getRotatedPoint(this.targetPoint,a,c,b),this.targetPoint.x=\nMath.round(d.x),this.targetPoint.y=Math.round(d.y));if(null!=this.points)for(var e=0;eb[e]?1:-1:(c=parseInt(a[e]),e=parseInt(b[e]),d=c==e?0:c>e?1:-1);break}0==d&&(c=a.length,e=b.length,c!=e&&(d=c>e?1:-1));return d}},mxPerimeter={RectanglePerimeter:function(a,b,c,d){b=a.getCenterX();var e=a.getCenterY(),f=Math.atan2(c.y-e,c.x-b),g=new mxPoint(0,0),k=Math.PI,l=Math.PI/2-f,m=Math.atan2(a.height,a.width);f<-k+m||f>k-m?(g.x=a.x,g.y=e-a.width*Math.tan(f)/\n2):f<-m?(g.y=a.y,g.x=b-a.height*Math.tan(l)/2):f=a.x&&c.x<=a.x+a.width?g.x=c.x:c.y>=a.y&&c.y<=a.y+a.height&&(g.y=c.y),c.xa.x+a.width&&(g.x=a.x+a.width),c.ya.y+a.height&&(g.y=a.y+a.height));return g},EllipsePerimeter:function(a,b,c,d){var e=a.x,f=a.y,g=a.width/2,k=a.height/2,l=e+g,m=f+k;b=c.x;c=c.y;var n=parseInt(b-l),p=parseInt(c-m);if(0==n&&0!=p)return new mxPoint(l,\nm+k*p/Math.abs(p));if(0==n&&0==p)return new mxPoint(b,c);if(d){if(c>=f&&c<=f+a.height)return a=c-m,a=Math.sqrt(g*g*(1-a*a/(k*k)))||0,b<=e&&(a=-a),new mxPoint(l+a,c);if(b>=e&&b<=e+a.width)return a=b-l,a=Math.sqrt(k*k*(1-a*a/(g*g)))||0,c<=f&&(a=-a),new mxPoint(b,m+a)}e=p/n;m-=e*l;f=g*g*e*e+k*k;a=-2*l*f;k=Math.sqrt(a*a-4*f*(g*g*e*e*l*l+k*k*l*l-g*g*k*k));g=(-a+k)/(2*f);l=(-a-k)/(2*f);k=e*g+m;m=e*l+m;Math.sqrt(Math.pow(g-b,2)+Math.pow(k-c,2))c?new mxPoint(g,e):new mxPoint(g,e+a);if(k==c)return g>l?new mxPoint(b,k):new mxPoint(b+f,k);var m=g,n=k;d&&(l>=b&&l<=b+f?m=l:c>=e&&c<=e+a&&(n=c));return l-t&&rMath.PI-t)?c=d&&(e&&c.x>=n.x&&c.x<=q.x||!e&&c.y>=n.y&&c.y<=q.y)?e?new mxPoint(c.x,n.y):new mxPoint(n.x,c.y):b==mxConstants.DIRECTION_NORTH?new mxPoint(f+k/2+l*Math.tan(r)/2,g+l):b==mxConstants.DIRECTION_SOUTH?new mxPoint(f+k/2-l*Math.tan(r)/2,g):b==mxConstants.DIRECTION_WEST?new mxPoint(f+k,g+l/2+k*Math.tan(r)/2):new mxPoint(f,g+\nl/2-k*Math.tan(r)/2):(d&&(d=new mxPoint(a,m),c.y>=g&&c.y<=g+l?(d.x=e?a:b==mxConstants.DIRECTION_WEST?f+k:f,d.y=c.y):c.x>=f&&c.x<=f+k&&(d.x=c.x,d.y=e?b==mxConstants.DIRECTION_NORTH?g+l:g:m),a=d.x,m=d.y),c=e&&c.x<=f+k/2||!e&&c.y<=g+l/2?mxUtils.intersection(c.x,c.y,a,m,n.x,n.y,p.x,p.y):mxUtils.intersection(c.x,c.y,a,m,p.x,p.y,q.x,q.y));null==c&&(c=new mxPoint(a,m));return c},HexagonPerimeter:function(a,b,c,d){var e=a.x,f=a.y,g=a.width,k=a.height,l=a.getCenterX();a=a.getCenterY();var m=c.x,n=c.y,p=-Math.atan2(n-\na,m-l),q=Math.PI,r=Math.PI/2;new mxPoint(l,a);b=null!=b?mxUtils.getValue(b.style,mxConstants.STYLE_DIRECTION,mxConstants.DIRECTION_EAST):mxConstants.DIRECTION_EAST;var t=b==mxConstants.DIRECTION_NORTH||b==mxConstants.DIRECTION_SOUTH;b=new mxPoint;var u=new mxPoint;if(mf+k||m>e+g&&ne+g&&n>f+k)d=!1;if(d){if(t){if(m==l){if(n<=f)return new mxPoint(l,f);if(n>=f+k)return new mxPoint(l,f+k)}else if(me+g){if(n==f+k/4)return new mxPoint(e+g,f+k/4);if(n==f+3*k/4)return new mxPoint(e+g,f+3*k/4)}else if(m==e){if(na)return new mxPoint(e,f+3*k/4)}else if(m==e+g){if(na)return new mxPoint(e+g,f+3*k/4)}if(n==f)return new mxPoint(l,f);if(n==f+k)return new mxPoint(l,f+k);mf+k/4&&nf+3*k/4&&(b=new mxPoint(e-Math.floor(.5*g),f+Math.floor(.5*k)),u=new mxPoint(e+g,f+Math.floor(1.25*k))):m>l&&(n>f+k/4&&nf+3*k/4&&(b=new mxPoint(e+Math.floor(1.5*g),f+Math.floor(.5*k)),u=new mxPoint(e,f+Math.floor(1.25*k))))}else{if(n==a){if(m<=e)return new mxPoint(e,f+k/2);if(m>=e+g)return new mxPoint(e+g,f+k/2)}else if(n<\nf){if(m==e+g/4)return new mxPoint(e+g/4,f);if(m==e+3*g/4)return new mxPoint(e+3*g/4,f)}else if(n>f+k){if(m==e+g/4)return new mxPoint(e+g/4,f+k);if(m==e+3*g/4)return new mxPoint(e+3*g/4,f+k)}else if(n==f){if(ml)return new mxPoint(e+3*g/4,f)}else if(n==f+k){if(ma)return new mxPoint(e+3*g/4,f+k)}if(m==e)return new mxPoint(e,a);if(m==e+g)return new mxPoint(e+g,a);ne+g/4&&me+3*g/4&&(b=new mxPoint(e+Math.floor(.5*g),f-Math.floor(.5*k)),u=new mxPoint(e+Math.floor(1.25*g),f+k)):n>a&&(m>e+g/4&&me+3*g/4&&(b=new mxPoint(e+Math.floor(.5*g),f+Math.floor(1.5*k)),u=new mxPoint(e+Math.floor(1.25*g),f)))}d=l;p=a;m>=e&&m<=\ne+g?(d=m,p=n=f&&n<=f+k&&(p=n,d=m-m?(b=new mxPoint(e+g,f),u=new mxPoint(e+g,f+\nk)):p>m&&pr&&pq-m&&p<=q||p<-q+m&&p>=-q?(b=new mxPoint(e,f),u=new mxPoint(e,f+k)):p<-m&&p>-r?(b=new mxPoint(e+Math.floor(1.5*g),f+Math.floor(.5*k)),u=new mxPoint(e,f+Math.floor(1.25*k))):p<-r&&p>-q+m&&(b=new mxPoint(e-Math.floor(.5*g),f+Math.floor(.5*k)),u=new mxPoint(e+g,f+Math.floor(1.25*k)))}else{m=\nMath.atan2(k/2,g/4);if(p==m)return new mxPoint(e+Math.floor(.75*g),f);if(p==q-m)return new mxPoint(e+Math.floor(.25*g),f);if(p==q||p==-q)return new mxPoint(e,f+Math.floor(.5*k));if(0==p)return new mxPoint(e+g,f+Math.floor(.5*k));if(p==-m)return new mxPoint(e+Math.floor(.75*g),f+k);if(p==-q+m)return new mxPoint(e+Math.floor(.25*g),f+k);0m&&pq-m&&\npp&&p>-m?(b=new mxPoint(e+Math.floor(.5*g),f+Math.floor(1.5*k)),u=new mxPoint(e+Math.floor(1.25*g),f)):p<-m&&p>-q+m?(b=new mxPoint(e,f+k),u=new mxPoint(e+g,f+k)):p<-q+m&&p>-q&&(b=new mxPoint(e-Math.floor(.25*g),f),u=new mxPoint(e+Math.floor(.5*g),f+Math.floor(1.5*k)))}c=mxUtils.intersection(l,a,c.x,c.y,b.x,b.y,u.x,u.y)}return null==c?new mxPoint(l,a):c}};\nfunction mxPrintPreview(a,b,c,d,e,f,g,k,l){this.graph=a;this.scale=null!=b?b:1/a.pageScale;this.border=null!=d?d:0;this.pageFormat=mxRectangle.fromRectangle(null!=c?c:a.pageFormat);this.title=null!=k?k:\"Printer-friendly version\";this.x0=null!=e?e:0;this.y0=null!=f?f:0;this.borderColor=g;this.pageSelector=null!=l?l:!0}mxPrintPreview.prototype.graph=null;mxPrintPreview.prototype.pageFormat=null;mxPrintPreview.prototype.addPageCss=!1;mxPrintPreview.prototype.pixelsPerInch=100;\nmxPrintPreview.prototype.pageMargin=27;mxPrintPreview.prototype.overflowClipMargin=\"1px\";mxPrintPreview.prototype.gridSize=null;mxPrintPreview.prototype.gridSteps=null;mxPrintPreview.prototype.gridColor=null;mxPrintPreview.prototype.gridStrokeWidth=.5;mxPrintPreview.prototype.defaultCss=\"@media screen {\\n body {\\n background: gray;\\n zoom: 0.7;\\n }\\n body > div {\\n margin-bottom: 20px;\\n box-sizing: border-box;\\n }\\n a, a * {\\n pointer-events: none;\\n }\\n}\\n@media print {\\n body {\\n margin: 0px;\\n }\\n * {\\n -webkit-print-color-adjust: exact;\\n }\\n}\";\nmxPrintPreview.prototype.scale=null;mxPrintPreview.prototype.border=0;mxPrintPreview.prototype.marginTop=0;mxPrintPreview.prototype.marginBottom=0;mxPrintPreview.prototype.x0=0;mxPrintPreview.prototype.y0=0;mxPrintPreview.prototype.autoOrigin=!0;mxPrintPreview.prototype.printOverlays=!1;mxPrintPreview.prototype.printControls=!1;mxPrintPreview.prototype.printBackgroundImage=!1;mxPrintPreview.prototype.backgroundColor=\"#ffffff\";mxPrintPreview.prototype.borderColor=null;\nmxPrintPreview.prototype.title=null;mxPrintPreview.prototype.pageSelector=null;mxPrintPreview.prototype.wnd=null;mxPrintPreview.prototype.targetWindow=null;mxPrintPreview.prototype.pageCount=0;mxPrintPreview.prototype.clipping=!0;mxPrintPreview.prototype.getWindow=function(){return this.wnd};\nmxPrintPreview.prototype.getDoctype=function(){var a=\"\";8==document.documentMode?a='':8 svg {\\n margin: \"+\nmxUtils.htmlEntities((c/d).toFixed(2))+\"in;\\n}\\n\");return b};\nmxPrintPreview.prototype.open=function(a,b,c,d,e,f,g){c=null;try{var k=this.graph.cellRenderer.initializeOverlay,l=null!=f;f=mxRectangle.fromRectangle(null!=f?f:this.pageFormat);var m=f.width+1,n=f.height+1;this.printOverlays&&(this.graph.cellRenderer.initializeOverlay=function(I,J){J.init(I.view.getDrawPane())});this.printControls&&(this.graph.cellRenderer.initControl=function(I,J,K,M){J.dialect=I.view.graph.dialect;J.init(I.view.getDrawPane())});this.wnd=null!=b?b:this.wnd;b=!1;null==this.wnd&&\n(b=!0,this.wnd=window.open());var p=this.wnd.document;if(b){var q=this.getDoctype();null!=q&&0\");p.writeln(\"\");p.writeln(\"\");this.writeHead(p,a);p.writeln(\"\");p.writeln(\"\")}var r=mxRectangle.fromRectangle(null!=g?this.graph.getBoundingBox(g):this.graph.getGraphBounds()),t=this.graph.getView().getScale(),u=t/this.scale,v=this.graph.getView().getTranslate();this.autoOrigin||(this.x0-=v.x*this.scale,\nthis.y0-=v.y*this.scale,r.width+=r.x,r.height+=r.y,r.x=0,this.border=r.y=0);var y=m-2*this.border,A=n-2*this.border;n+=this.marginTop+this.marginBottom;r.width/=u;r.height/=u;var z=Math.max(1,Math.ceil((r.width+this.x0)/y)),x=Math.max(1,Math.ceil((r.height+this.y0)/A));this.pageCount=z*x;var C=null;l&&(null==this.pendingCss&&(this.pageFormatClass={},this.pendingCss=\"\"),C=mxUtils.htmlEntities(\"gePageFormat-\"+String(f.width).replace(/\\./g,\"_\")+\"-\"+String(f.height).replace(/\\./g,\"_\")),null==this.pageFormatClass[C]&&\n(this.pageFormatClass[C]=!0,this.pendingCss+=this.getPageClassCss(C,f)));var B=mxUtils.bind(this,function(I){null!=this.borderColor&&(I.style.borderColor=this.borderColor,I.style.borderStyle=\"solid\",I.style.borderWidth=\"1px\");I.style.background=this.backgroundColor;null!=C?I.classList.add(C):(I.style.width=f.width+\"px\",I.style.height=f.height+\"px\");p.body.appendChild(I)}),E=this.getCoverPages(m,n);if(null!=E)for(var D=0;D\");a.writeln(\"\");a.close();this.addPendingCss(a);mxEvent.release(a.body)}}catch(b){}};\nmxPrintPreview.prototype.writeHead=function(a,b){null!=this.title&&a.writeln(\"\"+mxUtils.htmlEntities(this.title)+\"\");var c=mxClient.basePath;\"/src\"==c.substring(c.length-4)&&(c=c.substring(0,c.length-4));mxClient.link(\"stylesheet\",c+\"/css/common.css\",a);a.writeln('\")};mxPrintPreview.prototype.writePostfix=function(a){};mxPrintPreview.prototype.getRoot=function(){var a=this.graph.view.currentRoot;null==a&&(a=this.graph.getModel().getRoot());return a};mxPrintPreview.prototype.useCssTransforms=function(){return!mxClient.NO_FO&&!mxClient.IS_SF};\nmxPrintPreview.prototype.isCellVisible=function(a){return!0};mxPrintPreview.prototype.drawBackgroundImage=function(a){a.redraw()};\nmxPrintPreview.prototype.addGraphFragment=function(a,b,c,d,e,f){var g=this.graph.getView();d=this.graph.container;this.graph.container=e;var k=g.getCanvas(),l=g.getBackgroundPane(),m=g.getDrawPane(),n=g.getOverlayPane(),p=c;if(this.graph.dialect==mxConstants.DIALECT_SVG){if(g.createSvg(),this.useCssTransforms()){var q=g.getDrawPane().parentNode;q.setAttribute(\"transformOrigin\",\"0 0\");q.setAttribute(\"transform\",\"scale(\"+c+\",\"+c+\")translate(\"+a+\",\"+b+\")\");c=1;b=a=0}}else g.createHtml();q=g.isEventsEnabled();\ng.setEventsEnabled(!1);var r=this.graph.isEnabled();this.graph.setEnabled(!1);var t=g.getTranslate();g.translate=new mxPoint(a,b);var u=this.graph.selectionCellsHandler.updateHandler;this.graph.selectionCellsHandler.updateHandler=function(){};var v=this.graph.cellRenderer.redraw,y=g.states,A=g.scale,z=null;if(this.printBackgroundImage){var x=this.getBackgroundImage();null!=x&&(z=new mxRectangle(Math.round(a*A+x.x),Math.round(b*A+x.y),x.width-1,x.height-1),z=new mxImageShape(z,x.src),z.dialect=this.graph.dialect)}if(this.clipping){var C=\nnew mxRectangle((f.x+t.x+1.5)*A,(f.y+t.y+1.5)*A,(f.width-1.5)*A/p,(f.height-1.5)*A/p),B=this;this.graph.cellRenderer.redraw=function(D,F,G){if(null!=D){var H=y.get(D.cell);if(null!=H&&(H=g.getBoundingBox(H,!1),null!=H&&0f.height?c.style.height=\"100%\":c.style.width=\"100%\")):this.isTextLabel(c)||c.parentNode.removeChild(c),c=e;g.overlayPane.parentNode.removeChild(g.overlayPane);this.graph.setEnabled(r);this.graph.container=d;this.graph.cellRenderer.redraw=v;this.graph.selectionCellsHandler.updateHandler=u;g.canvas=k;g.backgroundPane=l;g.drawPane=m;g.overlayPane=n;g.translate=t;a.destroy();g.setEventsEnabled(q)}};\nmxPrintPreview.prototype.addGrid=function(a,b){0\":\"\";mxCellEditor.prototype.escapeCancelsEditing=!0;\nmxCellEditor.prototype.textNode=null;mxCellEditor.prototype.zIndex=1;mxCellEditor.prototype.minResize=new mxRectangle(0,20);mxCellEditor.prototype.blurEnabled=!1;mxCellEditor.prototype.initialValue=null;mxCellEditor.prototype.align=null;mxCellEditor.prototype.init=function(){this.textarea=document.createElement(\"div\");this.textarea.className=\"mxCellEditor mxPlainTextEditor\";this.textarea.contentEditable=!0;mxClient.IS_GC&&(this.textarea.style.minHeight=\"1em\");this.installListeners(this.textarea)};\nmxCellEditor.prototype.applyValue=function(a,b){this.graph.labelChanged(a.cell,b,this.trigger)};mxCellEditor.prototype.setAlign=function(a){var b=this.graph.getView().getState(this.editingCell);null!=this.textarea&&null!=b&&(b=mxUtils.getValue(b.style,mxConstants.STYLE_TEXT_DIRECTION,mxConstants.DEFAULT_TEXT_DIRECTION),null==b||\"vertical-\"!=b.substring(0,9))&&(this.textarea.style.textAlign=a);this.align=a;this.resize()};\nmxCellEditor.prototype.getInitialValue=function(a,b){a=mxUtils.htmlEntities(this.graph.getEditingValue(a.cell,b),!1);a=mxUtils.replaceTrailingNewlines(a,\"

\");return a.replace(/\\n/g,\"
\")};mxCellEditor.prototype.getCurrentValue=function(a){return mxUtils.extractTextWithWhitespace(this.textarea.childNodes)};mxCellEditor.prototype.isCancelEditingKeyEvent=function(a){return this.escapeCancelsEditing||mxEvent.isShiftDown(a)||mxEvent.isControlDown(a)||mxEvent.isMetaDown(a)};\nmxCellEditor.prototype.installListeners=function(a){mxEvent.addListener(a,\"dragstart\",mxUtils.bind(this,function(e){this.graph.stopEditing(!1);mxEvent.consume(e)}));mxEvent.addListener(a,\"blur\",mxUtils.bind(this,function(e){this.blurEnabled&&this.focusLost(e)}));var b=this.graph.container.scrollTop,c=this.graph.container.scrollLeft;mxEvent.addListener(a,\"keydown\",mxUtils.bind(this,function(e){b=this.graph.container.scrollTop;c=this.graph.container.scrollLeft;mxEvent.isConsumed(e)||(this.isStopEditingEvent(e)?\n(this.graph.stopEditing(!1),mxEvent.consume(e)):27==e.keyCode&&(this.graph.stopEditing(this.isCancelEditingKeyEvent(e)),mxEvent.consume(e)))}));var d=mxUtils.bind(this,function(e){null!=this.editingCell&&this.clearOnChange&&a.innerHTML==this.getEmptyLabelText()&&(!mxClient.IS_FF||8!=e.keyCode&&46!=e.keyCode)&&(this.clearOnChange=!1,a.innerText=\"\")});mxEvent.addListener(a,\"keypress\",d);mxEvent.addListener(a,\"paste\",d);mxEvent.addListener(a,\"compositionstart\",mxUtils.bind(this,function(e){null!=this.editingCell&&\nthis.clearOnChange&&a.innerHTML==this.getEmptyLabelText()&&(this.clearOnChange=!1,a.innerText=\"\")}));d=mxUtils.bind(this,function(e){null!=this.editingCell&&(this.graph.container.scrollTop=b,this.graph.container.scrollLeft=c,a.scrollIntoView({block:\"nearest\",inline:\"nearest\"}),0!=this.textarea.innerHTML.length&&\"
\"!=this.textarea.innerHTML||this.textarea.innerHTML==this.getEmptyLabelText()?this.clearOnChange=!1:(this.textarea.innerHTML=this.getEmptyLabelText(),this.clearOnChange=0\"==this.textarea.innerHTML?(this.textarea.innerHTML=this.getEmptyLabelText(),this.clearOnChange=\n!0):this.clearOnChange=this.textarea.innerHTML==this.getEmptyLabelText(),this.textShape=d.text,this.editingCell=a,this.trigger=b,null==this.textShape&&(this.textShape=this.graph.cellRenderer.createTextShape(d,\"\",this.graph.dialect)),a=mxUtils.bind(this,function(){if(null!=this.editingCell)if(null!=this.textShape&&null!=this.textShape.node&&this.isHideLabel(d)&&(this.textNode=this.textShape.node,this.textNode.style.visibility=\"hidden\"),this.resize(),this.graph.container.appendChild(this.textarea),\nmxClient.IS_IOS&&(this.graph.container.scrollTop=Math.max(this.graph.container.scrollTop,d.y+d.height-this.graph.container.clientHeight/3)),this.textarea.scrollIntoView({block:\"nearest\",inline:\"nearest\"}),this.textarea.focus(),null!=c){this.textarea.innerHTML=c;var e=document.createRange();e.selectNodeContents(this.textarea);e.collapse(!1);var f=window.getSelection();f.removeAllRanges();f.addRange(e)}else this.isSelectText()&&0=n.x:null!=c&&(k=(null!=m?m.x:c.x+c.width)<(null!=l?l.x:b.x))}if(null!=l)b=new mxCellState,b.x=l.x,b.y=l.y;else if(null!=b){var p=mxUtils.getPortConstraints(b,a,!0,mxConstants.DIRECTION_MASK_NONE);p!=mxConstants.DIRECTION_MASK_NONE&&\np!=mxConstants.DIRECTION_MASK_WEST+mxConstants.DIRECTION_MASK_EAST&&(k=p==mxConstants.DIRECTION_MASK_WEST)}else return;n=!0;null!=c&&(g=g.getCellGeometry(c.cell),null!=g&&g.relative?n=.5>=g.x:null!=b&&(n=(null!=l?l.x:b.x+b.width)<(null!=m?m.x:c.x)));null!=m?(c=new mxCellState,c.x=m.x,c.y=m.y):null!=c&&(p=mxUtils.getPortConstraints(c,a,!1,mxConstants.DIRECTION_MASK_NONE),p!=mxConstants.DIRECTION_MASK_NONE&&p!=mxConstants.DIRECTION_MASK_WEST+mxConstants.DIRECTION_MASK_EAST&&(n=p==mxConstants.DIRECTION_MASK_WEST));\nnull!=b&&null!=c&&(a=k?b.x:b.x+b.width,b=f.getRoutingCenterY(b),l=n?c.x:c.x+c.width,c=f.getRoutingCenterY(c),f=new mxPoint(a+(k?-d:d),b),g=new mxPoint(l+(n?-d:d),c),k==n?(d=k?Math.min(a,l)-d:Math.max(a,l)+d,e.push(new mxPoint(d,b)),e.push(new mxPoint(d,c))):(f.xb.x+b.width?null!=c?(d=c.x,m=Math.max(Math.abs(l-c.y),m)):a==mxConstants.DIRECTION_NORTH?l=b.y-2*k:a==mxConstants.DIRECTION_SOUTH?l=b.y+b.height+2*k:d=a==mxConstants.DIRECTION_EAST?b.x-2*m:b.x+b.width+2*m:null!=c&&(d=f.getRoutingCenterX(b),k=Math.max(Math.abs(d-c.x),m),l=c.y,m=0);e.push(new mxPoint(d-k,l-m));e.push(new mxPoint(d+k,l+m))}},ElbowConnector:function(a,b,c,d,e){var f=null!=d&&0n;k=f.xm}else l=Math.max(b.x,c.x),m=Math.min(b.x+b.width,c.x+c.width),(g=l==m)||(k=Math.max(b.y,c.y),n=Math.min(b.y+b.height,c.y+c.height),k=k==n);k||!g&&a.style[mxConstants.STYLE_ELBOW]!=mxConstants.ELBOW_VERTICAL?mxEdgeStyle.SideToSide(a,b,c,d,e):mxEdgeStyle.TopToBottom(a,b,c,d,e)},SideToSide:function(a,b,c,d,e){var f=a.view;d=null!=d&&0=b.y&&d.y<=b.y+b.height&&(k=d.y),d.y>=c.y&&d.y<=c.y+c.height&&(f=d.y)),mxUtils.contains(c,a,k)||mxUtils.contains(b,a,k)||e.push(new mxPoint(a,\nk)),mxUtils.contains(c,a,f)||mxUtils.contains(b,a,f)||e.push(new mxPoint(a,f)),1==e.length&&(null!=d?mxUtils.contains(c,a,d.y)||mxUtils.contains(b,a,d.y)||e.push(new mxPoint(a,d.y)):(f=Math.max(b.y,c.y),e.push(new mxPoint(a,f+(Math.min(b.y+b.height,c.y+c.height)-f)/2)))))},TopToBottom:function(a,b,c,d,e){var f=a.view;d=null!=d&&0=b.x&&d.x<=b.x+b.width&&(a=d.x),k=null!=d?d.y:Math.round(g+(k-g)/2),mxUtils.contains(c,a,k)||mxUtils.contains(b,a,k)||e.push(new mxPoint(a,k)),a=null!=d&&d.x>=c.x&&d.x<=c.x+c.width?d.x:f.getRoutingCenterX(c),mxUtils.contains(c,a,k)||mxUtils.contains(b,a,k)||e.push(new mxPoint(a,k)),1==e.length&&(null!=d&&1==e.length?mxUtils.contains(c,d.x,k)||mxUtils.contains(b,d.x,k)||\ne.push(new mxPoint(d.x,k)):(f=Math.max(b.x,c.x),e.push(new mxPoint(f+(Math.min(b.x+b.width,c.x+c.width)-f)/2,k)))))},SegmentConnector:function(a,b,c,d,e){var f=mxEdgeStyle.scalePointArray(a.absolutePoints,a.view.scale);b=mxEdgeStyle.scaleCellState(b,a.view.scale);var g=mxEdgeStyle.scaleCellState(c,a.view.scale);c=[];var k=0Math.abs(p[0].x-m.x)&&(p[0].x=m.x),1>Math.abs(p[0].y-m.y)&&(p[0].y=m.y));r=f[n];null!=r&&null!=p[p.length-1]&&(1>Math.abs(p[p.length-1].x-r.x)&&(p[p.length-1].x=r.x),1>Math.abs(p[p.length-1].y-r.y)&&(p[p.length-1].y=r.y));d=p[0];var t=b,u=f[0],v=d;null!=u&&(t=null);for(q=0;2>q;q++){var y=null!=u&&u.x==v.x,A=null!=u&&u.y==v.y,z=null!=t&&v.y>=t.y&&v.y<=t.y+t.height,x=\nnull!=t&&v.x>=t.x&&v.x<=t.x+t.width;t=A||null==u&&z;v=y||null==u&&x;if(0!=q||!(t&&v||y&&A)){if(null!=u&&!A&&!y&&(z||x)){l=z?!1:!0;break}if(v||t){l=t;1==q&&(l=0==p.length%2?t:v);break}}t=g;u=f[n];null!=u&&(t=null);v=p[p.length-1];y&&A&&(p=p.slice(1))}l&&(null!=f[0]&&f[0].y!=d.y||null==f[0]&&null!=b&&(d.yb.y+b.height))?c.push(new mxPoint(m.x,d.y)):!l&&(null!=f[0]&&f[0].x!=d.x||null==f[0]&&null!=b&&(d.xb.x+b.width))&&c.push(new mxPoint(d.x,m.y));l?m.y=d.y:m.x=d.x;for(q=0;qg.y+g.height))?c.push(new mxPoint(m.x,d.y)):!l&&(null!=f[n]&&f[n].x!=d.x||null==f[n]&&null!=g&&(d.xg.x+g.width))&&c.push(new mxPoint(d.x,m.y)));if(null==f[0]&&null!=b)for(;0=Math.max(1,a.view.scale))e.push(f),k=f;null!=r&&null!=e[e.length-1]&&1>=Math.abs(r.x-e[e.length-1].x)&&1>=Math.abs(r.y-e[e.length-1].y)&&(e.splice(e.length-1,1),null!=e[e.length-1]&&(1>Math.abs(e[e.length-1].x-r.x)&&\n(e[e.length-1].x=r.x),1>Math.abs(e[e.length-1].y-r.y)&&(e[e.length-1].y=r.y)))},orthBuffer:10,orthPointsFallback:!0,dirVectors:[[-1,0],[0,-1],[1,0],[0,1],[-1,0],[0,-1],[1,0]],wayPoints1:[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],routePatterns:[[[513,2308,2081,2562],[513,1090,514,2184,2114,2561],[513,1090,514,2564,2184,2562],[513,2308,2561,1090,514,2568,2308]],[[514,1057,513,2308,2081,2562],[514,2184,2114,2561],[514,2184,2562,1057,513,2564,2184],[514,1057,513,2568,2308,\n2561]],[[1090,514,1057,513,2308,2081,2562],[2114,2561],[1090,2562,1057,513,2564,2184],[1090,514,1057,513,2308,2561,2568]],[[2081,2562],[1057,513,1090,514,2184,2114,2561],[1057,513,1090,514,2184,2562,2564],[1057,2561,1090,514,2568,2308]]],inlineRoutePatterns:[[null,[2114,2568],null,null],[null,[514,2081,2114,2568],null,null],[null,[2114,2561],null,null],[[2081,2562],[1057,2114,2568],[2184,2562],null]],vertexSeperations:[],limits:[[0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0]],LEFT_MASK:32,TOP_MASK:64,RIGHT_MASK:128,\nBOTTOM_MASK:256,LEFT:1,TOP:2,RIGHT:4,BOTTOM:8,SIDE_MASK:480,CENTER_MASK:512,SOURCE_MASK:1024,TARGET_MASK:2048,VERTEX_MASK:3072,getJettySize:function(a,b){var c=mxUtils.getValue(a.style,b?mxConstants.STYLE_SOURCE_JETTY_SIZE:mxConstants.STYLE_TARGET_JETTY_SIZE,mxUtils.getValue(a.style,mxConstants.STYLE_JETTY_SIZE,mxEdgeStyle.orthBuffer));\"auto\"==c&&(mxUtils.getValue(a.style,b?mxConstants.STYLE_STARTARROW:mxConstants.STYLE_ENDARROW,mxConstants.NONE)!=mxConstants.NONE?(a=mxUtils.getNumber(a.style,b?mxConstants.STYLE_STARTSIZE:\nmxConstants.STYLE_ENDSIZE,mxConstants.DEFAULT_MARKERSIZE),c=Math.max(2,Math.ceil((a+mxEdgeStyle.orthBuffer)/mxEdgeStyle.orthBuffer))*mxEdgeStyle.orthBuffer):c=2*mxEdgeStyle.orthBuffer);return c},scalePointArray:function(a,b){var c=[];if(null!=a)for(var d=0;dx;x++)mxEdgeStyle.limits[x][1]=f[x][0]-A[x],mxEdgeStyle.limits[x][2]=f[x][1]-A[x],mxEdgeStyle.limits[x][4]=f[x][0]+f[x][2]+A[x],mxEdgeStyle.limits[x][8]=f[x][1]+f[x][3]+A[x];A=f[0][1]+\nf[0][3]/2;n=f[1][1]+f[1][3]/2;x=f[0][0]+f[0][2]/2-(f[1][0]+f[1][2]/2);C=A-n;A=0;0>x?A=0>C?2:1:0>=C&&(A=3,0==x&&(A=2));n=null;null!=g&&(n=l);l=[[.5,.5],[.5,.5]];g||(l[0]=[0,0]);k||(l[1]=[0,0]);for(x=0;2>x;x++)null!=n&&(l[x][0]=(n.x-f[x][0])/f[x][2],1>=Math.abs(n.x-f[x][0])?b[x]=mxConstants.DIRECTION_MASK_WEST:1>=Math.abs(n.x-f[x][0]-f[x][2])&&(b[x]=mxConstants.DIRECTION_MASK_EAST),l[x][1]=(n.y-f[x][1])/f[x][3],1>=Math.abs(n.y-f[x][1])?b[x]=mxConstants.DIRECTION_MASK_NORTH:1>=Math.abs(n.y-f[x][1]-f[x][3])&&\n(b[x]=mxConstants.DIRECTION_MASK_SOUTH)),n=null,null!=k&&(n=m);x=f[0][1]-(f[1][1]+f[1][3]);m=f[0][0]-(f[1][0]+f[1][2]);n=f[1][1]-(f[0][1]+f[0][3]);p=f[1][0]-(f[0][0]+f[0][2]);mxEdgeStyle.vertexSeperations[1]=Math.max(m-z,0);mxEdgeStyle.vertexSeperations[2]=Math.max(x-z,0);mxEdgeStyle.vertexSeperations[4]=Math.max(n-z,0);mxEdgeStyle.vertexSeperations[3]=Math.max(p-z,0);z=[];g=[];k=[];g[0]=m>=p?mxConstants.DIRECTION_MASK_WEST:mxConstants.DIRECTION_MASK_EAST;k[0]=x>=n?mxConstants.DIRECTION_MASK_NORTH:\nmxConstants.DIRECTION_MASK_SOUTH;g[1]=mxUtils.reversePortConstraints(g[0]);k[1]=mxUtils.reversePortConstraints(k[0]);m=m>=p?m:p;n=x>=n?x:n;p=[[0,0],[0,0]];q=!1;for(x=0;2>x;x++)0==b[x]&&(0==(g[x]&c[x])&&(g[x]=mxUtils.reversePortConstraints(g[x])),0==(k[x]&c[x])&&(k[x]=mxUtils.reversePortConstraints(k[x])),p[x][0]=k[x],p[x][1]=g[x]);0x;x++)0==b[x]&&(0==(p[x][0]&c[x])&&(p[x][0]=p[x][1]),z[x]=p[x][0]&c[x],z[x]|=(p[x][1]&c[x])<<8,z[x]|=(p[1-x][x]&c[x])<<16,z[x]|=(p[1-x][1-x]&c[x])<<24,0==(z[x]&15)&&(z[x]<<=8),0==(z[x]&3840)&&(z[x]=z[x]&15|z[x]>>8),0==(z[x]&983040)&&(z[x]=z[x]&65535|(z[x]&251658240)>>8),b[x]=z[x]&15,c[x]==mxConstants.DIRECTION_MASK_WEST||c[x]==mxConstants.DIRECTION_MASK_NORTH||\nc[x]==mxConstants.DIRECTION_MASK_EAST||c[x]==mxConstants.DIRECTION_MASK_SOUTH)&&(b[x]=c[x]);c=b[0]==mxConstants.DIRECTION_MASK_EAST?3:b[0];z=b[1]==mxConstants.DIRECTION_MASK_EAST?3:b[1];c-=A;z-=A;1>c&&(c+=4);1>z&&(z+=4);c=mxEdgeStyle.routePatterns[c-1][z-1];mxEdgeStyle.wayPoints1[0][0]=f[0][0];mxEdgeStyle.wayPoints1[0][1]=f[0][1];switch(b[0]){case mxConstants.DIRECTION_MASK_WEST:mxEdgeStyle.wayPoints1[0][0]-=y;mxEdgeStyle.wayPoints1[0][1]+=l[0][1]*f[0][3];break;case mxConstants.DIRECTION_MASK_SOUTH:mxEdgeStyle.wayPoints1[0][0]+=\nl[0][0]*f[0][2];mxEdgeStyle.wayPoints1[0][1]+=f[0][3]+y;break;case mxConstants.DIRECTION_MASK_EAST:mxEdgeStyle.wayPoints1[0][0]+=f[0][2]+y;mxEdgeStyle.wayPoints1[0][1]+=l[0][1]*f[0][3];break;case mxConstants.DIRECTION_MASK_NORTH:mxEdgeStyle.wayPoints1[0][0]+=l[0][0]*f[0][2],mxEdgeStyle.wayPoints1[0][1]-=y}y=0;g=z=0<(b[0]&(mxConstants.DIRECTION_MASK_EAST|mxConstants.DIRECTION_MASK_WEST))?0:1;for(x=0;x>5,n<<=A,15>=4),p=0<(c[x]&mxEdgeStyle.CENTER_MASK),(t||r)&&9>n?(q=t?0:1,n=p&&0==k?f[q][0]+l[q][0]*f[q][2]:p?f[q][1]+l[q][1]*f[q][3]:mxEdgeStyle.limits[q][n],0==k?(n=(n-mxEdgeStyle.wayPoints1[y][0])*m[0],0e&&(e+=4);1>a&&(a+=4);b=routePatterns[e-1][a-1];0!=c&&0!=d||null==inlineRoutePatterns[e-1][a-1]||(b=inlineRoutePatterns[e-1][a-1]);return b}},mxStyleRegistry={values:[],putValue:function(a,\nb){mxStyleRegistry.values[a]=b},getValue:function(a){return mxStyleRegistry.values[a]},getName:function(a){for(var b in mxStyleRegistry.values)if(mxStyleRegistry.values[b]==a)return b;return null}};mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ELBOW,mxEdgeStyle.ElbowConnector);mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ENTITY_RELATION,mxEdgeStyle.EntityRelation);mxStyleRegistry.putValue(mxConstants.EDGESTYLE_LOOP,mxEdgeStyle.Loop);mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SIDETOSIDE,mxEdgeStyle.SideToSide);\nmxStyleRegistry.putValue(mxConstants.EDGESTYLE_TOPTOBOTTOM,mxEdgeStyle.TopToBottom);mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ORTHOGONAL,mxEdgeStyle.OrthConnector);mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SEGMENT,mxEdgeStyle.SegmentConnector);mxStyleRegistry.putValue(mxConstants.PERIMETER_ELLIPSE,mxPerimeter.EllipsePerimeter);mxStyleRegistry.putValue(mxConstants.PERIMETER_RECTANGLE,mxPerimeter.RectanglePerimeter);mxStyleRegistry.putValue(mxConstants.PERIMETER_RHOMBUS,mxPerimeter.RhombusPerimeter);\nmxStyleRegistry.putValue(mxConstants.PERIMETER_TRIANGLE,mxPerimeter.TrianglePerimeter);mxStyleRegistry.putValue(mxConstants.PERIMETER_HEXAGON,mxPerimeter.HexagonPerimeter);function mxGraphView(a){this.graph=a;this.translate=new mxPoint;this.graphBounds=new mxRectangle;this.states=new mxDictionary}mxGraphView.prototype=new mxEventSource;mxGraphView.prototype.constructor=mxGraphView;mxGraphView.prototype.EMPTY_POINT=new mxPoint;mxGraphView.prototype.doneResource=\"none\"!=mxClient.language?\"done\":\"\";\nmxGraphView.prototype.updatingDocumentResource=\"none\"!=mxClient.language?\"updatingDocument\":\"\";mxGraphView.prototype.allowEval=!1;mxGraphView.prototype.captureDocumentGesture=!0;mxGraphView.prototype.rendering=!0;mxGraphView.prototype.graph=null;mxGraphView.prototype.currentRoot=null;mxGraphView.prototype.graphBounds=null;mxGraphView.prototype.scale=1;mxGraphView.prototype.translate=null;mxGraphView.prototype.states=null;mxGraphView.prototype.updateStyle=!1;mxGraphView.prototype.lastNode=null;\nmxGraphView.prototype.lastHtmlNode=null;mxGraphView.prototype.lastForegroundNode=null;mxGraphView.prototype.lastForegroundHtmlNode=null;mxGraphView.prototype.getGraphBounds=function(){return this.graphBounds};mxGraphView.prototype.setGraphBounds=function(a){this.graphBounds=a};\nmxGraphView.prototype.getBounds=function(a){var b=null;if(null!=a&&0b.length||null==b[0]||null==b[b.length-1])?this.clear(a.cell,!0):(this.updateEdgeBounds(a),this.updateEdgeLabelOffset(a)))};\nmxGraphView.prototype.updateVertexLabelOffset=function(a){var b=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_POSITION,mxConstants.ALIGN_CENTER);if(b==mxConstants.ALIGN_LEFT)b=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_WIDTH,null),b=null!=b?b*this.scale:a.width,a.absoluteOffset.x-=b;else if(b==mxConstants.ALIGN_RIGHT)a.absoluteOffset.x+=a.width;else if(b==mxConstants.ALIGN_CENTER&&(b=mxUtils.getValue(a.style,mxConstants.STYLE_LABEL_WIDTH,null),null!=b)){var c=mxUtils.getValue(a.style,mxConstants.STYLE_ALIGN,\nmxConstants.ALIGN_CENTER),d=0;c==mxConstants.ALIGN_CENTER?d=.5:c==mxConstants.ALIGN_RIGHT&&(d=1);0!=d&&(a.absoluteOffset.x-=(b*this.scale-a.width)*d)}b=mxUtils.getValue(a.style,mxConstants.STYLE_VERTICAL_LABEL_POSITION,mxConstants.ALIGN_MIDDLE);b==mxConstants.ALIGN_TOP?a.absoluteOffset.y-=a.height:b==mxConstants.ALIGN_BOTTOM&&(a.absoluteOffset.y+=a.height)};mxGraphView.prototype.resetValidationState=function(){this.lastForegroundHtmlNode=this.lastForegroundNode=this.lastHtmlNode=this.lastNode=null};\nmxGraphView.prototype.stateValidated=function(a){var b=this.graph.getModel().isEdge(a.cell)&&this.graph.keepEdgesInForeground||this.graph.getModel().isVertex(a.cell)&&this.graph.keepEdgesInBackground;a=this.graph.cellRenderer.insertStateAfter(a,b?this.lastForegroundNode||this.lastNode:this.lastNode,b?this.lastForegroundHtmlNode||this.lastHtmlNode:this.lastHtmlNode);b?(this.lastForegroundHtmlNode=a[1],this.lastForegroundNode=a[0]):(this.lastHtmlNode=a[1],this.lastNode=a[0])};\nmxGraphView.prototype.updateFixedTerminalPoints=function(a,b,c){this.updateFixedTerminalPoint(a,b,!0,this.graph.getConnectionConstraint(a,b,!0));this.updateFixedTerminalPoint(a,c,!1,this.graph.getConnectionConstraint(a,c,!1))};mxGraphView.prototype.updateFixedTerminalPoint=function(a,b,c,d){a.setAbsoluteTerminalPoint(this.getFixedTerminalPoint(a,b,c,d),c)};\nmxGraphView.prototype.getFixedTerminalPoint=function(a,b,c,d){var e=null;null!=d&&(e=this.graph.getConnectionPoint(b,d,!1));if(null==e&&null==b){b=this.scale;d=this.translate;var f=a.origin;e=this.graph.getCellGeometry(a.cell).getTerminalPoint(c);null!=e&&(e=new mxPoint(b*(d.x+e.x+f.x),b*(d.y+e.y+f.y)))}return e};\nmxGraphView.prototype.updateBoundsFromStencil=function(a){var b=null;if(null!=a&&null!=a.shape&&null!=a.shape.stencil&&\"fixed\"==a.shape.stencil.aspect){b=mxRectangle.fromRectangle(a);var c=a.style[mxConstants.STYLE_DIRECTION],d=c==mxConstants.DIRECTION_NORTH||c==mxConstants.DIRECTION_SOUTH;c=d?a.shape.stencil.h0:a.shape.stencil.w0;d=d?a.shape.stencil.w0:a.shape.stencil.h0;var e=Math.min(a.width/c,a.height/d);a.setRect(a.x+(a.width-c*e)/2,a.y+(a.height-d*e)/2,c*e,d*e)}return b};\nmxGraphView.prototype.updatePoints=function(a,b,c,d){if(null!=a&&null!=a.absolutePoints&&0b.length)||mxUtils.getValue(a.style,mxConstants.STYLE_ORTHOGONAL_LOOP,!1)&&(null!=e&&null!=e.point||null!=f&&null!=f.point)?!1:null!=c&&c==d};\nmxGraphView.prototype.getEdgeStyle=function(a,b,c,d){a=this.isLoopStyleEnabled(a,b,c,d)?mxUtils.getValue(a.style,mxConstants.STYLE_LOOP,this.graph.defaultLoopStyle):mxUtils.getValue(a.style,mxConstants.STYLE_NOEDGESTYLE,!1)?null:a.style[mxConstants.STYLE_EDGE];\"string\"==typeof a&&(b=mxStyleRegistry.getValue(a),null==b&&this.isAllowEval()&&(b=mxUtils.eval(a)),a=b);return\"function\"==typeof a?a:null};\nmxGraphView.prototype.updateFloatingTerminalPoints=function(a,b,c){var d=a.absolutePoints;if(null!=d){var e=d[0];null==d[d.length-1]&&null!=c&&this.updateFloatingTerminalPoint(a,c,b,!1);null==e&&null!=b&&this.updateFloatingTerminalPoint(a,b,c,!0)}};mxGraphView.prototype.updateFloatingTerminalPoint=function(a,b,c,d){a.setAbsoluteTerminalPoint(this.getFloatingTerminalPoint(a,b,c,d),d)};\nmxGraphView.prototype.getFloatingTerminalPoint=function(a,b,c,d){b=this.getTerminalPort(a,b,d);var e=this.getNextPoint(a,c,d),f=this.graph.isOrthogonal(a);c=mxUtils.toRadians(Number(b.style[mxConstants.STYLE_ROTATION]||\"0\"));var g=new mxPoint(b.getCenterX(),b.getCenterY());if(0!=c){var k=Math.cos(-c),l=Math.sin(-c);e=mxUtils.getRotatedPoint(e,k,l,g)}k=parseFloat(a.style[mxConstants.STYLE_PERIMETER_SPACING]||0);k+=parseFloat(a.style[d?mxConstants.STYLE_SOURCE_PERIMETER_SPACING:mxConstants.STYLE_TARGET_PERIMETER_SPACING]||\n0);a=this.getPerimeterPoint(b,e,0==c&&f,k);0!=c&&(k=Math.cos(c),l=Math.sin(c),a=mxUtils.getRotatedPoint(a,k,l,g));return a};mxGraphView.prototype.getTerminalPort=function(a,b,c){a=mxUtils.getValue(a.style,c?mxConstants.STYLE_SOURCE_PORT:mxConstants.STYLE_TARGET_PORT);null!=a&&(a=this.getState(this.graph.getModel().getCell(a)),null!=a&&(b=a));return b};\nmxGraphView.prototype.getPerimeterPoint=function(a,b,c,d){var e=null;if(null!=a){var f=this.getPerimeterFunction(a);if(null!=f&&null!=b&&(d=this.getPerimeterBounds(a,d),0=Math.round(k+g)&&l=f?0:f*f/(a*a+n*n));a>e&&(a=e);e=Math.sqrt(mxUtils.ptSegDistSq(g.x,g.y,k.x,k.y,b,c));-1==mxUtils.relativeCcw(g.x,g.y,k.x,k.y,b,c)&&(e=-e);return new mxPoint((d/2-m-a)/d*-2,e/this.scale)}}return new mxPoint};\nmxGraphView.prototype.updateEdgeLabelOffset=function(a){var b=a.absolutePoints;a.absoluteOffset.x=a.getCenterX();a.absoluteOffset.y=a.getCenterY();if(null!=b&&0c&&a.x>c+2&&a.x<=b)return!0;b=this.graph.container.offsetHeight;c=this.graph.container.clientHeight;return b>c&&a.y>c+2&&a.y<=b?!0:!1};mxGraphView.prototype.init=function(){this.installListeners();this.graph.dialect==mxConstants.DIALECT_SVG?this.createSvg():this.createHtml()};\nmxGraphView.prototype.installListeners=function(){var a=this.graph,b=a.container;null!=b&&(mxClient.IS_TOUCH&&(mxEvent.addListener(b,\"gesturestart\",mxUtils.bind(this,function(c){a.fireGestureEvent(c);mxEvent.consume(c)})),mxEvent.addListener(b,\"gesturechange\",mxUtils.bind(this,function(c){a.fireGestureEvent(c);mxEvent.consume(c)})),mxEvent.addListener(b,\"gestureend\",mxUtils.bind(this,function(c){a.fireGestureEvent(c);mxEvent.consume(c)}))),mxEvent.addGestureListeners(b,mxUtils.bind(this,function(c){!this.isContainerEvent(c)||\n(mxClient.IS_GC||mxClient.IS_OP||mxClient.IS_SF)&&this.isScrollEvent(c)||a.fireMouseEvent(mxEvent.MOUSE_DOWN,new mxMouseEvent(c))}),mxUtils.bind(this,function(c){this.isContainerEvent(c)&&a.fireMouseEvent(mxEvent.MOUSE_MOVE,new mxMouseEvent(c))}),mxUtils.bind(this,function(c){this.isContainerEvent(c)&&a.fireMouseEvent(mxEvent.MOUSE_UP,new mxMouseEvent(c))})),mxEvent.addListener(b,\"dblclick\",mxUtils.bind(this,function(c){this.isContainerEvent(c)&&a.dblClick(c)})),a.addMouseListener({mouseDown:function(c,\nd){a.popupMenuHandler.hideMenu()},mouseMove:function(){},mouseUp:function(){}}),this.moveHandler=mxUtils.bind(this,function(c){null!=a.tooltipHandler&&a.tooltipHandler.isHideOnHover()&&a.tooltipHandler.hide();if(this.captureDocumentGesture&&a.isMouseDown&&null!=a.container&&!this.isContainerEvent(c)&&\"none\"!=a.container.style.display&&\"hidden\"!=a.container.style.visibility&&!mxEvent.isConsumed(c)){var d=a.fireMouseEvent,e=mxEvent.MOUSE_MOVE,f=null;if(mxClient.IS_TOUCH){f=mxEvent.getClientX(c);var g=\nmxEvent.getClientY(c);f=mxUtils.convertPoint(b,f,g);f=a.view.getState(a.getCellAt(f.x,f.y))}d.call(a,e,new mxMouseEvent(c,f))}}),this.endHandler=mxUtils.bind(this,function(c){this.captureDocumentGesture&&a.isMouseDown&&null!=a.container&&!this.isContainerEvent(c)&&\"none\"!=a.container.style.display&&\"hidden\"!=a.container.style.visibility&&a.fireMouseEvent(mxEvent.MOUSE_UP,new mxMouseEvent(c))}),mxEvent.addGestureListeners(document,null,this.moveHandler,this.endHandler))};\nmxGraphView.prototype.createHtml=function(){var a=this.graph.container;null!=a&&(this.canvas=this.createHtmlPane(\"100%\",\"100%\"),this.canvas.style.overflow=\"hidden\",this.backgroundPane=this.createHtmlPane(\"1px\",\"1px\"),this.drawPane=this.createHtmlPane(\"1px\",\"1px\"),this.overlayPane=this.createHtmlPane(\"1px\",\"1px\"),this.decoratorPane=this.createHtmlPane(\"1px\",\"1px\"),this.canvas.appendChild(this.backgroundPane),this.canvas.appendChild(this.drawPane),this.canvas.appendChild(this.overlayPane),this.canvas.appendChild(this.decoratorPane),\na.appendChild(this.canvas),this.updateContainerStyle(a))};mxGraphView.prototype.updateHtmlCanvasSize=function(a,b){if(null!=this.graph.container){var c=this.graph.container.offsetHeight;this.canvas.style.width=this.graph.container.offsetWidth\"+b+\"\"),d&&b.addListener(mxEvent.CLICK,mxUtils.bind(this,function(e,f){this.isEnabled()&&this.setSelectionCell(a)})),this.addCellOverlay(a,b);this.removeCellOverlays(a);return null};mxGraph.prototype.startEditing=function(a,b){this.startEditingAtCell(null,a,b)};\nmxGraph.prototype.startEditingAtCell=function(a,b,c){null!=b&&mxEvent.isMultiTouchEvent(b)||(null==a&&(a=this.getSelectionCell(),null==a||this.isCellEditable(a)||(a=null)),null!=a&&(this.fireEvent(new mxEventObject(mxEvent.START_EDITING,\"cell\",a,\"event\",b)),this.cellEditor.startEditing(a,b,c),this.fireEvent(new mxEventObject(mxEvent.EDITING_STARTED,\"cell\",a,\"event\",b))))};mxGraph.prototype.getEditingValue=function(a,b){return this.convertValueToString(a)};\nmxGraph.prototype.stopEditing=function(a){this.cellEditor.stopEditing(a);this.fireEvent(new mxEventObject(mxEvent.EDITING_STOPPED,\"cancel\",a))};mxGraph.prototype.labelChanged=function(a,b,c){this.model.beginUpdate();try{var d=a.value;this.cellLabelChanged(a,b,this.isAutoSizeCell(a));this.fireEvent(new mxEventObject(mxEvent.LABEL_CHANGED,\"cell\",a,\"value\",b,\"old\",d,\"event\",c))}finally{this.model.endUpdate()}return a};\nmxGraph.prototype.cellLabelChanged=function(a,b,c){this.model.beginUpdate();try{this.model.setValue(a,b),c&&this.cellSizeUpdated(a,!1)}finally{this.model.endUpdate()}};mxGraph.prototype.escape=function(a){this.fireEvent(new mxEventObject(mxEvent.ESCAPE,\"event\",a))};\nmxGraph.prototype.click=function(a){var b=a.getEvent(),c=a.getCell(),d=new mxEventObject(mxEvent.CLICK,\"event\",b,\"cell\",c);a.isConsumed()&&d.consume();this.fireEvent(d);if(this.isEnabled()&&!mxEvent.isConsumed(b)&&!d.isConsumed()){if(null!=c){if(this.isTransparentClickEvent(b)){var e=!1;a=this.getCellAt(a.graphX,a.graphY,null,null,null,mxUtils.bind(this,function(g){var k=this.isCellSelected(g.cell);e=e||k;return!e||k||g.cell!=c&&this.model.isAncestor(g.cell,c)}));null!=a&&(c=a)}}else if(this.isSwimlaneSelectionEnabled()&&\n(c=this.getSwimlaneAt(a.getGraphX(),a.getGraphY()),!(null==c||this.isToggleEvent(b)&&mxEvent.isAltDown(b)))){d=c;for(a=[];null!=d;){d=this.model.getParent(d);var f=this.view.getState(d);this.isSwimlane(d)&&null!=f&&a.push(d)}if(0=e.scrollLeft&&b>=e.scrollTop&&a<=e.scrollLeft+e.clientWidth&&b<=e.scrollTop+e.clientHeight){var f=e.scrollLeft+e.clientWidth-\na;if(fthis.minPageBreakDist)?Math.ceil(d.height/f.height)+1:0,k=a?Math.ceil(d.width/f.width)+1:0,l=(k-1)*f.width,m=(g-1)*f.height;null==this.horizontalPageBreaks&&0this.model.getChildCount(b)&&c--;var y=this.model.updateEdgeParent;this.model.updateEdgeParent=function(A,z){0>mxUtils.indexOf(a,A)&&y.apply(this,arguments)};this.model.add(b,a[l],c+l);this.model.updateEdgeParent=y;this.autoSizeCellsOnAdd&&this.autoSizeCell(a[l],!0);(null==k||k)&&this.isExtendParentsOnAdd(a[l])&&this.isExtendParent(a[l])&&this.extendParent(a[l]);(null==g||g)&&this.constrainChild(a[l]);null!=d&&this.cellConnected(a[l],\nd,!0);null!=e&&this.cellConnected(a[l],e,!1)}this.fireEvent(new mxEventObject(mxEvent.CELLS_ADDED,\"cells\",a,\"parent\",b,\"index\",c,\"source\",d,\"target\",e,\"absolute\",f))}finally{this.model.endUpdate()}}};mxGraph.prototype.autoSizeCell=function(a,b){if(null!=b?b:1){b=this.model.getChildCount(a);for(var c=0;c\"),e=mxUtils.getSizeForString(k,g,f[mxConstants.STYLE_FONTFAMILY],b,f[mxConstants.STYLE_FONTSTYLE]),b=e.width+d,e=e.height+a,mxUtils.getValue(f,mxConstants.STYLE_HORIZONTAL,!0)||(f=e,e=b,b=f),c&&(b=this.snap(b+this.gridSize/\n2),e=this.snap(e+this.gridSize/2)),d=new mxRectangle(0,0,b,e)):(c=4*this.gridSize,d=new mxRectangle(0,0,c,c))}}return d};mxGraph.prototype.resizeCell=function(a,b,c){return this.resizeCells([a],[b],c)[0]};mxGraph.prototype.resizeCells=function(a,b,c){c=null!=c?c:this.isRecursiveResize();this.model.beginUpdate();try{var d=this.cellsResized(a,b,c);this.fireEvent(new mxEventObject(mxEvent.RESIZE_CELLS,\"cells\",a,\"bounds\",b,\"previous\",d))}finally{this.model.endUpdate()}return a};\nmxGraph.prototype.cellsResized=function(a,b,c){c=null!=c?c:!1;var d=[];if(null!=a&&null!=b&&a.length==b.length){this.model.beginUpdate();try{for(var e=0;ed||b.height>e)&&(b=b.clone(),b.width=Math.min(b.width,d),b.height=Math.min(b.height,e),this.model.setGeometry(a,b),this.isContractParent(a)&&this.contractParent(a))}}};mxGraph.prototype.importCells=function(a,b,c,d,e,f){return this.moveCells(a,b,c,!0,d,e,f)};\nmxGraph.prototype.moveCells=function(a,b,c,d,e,f,g){b=null!=b?b:0;c=null!=c?c:0;d=null!=d?d:!1;if(null!=a&&(0!=b||0!=c||d||null!=e)){var k=a=this.model.getTopmostCells(a);this.model.beginUpdate();try{for(var l=new mxDictionary,m=0;md.width&&(e=b.width-d.width,b.width-=e);c.x+c.width>d.x+d.width&&(e-=c.x+c.width-d.x-d.width-e);f=0;b.height>d.height&&(f=b.height-d.height,b.height-=f);c.y+c.height>\nd.y+d.height&&(f-=c.y+c.height-d.y-d.height-f);c.xg&&(n=0),b>f&&(p=0),this.view.setTranslate(Math.floor(n/2-k.x),Math.floor(p/2-k.y)),this.container.scrollLeft=\n(a-g)/2,this.container.scrollTop=(b-f)/2):this.view.setTranslate(a?Math.floor(l.x-k.x/m+n*c/m):l.x,b?Math.floor(l.y-k.y/m+p*d/m):l.y)};\nmxGraph.prototype.zoom=function(a,b,c,d){b=null!=b?b:this.centerZoom;var e=Math.round(this.view.scale*a*100)/100;null!=c&&(e=Math.round(e*c)/c);c=this.view.getState(this.getSelectionCell());a=e/this.view.scale;if(this.keepSelectionVisibleOnZoom&&null!=c)a=new mxRectangle(c.x*a,c.y*a,c.width*a,c.height*a),this.view.scale=e,this.scrollRectToVisible(a)||(this.view.revalidate(),this.view.setScale(e));else{var f=mxUtils.hasScrollbars(this.container);if(b&&!f)d=this.container.offsetWidth,f=this.container.offsetHeight,\n1b?(b=a.height/b,c=(b-a.height)/2,a.height=b,a.y-=Math.min(a.y,c),d=Math.min(this.container.scrollHeight,a.y+a.height),a.height=d-a.y):(b*=a.width,c=(b-a.width)/2,a.width=b,a.x-=Math.min(a.x,c),c=Math.min(this.container.scrollWidth,\na.x+a.width),a.width=c-a.x);b=this.container.clientWidth/a.width;c=this.view.scale*b;mxUtils.hasScrollbars(this.container)?(this.view.setScale(c),this.container.scrollLeft=Math.round(a.x*b),this.container.scrollTop=Math.round(a.y*b)):this.view.scaleAndTranslate(c,this.view.translate.x-a.x/this.view.scale,this.view.translate.y-a.y/this.view.scale)};\nmxGraph.prototype.scrollCellToVisible=function(a,b){var c=-this.view.translate.x,d=-this.view.translate.y;a=this.view.getState(a);null!=a&&(c=new mxRectangle(c+a.x,d+a.y,a.width,a.height),b&&null!=this.container&&(b=this.container.clientWidth,d=this.container.clientHeight,c.x=c.getCenterX()-b/2,c.width=b,c.y=c.getCenterY()-d/2,c.height=d),b=new mxPoint(this.view.translate.x,this.view.translate.y),this.scrollRectToVisible(c)&&(c=new mxPoint(this.view.translate.x,this.view.translate.y),this.view.translate.x=\nb.x,this.view.translate.y=b.y,this.view.setTranslate(c.x,c.y)))};\nmxGraph.prototype.scrollRectToVisible=function(a){var b=!1;if(null!=a){var c=this.container.offsetWidth,d=this.container.offsetHeight,e=Math.min(c,a.width),f=Math.min(d,a.height);if(mxUtils.hasScrollbars(this.container)){c=this.container;a.x+=this.view.translate.x;a.y+=this.view.translate.y;var g=c.scrollLeft-a.x;d=Math.max(g-c.scrollLeft,0);0g+c&&(this.view.translate.x-=(a.x+e-c-g)/l,b=!0);a.y+f>k+d&&(this.view.translate.y-=(a.y+f-d-k)/l,b=!0);a.x\")):this.setCellWarning(f,null);c=c&&null==g}d=\"\";this.isCellCollapsed(a)&&!c&&(d+=(mxResources.get(this.containsValidationErrorsResource)||this.containsValidationErrorsResource)+\"\\n\");d=this.model.isEdge(a)?d+\n(this.getEdgeValidationError(a,this.model.getTerminal(a,!0),this.model.getTerminal(a,!1))||\"\"):d+(this.getCellValidationError(a)||\"\");b=this.validateCell(a,b);null!=b&&(d+=b);null==this.model.getParent(a)&&this.view.validate();return 0f.max||bf.max||c\")),null==e&&null!=a.overlays&&a.overlays.visit(function(f,g){null!=e||b!=g.node&&b.parentNode!=g.node||(e=mxUtils.htmlEntities(g.overlay.toString()).replace(/\\\\n/g,\"
\"))}),null==e&&(c=this.selectionCellsHandler.getHandler(a.cell),null!=c&&\"function\"==typeof c.getTooltipForNode&&\n(e=c.getTooltipForNode(b))),null==e&&(e=this.getTooltipForCell(a.cell)));return e};mxGraph.prototype.getTooltipForCell=function(a){return null!=a&&null!=a.getTooltip?a.getTooltip():this.convertValueToString(a)};mxGraph.prototype.getLinkForCell=function(a){return null};mxGraph.prototype.getLinkTargetForCell=function(a){return null};mxGraph.prototype.getCursorForMouseEvent=function(a){return this.getCursorForCell(a.getCell())};mxGraph.prototype.getCursorForCell=function(a){return null};\nmxGraph.prototype.getStartSize=function(a,b){var c=new mxRectangle;a=this.getCurrentCellStyle(a,b);b=parseInt(mxUtils.getValue(a,mxConstants.STYLE_STARTSIZE,mxConstants.DEFAULT_STARTSIZE));mxUtils.getValue(a,mxConstants.STYLE_HORIZONTAL,!0)?c.height=b:c.width=b;return c};\nmxGraph.prototype.getSwimlaneDirection=function(a){var b=mxUtils.getValue(a,mxConstants.STYLE_DIRECTION,mxConstants.DIRECTION_EAST),c=1==mxUtils.getValue(a,mxConstants.STYLE_FLIPH,0),d=1==mxUtils.getValue(a,mxConstants.STYLE_FLIPV,0);a=mxUtils.getValue(a,mxConstants.STYLE_HORIZONTAL,!0)?0:3;b==mxConstants.DIRECTION_NORTH?a--:b==mxConstants.DIRECTION_WEST?a+=2:b==mxConstants.DIRECTION_SOUTH&&(a+=1);b=mxUtils.mod(a,2);c&&1==b&&(a+=2);d&&0==b&&(a+=2);return[mxConstants.DIRECTION_NORTH,mxConstants.DIRECTION_EAST,\nmxConstants.DIRECTION_SOUTH,mxConstants.DIRECTION_WEST][mxUtils.mod(a,4)]};mxGraph.prototype.getActualStartSize=function(a,b){var c=new mxRectangle;this.isSwimlane(a,b)&&(b=this.getCurrentCellStyle(a,b),a=parseInt(mxUtils.getValue(b,mxConstants.STYLE_STARTSIZE,mxConstants.DEFAULT_STARTSIZE)),b=this.getSwimlaneDirection(b),b==mxConstants.DIRECTION_NORTH?c.y=a:b==mxConstants.DIRECTION_WEST?c.x=a:b==mxConstants.DIRECTION_SOUTH?c.height=a:c.width=a);return c};\nmxGraph.prototype.getImage=function(a){return null!=a&&null!=a.style?a.style[mxConstants.STYLE_IMAGE]:null};mxGraph.prototype.isTransparentState=function(a){var b=!1;if(null!=a){b=mxUtils.getValue(a.style,mxConstants.STYLE_STROKECOLOR,mxConstants.NONE);var c=mxUtils.getValue(a.style,mxConstants.STYLE_FILLCOLOR,mxConstants.NONE);b=b==mxConstants.NONE&&c==mxConstants.NONE&&null==this.getImage(a)}return b};\nmxGraph.prototype.getVerticalAlign=function(a){return null!=a&&null!=a.style?a.style[mxConstants.STYLE_VERTICAL_ALIGN]||mxConstants.ALIGN_MIDDLE:null};mxGraph.prototype.getIndicatorColor=function(a){return null!=a&&null!=a.style?a.style[mxConstants.STYLE_INDICATOR_COLOR]:null};mxGraph.prototype.getIndicatorGradientColor=function(a){return null!=a&&null!=a.style?a.style[mxConstants.STYLE_INDICATOR_GRADIENTCOLOR]:null};\nmxGraph.prototype.getIndicatorShape=function(a){return null!=a&&null!=a.style?a.style[mxConstants.STYLE_INDICATOR_SHAPE]:null};mxGraph.prototype.getIndicatorImage=function(a){return null!=a&&null!=a.style?a.style[mxConstants.STYLE_INDICATOR_IMAGE]:null};mxGraph.prototype.getBorder=function(){return this.border};mxGraph.prototype.setBorder=function(a){this.border=a};\nmxGraph.prototype.isSwimlane=function(a,b){return null==a||this.model.getParent(a)==this.model.getRoot()||this.model.isEdge(a)?!1:this.getCurrentCellStyle(a,b)[mxConstants.STYLE_SHAPE]==mxConstants.SHAPE_SWIMLANE};mxGraph.prototype.isResizeContainer=function(){return this.resizeContainer};mxGraph.prototype.setResizeContainer=function(a){this.resizeContainer=a};mxGraph.prototype.isEnabled=function(){return this.enabled};\nmxGraph.prototype.setEnabled=function(a){this.enabled=a;this.fireEvent(new mxEventObject(\"enabledChanged\",\"enabled\",a))};mxGraph.prototype.isEscapeEnabled=function(){return this.escapeEnabled};mxGraph.prototype.setEscapeEnabled=function(a){this.escapeEnabled=a};mxGraph.prototype.isInvokesStopCellEditing=function(){return this.invokesStopCellEditing};mxGraph.prototype.setInvokesStopCellEditing=function(a){this.invokesStopCellEditing=a};mxGraph.prototype.isEnterStopsCellEditing=function(){return this.enterStopsCellEditing};\nmxGraph.prototype.setEnterStopsCellEditing=function(a){this.enterStopsCellEditing=a};mxGraph.prototype.isCellLocked=function(a){var b=this.model.getGeometry(a);return this.isCellsLocked()||null!=b&&this.model.isVertex(a)&&b.relative};mxGraph.prototype.isCellsLocked=function(){return this.cellsLocked};mxGraph.prototype.setCellsLocked=function(a){this.cellsLocked=a};mxGraph.prototype.getCloneableCells=function(a){return this.model.filterCells(a,mxUtils.bind(this,function(b){return this.isCellCloneable(b)}))};\nmxGraph.prototype.isCellCloneable=function(a){a=this.getCurrentCellStyle(a);return this.isCellsCloneable()&&0!=a[mxConstants.STYLE_CLONEABLE]};mxGraph.prototype.isCellsCloneable=function(){return this.cellsCloneable};mxGraph.prototype.setCellsCloneable=function(a){this.cellsCloneable=a};mxGraph.prototype.getExportableCells=function(a){return this.model.filterCells(a,mxUtils.bind(this,function(b){return this.canExportCell(b)}))};mxGraph.prototype.canExportCell=function(a){return this.exportEnabled};\nmxGraph.prototype.getImportableCells=function(a){return this.model.filterCells(a,mxUtils.bind(this,function(b){return this.canImportCell(b)}))};mxGraph.prototype.canImportCell=function(a){return this.importEnabled};mxGraph.prototype.isCellSelectable=function(a){return this.isCellsSelectable()};mxGraph.prototype.isCellsSelectable=function(){return this.cellsSelectable};mxGraph.prototype.setCellsSelectable=function(a){this.cellsSelectable=a};\nmxGraph.prototype.getDeletableCells=function(a){return this.model.filterCells(a,mxUtils.bind(this,function(b){return this.isCellDeletable(b)}))};mxGraph.prototype.isCellDeletable=function(a){a=this.getCurrentCellStyle(a);return this.isCellsDeletable()&&0!=a[mxConstants.STYLE_DELETABLE]};mxGraph.prototype.isCellsDeletable=function(){return this.cellsDeletable};mxGraph.prototype.setCellsDeletable=function(a){this.cellsDeletable=a};\nmxGraph.prototype.isLabelMovable=function(a){return!this.isCellLocked(a)&&(this.model.isEdge(a)&&this.edgeLabelsMovable||this.model.isVertex(a)&&this.vertexLabelsMovable)};mxGraph.prototype.getRotatableCells=function(a){return this.model.filterCells(a,mxUtils.bind(this,function(b){return this.isCellRotatable(b)}))};mxGraph.prototype.isCellRotatable=function(a){return 0!=this.getCurrentCellStyle(a)[mxConstants.STYLE_ROTATABLE]};\nmxGraph.prototype.getMovableCells=function(a){return this.model.filterCells(a,mxUtils.bind(this,function(b){return this.isCellMovable(b)}))};mxGraph.prototype.isCellMovable=function(a){var b=this.getCurrentCellStyle(a);return this.isCellsMovable()&&!this.isCellLocked(a)&&0!=b[mxConstants.STYLE_MOVABLE]};mxGraph.prototype.isCellsMovable=function(){return this.cellsMovable};mxGraph.prototype.setCellsMovable=function(a){this.cellsMovable=a};mxGraph.prototype.isGridEnabled=function(){return this.gridEnabled};\nmxGraph.prototype.setGridEnabled=function(a){this.gridEnabled=a};mxGraph.prototype.isPortsEnabled=function(){return this.portsEnabled};mxGraph.prototype.setPortsEnabled=function(a){this.portsEnabled=a};mxGraph.prototype.getGridSize=function(){return this.gridSize};mxGraph.prototype.setGridSize=function(a){this.gridSize=a};mxGraph.prototype.getTolerance=function(){return this.tolerance};mxGraph.prototype.setTolerance=function(a){this.tolerance=a};mxGraph.prototype.isVertexLabelsMovable=function(){return this.vertexLabelsMovable};\nmxGraph.prototype.setVertexLabelsMovable=function(a){this.vertexLabelsMovable=a};mxGraph.prototype.isEdgeLabelsMovable=function(){return this.edgeLabelsMovable};mxGraph.prototype.setEdgeLabelsMovable=function(a){this.edgeLabelsMovable=a};mxGraph.prototype.isSwimlaneNesting=function(){return this.swimlaneNesting};mxGraph.prototype.setSwimlaneNesting=function(a){this.swimlaneNesting=a};mxGraph.prototype.isSwimlaneSelectionEnabled=function(){return this.swimlaneSelectionEnabled};\nmxGraph.prototype.setSwimlaneSelectionEnabled=function(a){this.swimlaneSelectionEnabled=a};mxGraph.prototype.isMultigraph=function(){return this.multigraph};mxGraph.prototype.setMultigraph=function(a){this.multigraph=a};mxGraph.prototype.isAllowLoops=function(){return this.allowLoops};mxGraph.prototype.setAllowDanglingEdges=function(a){this.allowDanglingEdges=a};mxGraph.prototype.isAllowDanglingEdges=function(){return this.allowDanglingEdges};\nmxGraph.prototype.setConnectableEdges=function(a){this.connectableEdges=a};mxGraph.prototype.isConnectableEdges=function(){return this.connectableEdges};mxGraph.prototype.setCloneInvalidEdges=function(a){this.cloneInvalidEdges=a};mxGraph.prototype.isCloneInvalidEdges=function(){return this.cloneInvalidEdges};mxGraph.prototype.setAllowLoops=function(a){this.allowLoops=a};mxGraph.prototype.isDisconnectOnMove=function(){return this.disconnectOnMove};\nmxGraph.prototype.setDisconnectOnMove=function(a){this.disconnectOnMove=a};mxGraph.prototype.isDropEnabled=function(){return this.dropEnabled};mxGraph.prototype.setDropEnabled=function(a){this.dropEnabled=a};mxGraph.prototype.isSplitEnabled=function(){return this.splitEnabled};mxGraph.prototype.setSplitEnabled=function(a){this.splitEnabled=a};mxGraph.prototype.getResizableCells=function(a){return this.model.filterCells(a,mxUtils.bind(this,function(b){return this.isCellResizable(b)}))};\nmxGraph.prototype.isCellResizable=function(a){var b=this.getCurrentCellStyle(a);return this.isCellsResizable()&&!this.isCellLocked(a)&&\"0\"!=mxUtils.getValue(b,mxConstants.STYLE_RESIZABLE,\"1\")};mxGraph.prototype.isCellsResizable=function(){return this.cellsResizable};mxGraph.prototype.setCellsResizable=function(a){this.cellsResizable=a};mxGraph.prototype.isTerminalPointMovable=function(a,b){return!0};\nmxGraph.prototype.isCellBendable=function(a){var b=this.getCurrentCellStyle(a);return this.isCellsBendable()&&!this.isCellLocked(a)&&0!=b[mxConstants.STYLE_BENDABLE]};mxGraph.prototype.isCellsBendable=function(){return this.cellsBendable};mxGraph.prototype.setCellsBendable=function(a){this.cellsBendable=a};mxGraph.prototype.getEditableCells=function(a){return this.model.filterCells(a,mxUtils.bind(this,function(b){return this.isCellEditable(b)}))};\nmxGraph.prototype.isCellEditable=function(a){var b=this.getCurrentCellStyle(a);return this.isCellsEditable()&&!this.isCellLocked(a)&&0!=b[mxConstants.STYLE_EDITABLE]};mxGraph.prototype.isCellsEditable=function(){return this.cellsEditable};mxGraph.prototype.setCellsEditable=function(a){this.cellsEditable=a};mxGraph.prototype.isCellDisconnectable=function(a,b,c){return this.isCellsDisconnectable()&&!this.isCellLocked(a)};mxGraph.prototype.isCellsDisconnectable=function(){return this.cellsDisconnectable};\nmxGraph.prototype.setCellsDisconnectable=function(a){this.cellsDisconnectable=a};mxGraph.prototype.isValidSource=function(a){return null==a&&this.allowDanglingEdges||null!=a&&(!this.model.isEdge(a)||this.connectableEdges)&&this.isCellConnectable(a)};mxGraph.prototype.isValidTarget=function(a){return this.isValidSource(a)};mxGraph.prototype.isValidConnection=function(a,b){return this.isValidSource(a)&&this.isValidTarget(b)};mxGraph.prototype.setConnectable=function(a){this.connectionHandler.setEnabled(a)};\nmxGraph.prototype.isConnectable=function(){return this.connectionHandler.isEnabled()};mxGraph.prototype.setTooltips=function(a){this.tooltipHandler.setEnabled(a)};mxGraph.prototype.setPanning=function(a){this.panningHandler.panningEnabled=a};mxGraph.prototype.isEditing=function(a){if(null!=this.cellEditor){var b=this.cellEditor.getEditingCell();return null==a?null!=b:a==b}return!1};mxGraph.prototype.isAutoSizeCell=function(a){a=this.getCurrentCellStyle(a);return this.isAutoSizeCells()||1==a[mxConstants.STYLE_AUTOSIZE]};\nmxGraph.prototype.isAutoSizeCells=function(){return this.autoSizeCells};mxGraph.prototype.setAutoSizeCells=function(a){this.autoSizeCells=a};mxGraph.prototype.isExtendParent=function(a){return!this.getModel().isEdge(a)&&this.isExtendParents()};mxGraph.prototype.isExtendParents=function(){return this.extendParents};mxGraph.prototype.setExtendParents=function(a){this.extendParents=a};mxGraph.prototype.isExtendParentsOnAdd=function(a){return this.extendParentsOnAdd};\nmxGraph.prototype.setExtendParentsOnAdd=function(a){this.extendParentsOnAdd=a};mxGraph.prototype.isExtendParentsOnMove=function(){return this.extendParentsOnMove};mxGraph.prototype.setExtendParentsOnMove=function(a){this.extendParentsOnMove=a};mxGraph.prototype.isContractParent=function(a){return!this.getModel().isEdge(a)&&this.isContractParents()};mxGraph.prototype.isContractParents=function(){return this.contractParents};mxGraph.prototype.setContractParents=function(a){this.contractParents=a};\nmxGraph.prototype.isContractParentsOnRemove=function(){return this.contractParentsOnRemove};mxGraph.prototype.setContractParentsOnRemove=function(a){this.contractParentsOnRemove=a};mxGraph.prototype.isContractParentsOnMove=function(){return this.contractParentsOnMove};mxGraph.prototype.setContractParentsOnMove=function(a){this.contractParentsOnMove=a};mxGraph.prototype.isRecursiveResize=function(a){return this.recursiveResize};\nmxGraph.prototype.setRecursiveResize=function(a){this.recursiveResize=a};mxGraph.prototype.isConstrainChild=function(a){return this.isConstrainChildren()&&!this.getModel().isEdge(this.getModel().getParent(a))};mxGraph.prototype.isConstrainChildren=function(){return this.constrainChildren};mxGraph.prototype.setConstrainChildren=function(a){this.constrainChildren=a};mxGraph.prototype.isConstrainRelativeChildren=function(){return this.constrainRelativeChildren};\nmxGraph.prototype.setConstrainRelativeChildren=function(a){this.constrainRelativeChildren=a};mxGraph.prototype.isAllowNegativeCoordinates=function(){return this.allowNegativeCoordinates};mxGraph.prototype.setAllowNegativeCoordinates=function(a){this.allowNegativeCoordinates=a};mxGraph.prototype.getOverlap=function(a){return this.isAllowOverlapParent(a)?this.defaultOverlap:0};mxGraph.prototype.isAllowOverlapParent=function(a){return!1};\nmxGraph.prototype.getFoldableCells=function(a,b){return this.model.filterCells(a,mxUtils.bind(this,function(c){return this.isCellFoldable(c,b)}))};mxGraph.prototype.isCellFoldable=function(a,b){b=this.getCurrentCellStyle(a);return 0mxUtils.indexOf(a,g);)g=this.model.getParent(g);return this.model.isLayer(c)||null!=g?null:c};mxGraph.prototype.getDefaultParent=function(){var a=this.getCurrentRoot();null==a&&(a=this.defaultParent,null==a&&(a=this.model.getRoot(),a=this.model.getChildAt(a,0)));return a};mxGraph.prototype.setDefaultParent=function(a){this.defaultParent=a};mxGraph.prototype.getSwimlane=function(a){for(;null!=a&&!this.isSwimlane(a);)a=this.model.getParent(a);return a};\nmxGraph.prototype.getSwimlaneAt=function(a,b,c){null==c&&(c=this.getCurrentRoot(),null==c&&(c=this.model.getRoot()));if(null!=c)for(var d=this.model.getChildCount(c),e=0;ea.width*e||0a.height*e)return!0}return!1};mxGraph.prototype.getChildVertices=function(a){return this.getChildCells(a,!0,!1)};mxGraph.prototype.getChildEdges=function(a){return this.getChildCells(a,!1,!0)};\nmxGraph.prototype.getChildCells=function(a,b,c){a=null!=a?a:this.getDefaultParent();a=this.model.getChildCells(a,null!=b?b:!1,null!=c?c:!1);b=[];for(c=0;c=a&&u.y+u.height<=p&&u.y>=b&&u.x+u.width<=n)&&f.push(t);v&&!l||this.getCells(a,b,c,d,t,f,g,k,l)}}}return f};mxGraph.prototype.getCellsBeyond=function(a,b,c,d,e){var f=[];if(d||e)if(null==c&&(c=this.getDefaultParent()),null!=c)for(var g=this.model.getChildCount(c),k=0;k=a)&&(!e||m.y>=b)&&f.push(l)}return f};\nmxGraph.prototype.findTreeRoots=function(a,b,c){b=null!=b?b:!1;c=null!=c?c:!1;var d=[];if(null!=a){for(var e=this.getModel(),f=e.getChildCount(a),g=null,k=0,l=0;lk&&(k=n,g=m)}}0==d.length&&null!=g&&d.push(g)}return d};\nmxGraph.prototype.traverse=function(a,b,c,d,e,f){if(null!=c&&null!=a&&(b=null!=b?b:!0,f=null!=f?f:!1,e=e||new mxDictionary,null==d||!e.get(d))&&(e.put(d,!0),d=c(a,d),null==d||d)&&(d=this.model.getEdgeCount(a),0b?f-1:b)),this.setSelectionCell(a)):this.getCurrentRoot()!=d&&this.setSelectionCell(d)};mxGraph.prototype.selectAll=function(a,b){a=a||this.getDefaultParent();b=b?this.model.filterDescendants(mxUtils.bind(this,function(c){return c!=a&&null!=this.view.getState(c)}),a):this.model.getChildren(a);null!=b&&this.setSelectionCells(b)};mxGraph.prototype.selectVertices=function(a,b){this.selectCells(!0,!1,a,b)};\nmxGraph.prototype.selectEdges=function(a){this.selectCells(!1,!0,a)};mxGraph.prototype.selectCells=function(a,b,c,d){c=c||this.getDefaultParent();var e=mxUtils.bind(this,function(f){return null!=this.view.getState(f)&&((d||0==this.model.getChildCount(f))&&this.model.isVertex(f)&&a&&!this.model.isEdge(this.model.getParent(f))||this.model.isEdge(f)&&b)});c=this.model.filterDescendants(e,c);null!=c&&this.setSelectionCells(c)};\nmxGraph.prototype.selectCellForEvent=function(a,b){var c=this.isCellSelected(a);this.isToggleEvent(b)?c?this.removeSelectionCell(a):this.addSelectionCell(a):c&&1==this.getSelectionCount()||this.setSelectionCell(a)};mxGraph.prototype.selectCellsForEvent=function(a,b){this.isToggleEvent(b)?this.addSelectionCells(a):this.setSelectionCells(a)};\nmxGraph.prototype.createHandler=function(a){var b=null;if(null!=a)if(this.model.isEdge(a.cell)){b=a.getVisibleTerminalState(!0);var c=a.getVisibleTerminalState(!1),d=this.getCellGeometry(a.cell);b=this.view.getEdgeStyle(a,null!=d?d.points:null,b,c);b=this.createEdgeHandler(a,b)}else b=this.createVertexHandler(a);return b};mxGraph.prototype.createVertexHandler=function(a){return new mxVertexHandler(a)};\nmxGraph.prototype.createEdgeHandler=function(a,b){return b==mxEdgeStyle.Loop||b==mxEdgeStyle.ElbowConnector||b==mxEdgeStyle.SideToSide||b==mxEdgeStyle.TopToBottom?this.createElbowEdgeHandler(a):b==mxEdgeStyle.SegmentConnector||b==mxEdgeStyle.OrthConnector?this.createEdgeSegmentHandler(a):new mxEdgeHandler(a)};mxGraph.prototype.createEdgeSegmentHandler=function(a){return new mxEdgeSegmentHandler(a)};mxGraph.prototype.createElbowEdgeHandler=function(a){return new mxElbowEdgeHandler(a)};\nmxGraph.prototype.addMouseListener=function(a){null==this.mouseListeners&&(this.mouseListeners=[]);this.mouseListeners.push(a)};mxGraph.prototype.removeMouseListener=function(a){if(null!=this.mouseListeners)for(var b=0;bthis.doubleClickCounter){if(this.doubleClickCounter++,d=!1,a==mxEvent.MOUSE_UP?b.getCell()==this.lastTouchCell&&null!=this.lastTouchCell&&(this.lastTouchTime=0,d=this.lastTouchCell,this.lastTouchCell=null,this.dblClick(b.getEvent(),d),d=!0):(this.fireDoubleClick=!0,this.lastTouchTime=0),d){mxEvent.consume(b.getEvent());\nreturn}}else{if(null==this.lastTouchEvent||this.lastTouchEvent!=b.getEvent())this.lastTouchCell=b.getCell(),this.lastTouchX=b.getX(),this.lastTouchY=b.getY(),this.lastTouchTime=d,this.lastTouchEvent=b.getEvent(),this.doubleClickCounter=0}else if((this.isMouseDown||a==mxEvent.MOUSE_UP)&&this.fireDoubleClick){this.fireDoubleClick=!1;d=this.lastTouchCell;this.lastTouchCell=null;this.isMouseDown=!1;(null!=d||(mxEvent.isTouchEvent(b.getEvent())||mxEvent.isPenEvent(b.getEvent()))&&(mxClient.IS_GC||mxClient.IS_SF))&&\nMath.abs(this.lastTouchX-b.getX())=this.max)||!this.source&&(0==this.max||f>=this.max))&&(g+=this.countError+\"\\n\"),null!=this.validNeighbors&&null!=this.typeError&&0this.graph.model.getChildCount(g),l=new mxDictionary;a=this.graph.getOpposites(this.graph.getEdges(this.cell),this.cell);for(b=0;b=this.cellCount&&!this.livePreviewActive&&this.allowLivePreview?this.cloning&&this.livePreviewActive||(this.livePreviewUsed=this.livePreviewActive=!0):this.livePreviewUsed||null!=this.shape||(this.shape=this.createPreviewShape(this.bounds))};\nmxGraphHandler.prototype.mouseMove=function(a,b){a=this.graph;var c=a.tolerance;if(null==this.first&&this.delayedSelection&&null!=this.cell&&null!=this.mouseDownX&&null!=this.mouseDownY&&(Math.abs(this.mouseDownX-b.getX())>c||Math.abs(this.mouseDownY-b.getY())>c)){this.delayedSelection=!1;this.cellWasClicked=!0;this.graph.isCellSelected(this.cell)||mxEvent.isAltDown(b.getEvent())||(this.graph.isToggleEvent(b.getEvent())?a.addSelectionCell(this.cell):this.graph.isAncestorSelected(this.cell)||a.setSelectionCell(this.cell));\nvar d=a.getSelectionCells();this.graph.isToggleEvent(b.getEvent())&&mxEvent.isAltDown(b.getEvent())&&!a.isSelectionEmpty()||(d=d.concat(this.cell));this.start(this.cell,this.mouseDownX,this.mouseDownY,this.getCells(null,d))}d=null!=this.first?this.getDelta(b):null;if(b.isConsumed()||!a.isMouseDown||null==this.cell||null==d||null==this.bounds||this.suspended)!this.isMoveEnabled()&&!this.isCloneEnabled()||!this.updateCursor||b.isConsumed()||null==b.getState()&&null==b.sourceState||a.isMouseDown||(d=\na.getCursorForMouseEvent(b),null==d&&a.isEnabled()&&a.isCellMovable(b.getCell())&&(d=a.getModel().isEdge(b.getCell())?mxConstants.CURSOR_MOVABLE_EDGE:mxConstants.CURSOR_MOVABLE_VERTEX),null!=d&&null!=b.sourceState&&b.sourceState.setCursor(d));else if(mxEvent.isMultiTouchEvent(b.getEvent()))this.reset();else{if(null!=this.shape||this.livePreviewActive||this.cloning||Math.abs(d.x)>c||Math.abs(d.y)>c){null==this.highlight&&(this.highlight=new mxCellHighlight(this.graph,mxConstants.DROP_TARGET_COLOR,\n3));c=a.isCloneEvent(b.getEvent())&&a.isCellsCloneable()&&this.isCloneEnabled();var e=a.isGridEnabledEvent(b.getEvent()),f=b.getCell();f=null!=f&&0>mxUtils.indexOf(this.cells,f)?f:a.getCellAt(b.getGraphX(),b.getGraphY(),null,null,null,mxUtils.bind(this,function(n,p,q){return 0<=mxUtils.indexOf(this.cells,n.cell)}));var g=!0,k=null;this.cloning=c;a.isDropEnabled()&&this.highlightEnabled&&(k=a.getDropTarget(this.cells,b.getEvent(),f,c));var l=a.getView().getState(k),m=!1;null!=l&&(c||this.isValidDropTarget(k,\nb))?(this.target!=k&&(this.target=k,this.setHighlightColor(mxConstants.DROP_TARGET_COLOR)),m=!0):(this.target=null,this.connectOnDrop&&null!=f&&1==this.cells.length&&a.getModel().isVertex(f)&&a.isCellConnectable(f)&&(l=a.getView().getState(f),null!=l&&(f=null==a.getEdgeValidationError(null,this.cell,f)?mxConstants.VALID_COLOR:mxConstants.INVALID_CONNECT_TARGET_COLOR,this.setHighlightColor(f),m=!0)));null!=l&&m?this.highlight.highlight(l):this.highlight.hide();null!=this.guide&&this.useGuidesForEvent(b)?\n(d=this.guide.move(this.bounds,d,e,c),g=!1):d=a.snapDelta(d,this.bounds,!e,!1,!1);null!=this.guide&&g&&this.guide.hide();this.isConstrainedEvent(b)&&(Math.abs(d.x)>Math.abs(d.y)?d.y=0:d.x=0);this.checkPreview();if(this.currentDx!=d.x||this.currentDy!=d.y)this.currentDx=d.x,this.currentDy=d.y,this.updatePreview()}this.updateHint(b);this.consumeMouseEvent(mxEvent.MOUSE_MOVE,b);mxEvent.consume(b.getEvent())}};\nmxGraphHandler.prototype.isConstrainedEvent=function(a){return(null==this.target||this.graph.isCloneEvent(a.getEvent()))&&this.graph.isConstrainedEvent(a.getEvent())};mxGraphHandler.prototype.updatePreview=function(a){this.livePreviewUsed&&!a?null!=this.cells&&(this.setHandlesVisibleForCells(this.graph.selectionCellsHandler.getHandledSelectionCells(),!1),this.updateLivePreview(this.currentDx,this.currentDy)):this.updatePreviewShape()};\nmxGraphHandler.prototype.updatePreviewShape=function(){null!=this.shape&&null!=this.pBounds&&(this.shape.bounds=new mxRectangle(Math.round(this.pBounds.x+this.currentDx),Math.round(this.pBounds.y+this.currentDy),this.pBounds.width,this.pBounds.height),this.shape.redraw())};\nmxGraphHandler.prototype.updateLivePreview=function(a,b){if(!this.suspended){var c=[];null!=this.allCells&&this.allCells.visit(mxUtils.bind(this,function(n,p){n=this.graph.view.getState(p.cell);n!=p&&(p.destroy(),null!=n?this.allCells.put(p.cell,n):this.allCells.remove(p.cell),p=n);if(null!=p&&(n=p.clone(),c.push([p,n]),null!=p.shape&&(null==p.shape.originalPointerEvents&&(p.shape.originalPointerEvents=p.shape.pointerEvents),p.shape.pointerEvents=!1,null!=p.text&&(null==p.text.originalPointerEvents&&\n(p.text.originalPointerEvents=p.text.pointerEvents),p.text.pointerEvents=!1)),this.graph.model.isVertex(p.cell))){if(!this.cloning||this.graph.isCellCloneable(p.cell))p.x+=a,p.y+=b;this.cloning?null!=p.text&&(p.text.updateBoundingBox(),null!=p.text.boundingBox&&(p.text.boundingBox.x+=a,p.text.boundingBox.y+=b),null!=p.text.unrotatedBoundingBox&&(p.text.unrotatedBoundingBox.x+=a,p.text.unrotatedBoundingBox.y+=b)):(p.view.graph.cellRenderer.redraw(p,!0),p.view.invalidate(p.cell),p.invalid=!1,null!=\np.control&&null!=p.control.node&&(p.control.node.style.visibility=\"hidden\"))}}));if(0==c.length)this.reset();else{for(var d=this.graph.view.scale,e=0;ethis.graph.tolerance||Math.abs(this.dy)>this.graph.tolerance,!a&&this.active&&this.fireEvent(new mxEventObject(mxEvent.PAN_START,\n\"event\",b)));(this.active||this.panningTrigger)&&b.consume()};mxPanningHandler.prototype.mouseUp=function(a,b){if(this.active){if(null!=this.dx&&null!=this.dy){if(!this.graph.useScrollbarsForPanning||!mxUtils.hasScrollbars(this.graph.container)){a=this.graph.getView().scale;var c=this.graph.getView().translate;this.graph.panGraph(0,0);this.panGraph(c.x+this.dx/a,c.y+this.dy/a)}b.consume()}this.fireEvent(new mxEventObject(mxEvent.PAN_END,\"event\",b))}this.reset()};\nmxPanningHandler.prototype.zoomGraph=function(a){var b=Math.round(this.initialScale*a.scale*100)/100;null!=this.minScale&&(b=Math.max(this.minScale,b));null!=this.maxScale&&(b=Math.min(this.maxScale,b));this.graph.view.scale!=b&&(this.graph.zoomTo(b),mxEvent.consume(a))};mxPanningHandler.prototype.reset=function(){this.panningTrigger=this.graph.isMouseDown=!1;this.mouseDownEvent=null;this.active=!1;this.dy=this.dx=null};\nmxPanningHandler.prototype.panGraph=function(a,b){this.graph.getView().setTranslate(a,b)};mxPanningHandler.prototype.destroy=function(){this.graph.removeMouseListener(this);this.graph.removeListener(this.forcePanningHandler);this.graph.removeListener(this.gestureHandler);mxEvent.removeGestureListeners(document,null,null,this.mouseUpListener);mxEvent.removeListener(document,\"mouseleave\",this.mouseUpListener)};\nfunction mxPopupMenuHandler(a,b){null!=a&&(this.graph=a,this.factoryMethod=b,this.graph.addMouseListener(this),this.gestureHandler=mxUtils.bind(this,function(c,d){this.inTolerance=!1}),this.graph.addListener(mxEvent.GESTURE,this.gestureHandler),this.init())}mxPopupMenuHandler.prototype=new mxPopupMenu;mxPopupMenuHandler.prototype.constructor=mxPopupMenuHandler;mxPopupMenuHandler.prototype.graph=null;mxPopupMenuHandler.prototype.selectOnPopup=!0;\nmxPopupMenuHandler.prototype.clearSelectionOnBackground=!0;mxPopupMenuHandler.prototype.triggerX=null;mxPopupMenuHandler.prototype.triggerY=null;mxPopupMenuHandler.prototype.screenX=null;mxPopupMenuHandler.prototype.screenY=null;mxPopupMenuHandler.prototype.init=function(){mxPopupMenu.prototype.init.apply(this);mxEvent.addGestureListeners(this.div,mxUtils.bind(this,function(a){this.graph.tooltipHandler.hide()}))};\nmxPopupMenuHandler.prototype.getTriggerPoint=function(){var a=this.graph.view.scale,b=this.graph.view.translate;return new mxPoint(this.triggerX/a-b.x,this.triggerY/a-b.y)};mxPopupMenuHandler.prototype.isSelectOnPopup=function(a){return this.selectOnPopup};\nmxPopupMenuHandler.prototype.mouseDown=function(a,b){this.isEnabled()&&!mxEvent.isMultiTouchEvent(b.getEvent())&&(this.hideMenu(),this.triggerX=b.getGraphX(),this.triggerY=b.getGraphY(),this.screenX=mxEvent.getMainEvent(b.getEvent()).screenX,this.screenY=mxEvent.getMainEvent(b.getEvent()).screenY,this.popupTrigger=this.isPopupTrigger(b),this.inTolerance=!0)};\nmxPopupMenuHandler.prototype.mouseMove=function(a,b){this.inTolerance&&null!=this.screenX&&null!=this.screenY&&(Math.abs(mxEvent.getMainEvent(b.getEvent()).screenX-this.screenX)>this.graph.tolerance||Math.abs(mxEvent.getMainEvent(b.getEvent()).screenY-this.screenY)>this.graph.tolerance)&&(this.inTolerance=!1)};\nmxPopupMenuHandler.prototype.mouseUp=function(a,b,c){a=null==c;c=null!=c?c:mxUtils.bind(this,function(e){var f=mxUtils.getScrollOrigin();this.popup(b.getX()+f.x+1,b.getY()+f.y+1,e,b.getEvent())});if(this.popupTrigger&&this.inTolerance&&null!=this.triggerX&&null!=this.triggerY){var d=this.getCellForPopupEvent(b);this.graph.isEnabled()&&this.isSelectOnPopup(b)&&null!=d&&!this.graph.isCellSelected(d)?this.graph.setSelectionCell(d):this.clearSelectionOnBackground&&null==d&&this.graph.clearSelection();\nthis.graph.tooltipHandler.hide();c(d);a&&b.consume()}this.inTolerance=this.popupTrigger=!1};mxPopupMenuHandler.prototype.getCellForPopupEvent=function(a){return a.getCell()};mxPopupMenuHandler.prototype.destroy=function(){this.graph.removeMouseListener(this);this.graph.removeListener(this.gestureHandler);mxPopupMenu.prototype.destroy.apply(this)};\nfunction mxCellMarker(a,b,c,d){mxEventSource.call(this);null!=a&&(this.graph=a,this.validColor=null!=b?b:mxConstants.DEFAULT_VALID_COLOR,this.invalidColor=null!=c?c:mxConstants.DEFAULT_INVALID_COLOR,this.hotspot=null!=d?d:mxConstants.DEFAULT_HOTSPOT,this.highlight=new mxCellHighlight(a))}mxUtils.extend(mxCellMarker,mxEventSource);mxCellMarker.prototype.graph=null;mxCellMarker.prototype.enabled=!0;mxCellMarker.prototype.hotspot=mxConstants.DEFAULT_HOTSPOT;mxCellMarker.prototype.hotspotEnabled=!1;\nmxCellMarker.prototype.validColor=null;mxCellMarker.prototype.invalidColor=null;mxCellMarker.prototype.currentColor=null;mxCellMarker.prototype.validState=null;mxCellMarker.prototype.markedState=null;mxCellMarker.prototype.setEnabled=function(a){this.enabled=a};mxCellMarker.prototype.isEnabled=function(){return this.enabled};mxCellMarker.prototype.setHotspot=function(a){this.hotspot=a};mxCellMarker.prototype.getHotspot=function(){return this.hotspot};\nmxCellMarker.prototype.setHotspotEnabled=function(a){this.hotspotEnabled=a};mxCellMarker.prototype.isHotspotEnabled=function(){return this.hotspotEnabled};mxCellMarker.prototype.hasValidState=function(){return null!=this.validState};mxCellMarker.prototype.getValidState=function(){return this.validState};mxCellMarker.prototype.getMarkedState=function(){return this.markedState};mxCellMarker.prototype.reset=function(){this.validState=null;null!=this.markedState&&(this.markedState=null,this.unmark())};\nmxCellMarker.prototype.process=function(a){var b=null;this.isEnabled()&&(b=this.getState(a),this.setCurrentState(b,a));return b};mxCellMarker.prototype.setCurrentState=function(a,b,c){var d=null!=a?this.isValidState(a):!1;c=null!=c?c:this.getMarkerColor(b.getEvent(),a,d);this.validState=d?a:null;if(a!=this.markedState||c!=this.currentColor)this.currentColor=c,null!=a&&null!=this.currentColor?(this.markedState=a,this.mark()):null!=this.markedState&&(this.markedState=null,this.unmark())};\nmxCellMarker.prototype.markCell=function(a,b){a=this.graph.getView().getState(a);null!=a&&(this.currentColor=null!=b?b:this.validColor,this.markedState=a,this.mark())};mxCellMarker.prototype.mark=function(){this.highlight.setHighlightColor(this.currentColor);this.highlight.highlight(this.markedState);this.fireEvent(new mxEventObject(mxEvent.MARK,\"state\",this.markedState))};mxCellMarker.prototype.unmark=function(){this.mark()};mxCellMarker.prototype.isValidState=function(a){return!0};\nmxCellMarker.prototype.getMarkerColor=function(a,b,c){return c?this.validColor:this.invalidColor};mxCellMarker.prototype.getState=function(a){var b=this.graph.getView(),c=this.getCell(a);b=this.getStateToMark(b.getState(c));return null!=b&&this.intersects(b,a)?b:null};mxCellMarker.prototype.getCell=function(a){return a.getCell()};mxCellMarker.prototype.getStateToMark=function(a){return a};\nmxCellMarker.prototype.intersects=function(a,b){return this.hotspotEnabled?mxUtils.intersectsHotspot(a,b.getGraphX(),b.getGraphY(),this.hotspot,mxConstants.MIN_HOTSPOT_SIZE,mxConstants.MAX_HOTSPOT_SIZE):!0};mxCellMarker.prototype.destroy=function(){this.highlight.destroy()};\nfunction mxSelectionCellsHandler(a){mxEventSource.call(this);this.graph=a;this.handlers=new mxDictionary;this.graph.addMouseListener(this);this.redrawHandler=mxUtils.bind(this,function(b,c){this.isEnabled()&&this.refresh(!1)});this.refreshHandler=mxUtils.bind(this,function(b,c){this.isEnabled()&&this.refresh(!0)});this.graph.addListener(mxEvent.EDITING_STOPPED,this.redrawHandler);this.graph.addListener(mxEvent.EDITING_STARTED,this.redrawHandler);this.graph.getSelectionModel().addListener(mxEvent.CHANGE,\nthis.redrawHandler);this.graph.getModel().addListener(mxEvent.CHANGE,this.refreshHandler);this.graph.getView().addListener(mxEvent.SCALE,this.refreshHandler);this.graph.getView().addListener(mxEvent.TRANSLATE,this.refreshHandler);this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE,this.refreshHandler);this.graph.getView().addListener(mxEvent.DOWN,this.refreshHandler);this.graph.getView().addListener(mxEvent.UP,this.refreshHandler)}mxUtils.extend(mxSelectionCellsHandler,mxEventSource);\nmxSelectionCellsHandler.prototype.graph=null;mxSelectionCellsHandler.prototype.enabled=!0;mxSelectionCellsHandler.prototype.refreshHandler=null;mxSelectionCellsHandler.prototype.maxHandlers=100;mxSelectionCellsHandler.prototype.handlers=null;mxSelectionCellsHandler.prototype.isEnabled=function(){return this.enabled};mxSelectionCellsHandler.prototype.setEnabled=function(a){this.enabled=a};mxSelectionCellsHandler.prototype.getHandler=function(a){return this.handlers.get(a)};\nmxSelectionCellsHandler.prototype.isHandled=function(a){return null!=this.getHandler(a)};mxSelectionCellsHandler.prototype.reset=function(){this.handlers.visit(function(a,b){b.reset.apply(b)})};mxSelectionCellsHandler.prototype.getHandledSelectionCells=function(){return this.graph.getSelectionCells()};\nmxSelectionCellsHandler.prototype.refresh=function(a){var b=this.handlers;this.handlers=new mxDictionary;var c=mxUtils.sortCells(this.getHandledSelectionCells(),!1);if(!a&&0this.graph.tolerance||Math.abs(b.getGraphY()-this.first.y)>this.graph.tolerance)&&this.updateCurrentState(b,a);if(null!=this.first){var e=null;c=a;null!=this.constraintHandler.currentConstraint&&null!=this.constraintHandler.currentFocus&&null!=this.constraintHandler.currentPoint?(e=this.constraintHandler.currentConstraint,\nc=this.constraintHandler.currentPoint.clone()):null!=this.previous&&mxEvent.isShiftDown(b.getEvent())&&!this.graph.isIgnoreTerminalEvent(b.getEvent())&&(d=new mxPoint(this.previous.getCenterX(),this.previous.getCenterY()),null!=this.sourceConstraint&&(d=this.first),Math.abs(d.x-a.x)this.graph.tolerance||f>this.graph.tolerance)&&(this.shape=this.createShape(),null!=this.edgeState&&this.shape.apply(this.edgeState),this.updateCurrentState(b,a));null!=this.shape&&(null!=this.edgeState?this.shape.points=this.edgeState.absolutePoints:(a=[d],null!=this.waypoints&&(a=a.concat(this.waypoints)),a.push(c),this.shape.points=a),this.drawPreview());\nnull!=this.cursor&&(this.graph.container.style.cursor=this.cursor);mxEvent.consume(b.getEvent());b.consume()}else this.isEnabled()&&this.graph.isEnabled()?this.previous!=this.currentState&&null==this.edgeState?(this.destroyIcons(),null!=this.currentState&&null==this.error&&null==this.constraintHandler.currentConstraint&&(this.icons=this.createIcons(this.currentState),null==this.icons&&(this.currentState.setCursor(mxConstants.CURSOR_CONNECT),b.consume())),this.previous=this.currentState):this.previous!=\nthis.currentState||null==this.currentState||null!=this.icons||this.graph.isMouseDown||b.consume():this.constraintHandler.reset();if(!this.graph.isMouseDown&&null!=this.currentState&&null!=this.icons){a=!1;c=b.getSource();for(d=0;dthis.graph.tolerance||b>this.graph.tolerance))null==this.waypoints&&(this.waypoints=[]),c=this.graph.view.scale,b=new mxPoint(this.graph.snap(a.getGraphX()/c)*c,this.graph.snap(a.getGraphY()/c)*c),this.waypoints.push(b)};\nmxConnectionHandler.prototype.checkConstraints=function(a,b){return null==a||null==b||null==a.point||null==b.point||!a.point.equals(b.point)||a.dx!=b.dx||a.dy!=b.dy||a.perimeter!=b.perimeter};\nmxConnectionHandler.prototype.mouseUp=function(a,b){if(!b.isConsumed()&&this.isConnecting()){if(this.waypointsEnabled&&!this.isStopEvent(b)){this.addWaypointForEvent(b);b.consume();return}a=this.sourceConstraint;var c=this.constraintHandler.currentConstraint,d=null!=this.previous?this.previous.cell:null,e=null;null!=this.constraintHandler.currentConstraint&&null!=this.constraintHandler.currentFocus&&(e=this.constraintHandler.currentFocus.cell);null==e&&null!=this.currentState&&(e=this.currentState.cell);\nnull!=this.error||null!=d&&null!=e&&d==e&&!this.checkConstraints(a,c)?(null!=this.previous&&null!=this.marker.validState&&this.previous.cell==this.marker.validState.cell&&this.graph.selectCellForEvent(this.marker.source,b.getEvent()),null!=this.error&&0f||Math.abs(e)>f)null==this.div&&(this.div=this.createShape()),mxUtils.clearSelection(),this.update(a,c),b.consume()}};\nmxRubberband.prototype.createShape=function(){null==this.sharedDiv&&(this.sharedDiv=document.createElement(\"div\"),this.sharedDiv.className=\"mxRubberband\",mxUtils.setOpacity(this.sharedDiv,this.defaultOpacity));this.graph.container.appendChild(this.sharedDiv);var a=this.sharedDiv;mxClient.IS_SVG&&this.fadeOut&&(this.sharedDiv=null);return a};mxRubberband.prototype.isActive=function(a,b){return null!=this.div&&\"none\"!=this.div.style.display};\nmxRubberband.prototype.mouseUp=function(a,b){a=this.isActive();this.reset();a&&(this.execute(b.getEvent()),b.consume())};mxRubberband.prototype.execute=function(a){var b=new mxRectangle(this.x,this.y,this.width,this.height);this.graph.selectRegion(b,a)};\nmxRubberband.prototype.reset=function(){if(null!=this.div)if(mxClient.IS_SVG&&this.fadeOut){var a=this.div;mxUtils.setPrefixedStyle(a.style,\"transition\",\"all 0.2s linear\");a.style.pointerEvents=\"none\";a.style.opacity=0;window.setTimeout(function(){a.parentNode.removeChild(a)},200)}else this.div.parentNode.removeChild(this.div);mxEvent.removeGestureListeners(document,null,this.dragHandler,this.dropHandler);this.dropHandler=this.dragHandler=null;this.currentY=this.currentX=0;this.div=this.first=null;\nthis.fireEvent(new mxEventObject(mxEvent.RESET))};mxRubberband.prototype.update=function(a,b){this.currentX=a;this.currentY=b;this.repaint();this.fireEvent(new mxEventObject(mxEvent.UPDATE,\"x\",this.x,\"y\",this.y,\"width\",this.width,\"height\",this.height))};\nmxRubberband.prototype.repaint=function(){if(null!=this.div){var a=this.currentX-this.graph.panDx,b=this.currentY-this.graph.panDy;this.x=Math.min(this.first.x,a);this.y=Math.min(this.first.y,b);this.width=Math.max(this.first.x,a)-this.x;this.height=Math.max(this.first.y,b)-this.y;this.div.style.left=this.x+0+\"px\";this.div.style.top=this.y+0+\"px\";this.div.style.width=Math.max(1,this.width)+\"px\";this.div.style.height=Math.max(1,this.height)+\"px\"}};\nmxRubberband.prototype.destroy=function(){this.destroyed||(this.destroyed=!0,this.graph.removeMouseListener(this),this.graph.removeListener(this.forceRubberbandHandler),this.graph.removeListener(this.panHandler),this.reset(),null!=this.sharedDiv&&(this.sharedDiv=null))};function mxHandle(a,b,c,d){this.graph=a.view.graph;this.state=a;this.cursor=null!=b?b:this.cursor;this.image=null!=c?c:this.image;this.shape=null!=d?d:null;this.init()}mxHandle.prototype.cursor=\"default\";mxHandle.prototype.image=null;\nmxHandle.prototype.ignoreGrid=!1;mxHandle.prototype.getPosition=function(a){};mxHandle.prototype.setPosition=function(a,b,c){};mxHandle.prototype.execute=function(a){};mxHandle.prototype.copyStyle=function(a){this.graph.setCellStyles(a,this.state.style[a],[this.state.cell])};\nmxHandle.prototype.processEvent=function(a){var b=this.graph.view.scale,c=this.graph.view.translate;c=new mxPoint(a.getGraphX()/b-c.x,a.getGraphY()/b-c.y);null!=this.shape&&null!=this.shape.bounds&&(c.x-=this.shape.bounds.width/b/4,c.y-=this.shape.bounds.height/b/4);b=-mxUtils.toRadians(this.getRotation());var d=-mxUtils.toRadians(this.getTotalRotation())-b;c=this.flipPoint(this.rotatePoint(this.snapPoint(this.rotatePoint(c,b),this.ignoreGrid||!this.graph.isGridEnabledEvent(a.getEvent())),d));this.setPosition(this.state.getPaintBounds(),\nc,a);this.redraw()};mxHandle.prototype.positionChanged=function(){null!=this.state.text&&this.state.text.apply(this.state);null!=this.state.shape&&this.graph.cellRenderer.configureShape(this.state);this.graph.cellRenderer.redraw(this.state,!0)};mxHandle.prototype.getRotation=function(){return null!=this.state.shape?this.state.shape.getRotation():0};mxHandle.prototype.getTotalRotation=function(){return null!=this.state.shape?this.state.shape.getShapeRotation():0};\nmxHandle.prototype.init=function(){var a=this.isHtmlRequired();null!=this.image?(this.shape=new mxImageShape(new mxRectangle(0,0,this.image.width,this.image.height),this.image.src),this.shape.preserveImageAspect=!1):null==this.shape&&(this.shape=this.createShape(a));this.initShape(a)};mxHandle.prototype.createShape=function(a){a=new mxRectangle(0,0,mxConstants.HANDLE_SIZE,mxConstants.HANDLE_SIZE);return new mxRectangleShape(a,mxConstants.HANDLE_FILLCOLOR,mxConstants.HANDLE_STROKECOLOR)};\nmxHandle.prototype.initShape=function(a){a&&this.shape.isHtmlAllowed()?(this.shape.dialect=mxConstants.DIALECT_STRICTHTML,this.shape.init(this.graph.container)):(this.shape.dialect=this.graph.dialect!=mxConstants.DIALECT_SVG?mxConstants.DIALECT_MIXEDHTML:mxConstants.DIALECT_SVG,null!=this.cursor&&this.shape.init(this.graph.getView().getOverlayPane()));mxEvent.redirectMouseEvents(this.shape.node,this.graph,this.state);this.shape.node.style.cursor=this.cursor};\nmxHandle.prototype.redraw=function(){if(null!=this.shape&&null!=this.state.shape){var a=this.getPosition(this.state.getPaintBounds());if(null!=a){var b=mxUtils.toRadians(this.getTotalRotation());a=this.rotatePoint(this.flipPoint(a),b);b=this.graph.view.scale;var c=this.graph.view.translate;this.shape.bounds.x=Math.floor((a.x+c.x)*b-this.shape.bounds.width/2);this.shape.bounds.y=Math.floor((a.y+c.y)*b-this.shape.bounds.height/2);this.shape.redraw()}}};\nmxHandle.prototype.isHtmlRequired=function(){return null!=this.state.text&&this.state.text.node.parentNode==this.graph.container};mxHandle.prototype.rotatePoint=function(a,b){var c=this.state.getCellBounds();c=new mxPoint(c.getCenterX(),c.getCenterY());return mxUtils.getRotatedPoint(a,Math.cos(b),Math.sin(b),c)};\nmxHandle.prototype.flipPoint=function(a){if(null!=this.state.shape){var b=this.state.getCellBounds();this.state.shape.flipH&&(a.x=2*b.x+b.width-a.x);this.state.shape.flipV&&(a.y=2*b.y+b.height-a.y)}return a};mxHandle.prototype.snapPoint=function(a,b){b||(a.x=this.graph.snap(a.x),a.y=this.graph.snap(a.y));return a};mxHandle.prototype.setVisible=function(a){null!=this.shape&&null!=this.shape.node&&(this.shape.node.style.display=a?\"\":\"none\")};\nmxHandle.prototype.reset=function(){this.setVisible(!0);this.state.style=this.graph.getCellStyle(this.state.cell);this.positionChanged()};mxHandle.prototype.destroy=function(){null!=this.shape&&(this.shape.destroy(),this.shape=null)};\nfunction mxVertexHandler(a){null!=a&&(this.state=a,this.init(),this.escapeHandler=mxUtils.bind(this,function(b,c){this.livePreview&&null!=this.index&&(this.state.view.graph.cellRenderer.redraw(this.state,!0),this.state.view.invalidate(this.state.cell),this.state.invalid=!1,this.state.view.validate());this.reset()}),this.state.view.graph.addListener(mxEvent.ESCAPE,this.escapeHandler))}mxVertexHandler.prototype.graph=null;mxVertexHandler.prototype.state=null;mxVertexHandler.prototype.singleSizer=!1;\nmxVertexHandler.prototype.index=null;mxVertexHandler.prototype.allowHandleBoundsCheck=!0;mxVertexHandler.prototype.handleImage=null;mxVertexHandler.prototype.handlesVisible=!0;mxVertexHandler.prototype.tolerance=0;mxVertexHandler.prototype.rotationEnabled=!1;mxVertexHandler.prototype.parentHighlightEnabled=!1;mxVertexHandler.prototype.rotationRaster=!0;mxVertexHandler.prototype.rotationCursor=\"crosshair\";mxVertexHandler.prototype.livePreview=!1;mxVertexHandler.prototype.movePreviewToFront=!1;\nmxVertexHandler.prototype.manageSizers=!1;mxVertexHandler.prototype.constrainGroupByChildren=!1;mxVertexHandler.prototype.rotationHandleVSpacing=-16;mxVertexHandler.prototype.horizontalOffset=0;mxVertexHandler.prototype.verticalOffset=0;\nmxVertexHandler.prototype.init=function(){this.graph=this.state.view.graph;this.selectionBounds=this.getSelectionBounds(this.state);this.bounds=new mxRectangle(this.selectionBounds.x,this.selectionBounds.y,this.selectionBounds.width,this.selectionBounds.height);this.selectionBorder=this.createSelectionShape(this.bounds);this.selectionBorder.dialect=mxConstants.DIALECT_SVG;this.selectionBorder.svgStrokeTolerance=0;this.selectionBorder.pointerEvents=!1;this.selectionBorder.rotation=Number(this.state.style[mxConstants.STYLE_ROTATION]||\n\"0\");this.selectionBorder.init(this.graph.getView().getOverlayPane());mxEvent.redirectMouseEvents(this.selectionBorder.node,this.graph,this.state);this.graph.isCellMovable(this.state.cell)&&!this.graph.isCellLocked(this.state.cell)&&this.selectionBorder.setCursor(mxConstants.CURSOR_MOVABLE_VERTEX);this.refresh();this.redraw();this.constrainGroupByChildren&&this.updateMinBounds()};\nmxVertexHandler.prototype.isHandlesVisible=function(){return!this.graph.isCellLocked(this.state.cell)&&(0>=mxGraphHandler.prototype.maxCells||this.graph.getSelectionCount()<=mxGraphHandler.prototype.maxCells)};\nmxVertexHandler.prototype.refresh=function(){null!=this.selectionBorder&&(this.selectionBorder.strokewidth=this.getSelectionStrokeWidth(),this.selectionBorder.isDashed=this.isSelectionDashed(),this.selectionBorder.stroke=this.getSelectionColor(),this.selectionBorder.redraw());null!=this.sizers&&this.destroySizers();this.isHandlesVisible()&&(this.sizers=this.createSizers());null!=this.customHandles&&this.destroyCustomHandles();this.isHandlesVisible()&&(this.customHandles=this.createCustomHandles())};\nmxVertexHandler.prototype.isRotationHandleVisible=function(){return this.graph.isEnabled()&&this.rotationEnabled&&this.graph.isCellRotatable(this.state.cell)};mxVertexHandler.prototype.isConstrainedEvent=function(a){return mxEvent.isShiftDown(a.getEvent())||\"fixed\"==this.state.style[mxConstants.STYLE_ASPECT]};mxVertexHandler.prototype.isCenteredEvent=function(a,b){return!1};mxVertexHandler.prototype.createCustomHandles=function(){return null};\nmxVertexHandler.prototype.updateMinBounds=function(){var a=this.graph.getChildCells(this.state.cell);if(0this.graph.tolerance||Math.abs(a.getGraphY()-this.startY)>this.graph.tolerance)&&(this.inTolerance=!1)};mxVertexHandler.prototype.updateHint=function(a){};mxVertexHandler.prototype.removeHint=function(){};mxVertexHandler.prototype.roundAngle=function(a){return Math.round(10*a)/10};\nmxVertexHandler.prototype.roundLength=function(a){return Math.round(100*a)/100};\nmxVertexHandler.prototype.mouseMove=function(a,b){b.isConsumed()||null==this.index?this.graph.isMouseDown||null==this.getHandleForEvent(b)||b.consume(!1):(this.checkTolerance(b),this.inTolerance||(this.index<=mxEvent.CUSTOM_HANDLE?null!=this.customHandles&&null!=this.customHandles[mxEvent.CUSTOM_HANDLE-this.index]&&(this.customHandles[mxEvent.CUSTOM_HANDLE-this.index].processEvent(b),this.customHandles[mxEvent.CUSTOM_HANDLE-this.index].active=!0,null!=this.ghostPreview?(this.ghostPreview.apply(this.state),\nthis.ghostPreview.strokewidth=this.getSelectionStrokeWidth()/this.ghostPreview.scale/this.ghostPreview.scale,this.ghostPreview.isDashed=this.isSelectionDashed(),this.ghostPreview.stroke=this.getSelectionColor(),this.ghostPreview.redraw(),null!=this.selectionBounds&&(this.selectionBorder.node.style.display=\"none\")):(this.movePreviewToFront&&this.moveToFront(),this.customHandles[mxEvent.CUSTOM_HANDLE-this.index].positionChanged())):this.index==mxEvent.LABEL_HANDLE?this.moveLabel(b):(this.index==mxEvent.ROTATION_HANDLE?\nthis.rotateVertex(b):this.resizeVertex(b),this.updateHint(b))),b.consume())};mxVertexHandler.prototype.isGhostPreview=function(){return 0d?180:0;0a-this.startDist?15:25>a-this.startDist?5:\n1,this.currentAlpha=Math.round(this.currentAlpha/raster)*raster):this.currentAlpha=this.roundAngle(this.currentAlpha);this.selectionBorder.rotation=this.currentAlpha;this.selectionBorder.redraw();this.livePreviewActive&&this.redrawHandles()};\nmxVertexHandler.prototype.resizeVertex=function(a){var b=new mxPoint(this.state.getCenterX(),this.state.getCenterY()),c=mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION]||\"0\"),d=new mxPoint(a.getGraphX(),a.getGraphY()),e=this.graph.view.translate,f=this.graph.view.scale,g=Math.cos(-c),k=Math.sin(-c),l=d.x-this.startX,m=d.y-this.startY;d=k*l+g*m;l=g*l-k*m;m=d;g=this.graph.getCellGeometry(this.state.cell);this.unscaledBounds=this.union(g,l/f,m/f,this.index,this.graph.isGridEnabledEvent(a.getEvent()),\n1,new mxPoint(0,0),this.isConstrainedEvent(a),this.isCenteredEvent(this.state,a));g.relative||(k=this.graph.getMaximumGraphBounds(),null!=k&&null!=this.parentState&&(k=mxRectangle.fromRectangle(k),k.x-=(this.parentState.x-e.x*f)/f,k.y-=(this.parentState.y-e.y*f)/f),this.graph.isConstrainChild(this.state.cell)&&(d=this.graph.getCellContainmentArea(this.state.cell),null!=d&&(l=this.graph.getOverlap(this.state.cell),0k.x+k.width&&(this.unscaledBounds.width-=this.unscaledBounds.x+this.unscaledBounds.width-k.x-k.width),this.unscaledBounds.y+this.unscaledBounds.height>\nk.y+k.height&&(this.unscaledBounds.height-=this.unscaledBounds.y+this.unscaledBounds.height-k.y-k.height)));d=this.bounds;this.bounds=new mxRectangle((null!=this.parentState?this.parentState.x:e.x*f)+this.unscaledBounds.x*f,(null!=this.parentState?this.parentState.y:e.y*f)+this.unscaledBounds.y*f,this.unscaledBounds.width*f,this.unscaledBounds.height*f);g.relative&&null!=this.parentState&&(this.bounds.x+=this.state.x-this.parentState.x,this.bounds.y+=this.state.y-this.parentState.y);g=Math.cos(c);\nk=Math.sin(c);c=new mxPoint(this.bounds.getCenterX(),this.bounds.getCenterY());l=c.x-b.x;m=c.y-b.y;b=g*l-k*m-l;c=k*l+g*m-m;l=this.bounds.x-this.state.x;m=this.bounds.y-this.state.y;e=g*l-k*m;g=k*l+g*m;this.bounds.x+=b;this.bounds.y+=c;this.unscaledBounds.x=this.roundLength(this.unscaledBounds.x+b/f);this.unscaledBounds.y=this.roundLength(this.unscaledBounds.y+c/f);this.unscaledBounds.width=this.roundLength(this.unscaledBounds.width);this.unscaledBounds.height=this.roundLength(this.unscaledBounds.height);\nthis.graph.isCellCollapsed(this.state.cell)||0==b&&0==c?this.childOffsetY=this.childOffsetX=0:(this.childOffsetX=this.state.x-this.bounds.x+e,this.childOffsetY=this.state.y-this.bounds.y+g);d.equals(this.bounds)||(this.livePreviewActive&&this.updateLivePreview(a),null!=this.preview?this.drawPreview():this.updateParentHighlight())};\nmxVertexHandler.prototype.updateLivePreview=function(a){a=this.graph.view.scale;var b=this.graph.view.translate,c=this.state.clone();this.state.x=this.bounds.x;this.state.y=this.bounds.y;this.state.origin=new mxPoint(this.state.x/a-b.x,this.state.y/a-b.y);this.state.width=this.bounds.width;this.state.height=this.bounds.height;b=this.state.absoluteOffset;new mxPoint(b.x,b.y);this.state.absoluteOffset.x=0;this.state.absoluteOffset.y=0;b=this.graph.getCellGeometry(this.state.cell);if(null!=b){var d=\nb.offset||this.EMPTY_POINT;null==d||b.relative||(this.state.absoluteOffset.x=this.state.view.scale*d.x,this.state.absoluteOffset.y=this.state.view.scale*d.y);this.state.view.updateVertexLabelOffset(this.state)}b=this.state.cell.geometry.width;d=this.state.cell.geometry.height;this.state.cell.geometry.width=this.roundLength(this.bounds.width/a);this.state.cell.geometry.height=this.roundLength(this.bounds.height/a);this.state.view.graph.cellRenderer.redraw(this.state,!0);this.state.cell.geometry.width=\nb;this.state.cell.geometry.height=d;this.state.view.invalidate(this.state.cell);this.state.invalid=!1;this.state.view.validate();this.redrawHandles();this.movePreviewToFront&&this.moveToFront();null!=this.state.control&&null!=this.state.control.node&&(this.state.control.node.style.visibility=\"hidden\");this.state.setState(c)};\nmxVertexHandler.prototype.moveToFront=function(){if(null!=this.state.text&&null!=this.state.text.node&&null!=this.state.text.node.nextSibling||null!=this.state.shape&&null!=this.state.shape.node&&null!=this.state.shape.node.nextSibling&&(null==this.state.text||this.state.shape.node.nextSibling!=this.state.text.node))null!=this.state.shape&&null!=this.state.shape.node&&this.state.shape.node.parentNode.appendChild(this.state.shape.node),null!=this.state.text&&null!=this.state.text.node&&this.state.text.node.parentNode.appendChild(this.state.text.node)};\nmxVertexHandler.prototype.mouseUp=function(a,b){if(null!=this.index&&null!=this.state){var c=new mxPoint(b.getGraphX(),b.getGraphY());a=this.index;this.index=null;null==this.ghostPreview&&this.state.view.invalidate(this.state.cell,!1,!1);this.graph.getModel().beginUpdate();try{if(a<=mxEvent.CUSTOM_HANDLE){if(null!=this.customHandles&&null!=this.customHandles[mxEvent.CUSTOM_HANDLE-a]){var d=this.state.view.graph.getCellStyle(this.state.cell);this.customHandles[mxEvent.CUSTOM_HANDLE-a].active=!1;this.customHandles[mxEvent.CUSTOM_HANDLE-\na].execute(b);null!=this.customHandles&&null!=this.customHandles[mxEvent.CUSTOM_HANDLE-a]&&(this.state.style=d,this.customHandles[mxEvent.CUSTOM_HANDLE-a].positionChanged())}}else if(a==mxEvent.ROTATION_HANDLE)if(null!=this.currentAlpha){var e=this.currentAlpha-(this.state.style[mxConstants.STYLE_ROTATION]||0);0!=e&&this.rotateCell(this.state.cell,e)}else this.rotateClick();else{var f=this.graph.isGridEnabledEvent(b.getEvent()),g=mxUtils.toRadians(this.state.style[mxConstants.STYLE_ROTATION]||\"0\"),\nk=Math.cos(-g),l=Math.sin(-g),m=c.x-this.startX,n=c.y-this.startY;d=l*m+k*n;m=k*m-l*n;n=d;var p=this.graph.view.scale,q=this.isRecursiveResize(this.state,b);this.resizeCell(this.state.cell,this.roundLength(m/p),this.roundLength(n/p),a,f,this.isConstrainedEvent(b),q)}}finally{this.graph.getModel().endUpdate()}this.state.invalid&&this.state.view.validate();b.consume();this.reset();this.redrawHandles()}};mxVertexHandler.prototype.isRecursiveResize=function(a,b){return this.graph.isRecursiveResize(this.state)};\nmxVertexHandler.prototype.rotateClick=function(){};\nmxVertexHandler.prototype.rotateCell=function(a,b,c){if(0!=b){var d=this.graph.getModel();if(d.isVertex(a)||d.isEdge(a)){if(!d.isEdge(a)){var e=(this.graph.getCurrentCellStyle(a)[mxConstants.STYLE_ROTATION]||0)+b;this.graph.setCellStyles(mxConstants.STYLE_ROTATION,e,[a])}e=this.graph.getCellGeometry(a);if(null!=e){var f=this.graph.getCellGeometry(c);null==f||d.isEdge(c)||(e=e.clone(),e.rotate(b,new mxPoint(f.width/2,f.height/2)),d.setGeometry(a,e));if(d.isVertex(a)&&!e.relative||d.isEdge(a))for(c=\nd.getChildCount(a),e=0;ed&&(a+=c,a=e?this.graph.snap(a/f)*f:Math.round(a/f)*f);if(0==d||\n3==d||5==d)p+=b,p=e?this.graph.snap(p/f)*f:Math.round(p/f)*f;else if(2==d||4==d||7==d)q+=b,q=e?this.graph.snap(q/f)*f:Math.round(q/f)*f;e=q-p;c=r-a;k&&(k=this.graph.getCellGeometry(this.state.cell),null!=k&&(k=k.width/k.height,1==d||2==d||7==d||6==d?e=c*k:c=e/k,0==d&&(p=q-e,a=r-c)));l&&(e+=e-m,c+=c-n,p+=t-(p+e/2),a+=u-(a+c/2));0>e&&(p+=e,e=Math.abs(e));0>c&&(a+=c,c=Math.abs(c));d=new mxRectangle(p+g.x*f,a+g.y*f,e,c);null!=this.minBounds&&(d.width=Math.max(d.width,this.minBounds.x*f+this.minBounds.width*\nf+Math.max(0,this.x0*f-d.x)),d.height=Math.max(d.height,this.minBounds.y*f+this.minBounds.height*f+Math.max(0,this.y0*f-d.y)));return d};mxVertexHandler.prototype.redraw=function(a){this.selectionBounds=this.getSelectionBounds(this.state);this.bounds=new mxRectangle(this.selectionBounds.x,this.selectionBounds.y,this.selectionBounds.width,this.selectionBounds.height);this.drawPreview();a||this.redrawHandles()};\nmxVertexHandler.prototype.getHandlePadding=function(){var a=new mxPoint(0,0),b=this.tolerance;null!=this.sizers&&0this.state.width&&2>this.state.height&&\n(this.labelShape=this.createSizer(mxConstants.CURSOR_MOVABLE_VERTEX,mxEvent.LABEL_HANDLE,null,mxConstants.LABEL_HANDLE_FILLCOLOR),b.push(this.labelShape));null==this.rotationShape&&(this.rotationShape=this.createSizer(this.rotationCursor,mxEvent.ROTATION_HANDLE,mxConstants.HANDLE_SIZE+3,mxConstants.HANDLE_FILLCOLOR),b.push(this.rotationShape));return b};\nmxVertexHandler.prototype.destroyCustomHandles=function(){if(null!=this.customHandles){for(var a=0;a\nmxEvent.VIRTUAL_HANDLE&&null!=this.customHandles)for(c=0;cmxEvent.VIRTUAL_HANDLE&&(c[this.index-1]=d)}return null!=e?e:c};\nmxEdgeHandler.prototype.isOutlineConnectEvent=function(a){if(mxEvent.isShiftDown(a.getEvent())&&mxEvent.isAltDown(a.getEvent()))return!1;var b=mxUtils.getOffset(this.graph.container),c=a.getEvent(),d=mxEvent.getClientX(c);c=mxEvent.getClientY(c);var e=document.documentElement,f=this.currentPoint.x-this.graph.container.scrollLeft+b.x-((window.pageXOffset||e.scrollLeft)-(e.clientLeft||0));b=this.currentPoint.y-this.graph.container.scrollTop+b.y-((window.pageYOffset||e.scrollTop)-(e.clientTop||0));return this.outlineConnect&&\n(mxEvent.isShiftDown(a.getEvent())&&!mxEvent.isAltDown(a.getEvent())||a.isSource(this.marker.highlight.shape)||!mxEvent.isShiftDown(a.getEvent())&&mxEvent.isAltDown(a.getEvent())&&null!=a.getState()||this.marker.highlight.isHighlightAt(d,c)||(f!=d||b!=c)&&null==a.getState()&&this.marker.highlight.isHighlightAt(f,b))};\nmxEdgeHandler.prototype.updatePreviewState=function(a,b,c,d,e){var f=this.isSource?c:this.state.getVisibleTerminalState(!0),g=this.isTarget?c:this.state.getVisibleTerminalState(!1),k=this.graph.getConnectionConstraint(a,f,!0),l=this.graph.getConnectionConstraint(a,g,!1),m=this.getConstraintHandler(),n=m.currentConstraint;null==n&&e&&(null!=c?(d.isSource(this.marker.highlight.shape)&&(b=new mxPoint(d.getGraphX(),d.getGraphY())),n=this.graph.getOutlineConstraint(b,c,d),m.setFocus(d,c,this.isSource),\nm.currentConstraint=n,m.currentPoint=b):n=new mxConnectionConstraint);if(this.outlineConnect&&null!=this.marker.highlight&&null!=this.marker.highlight.shape){var p=this.graph.view.scale;null!=m.currentConstraint&&null!=m.currentFocus?(this.marker.highlight.shape.stroke=e?mxConstants.OUTLINE_HIGHLIGHT_COLOR:\"transparent\",this.marker.highlight.shape.strokewidth=mxConstants.OUTLINE_HIGHLIGHT_STROKEWIDTH/p/p,this.marker.highlight.repaint()):this.marker.hasValidState()&&(this.marker.highlight.shape.stroke=\nthis.graph.isCellConnectable(d.getCell())&&this.marker.getValidState()!=d.getState()?\"transparent\":mxConstants.DEFAULT_VALID_COLOR,this.marker.highlight.shape.strokewidth=mxConstants.HIGHLIGHT_STROKEWIDTH/p/p,this.marker.highlight.repaint())}this.isSource?k=n:this.isTarget&&(l=n);if(this.isSource||this.isTarget)null!=n&&null!=n.point?(a.style[this.isSource?mxConstants.STYLE_EXIT_X:mxConstants.STYLE_ENTRY_X]=n.point.x,a.style[this.isSource?mxConstants.STYLE_EXIT_Y:mxConstants.STYLE_ENTRY_Y]=n.point.y):\n(delete a.style[this.isSource?mxConstants.STYLE_EXIT_X:mxConstants.STYLE_ENTRY_X],delete a.style[this.isSource?mxConstants.STYLE_EXIT_Y:mxConstants.STYLE_ENTRY_Y]);a.setVisibleTerminalState(f,!0);a.setVisibleTerminalState(g,!1);this.isSource&&null==f||a.view.updateFixedTerminalPoint(a,f,!0,k);this.isTarget&&null==g||a.view.updateFixedTerminalPoint(a,g,!1,l);(this.isSource||this.isTarget)&&null==c&&(a.setAbsoluteTerminalPoint(b,this.isSource),null==this.marker.getMarkedState()&&(this.error=this.graph.allowDanglingEdges?\nnull:\"\"));a.view.updatePoints(a,this.points,f,g);a.view.updateFloatingTerminalPoints(a,f,g)};\nmxEdgeHandler.prototype.mouseMove=function(a,b){if(null!=this.index&&null!=this.marker){var c=this.getConstraintHandler();this.currentPoint=this.getPointForEvent(b);this.error=null;null!=this.snapPoint&&mxEvent.isShiftDown(b.getEvent())&&!this.graph.isIgnoreTerminalEvent(b.getEvent())&&null==c.currentFocus&&c.currentFocus!=this.state&&(Math.abs(this.snapPoint.x-this.currentPoint.x)mxEvent.VIRTUAL_HANDLE)null!=this.customHandles&&(this.customHandles[mxEvent.CUSTOM_HANDLE-this.index].processEvent(b),this.customHandles[mxEvent.CUSTOM_HANDLE-this.index].positionChanged(),null!=this.shape&&null!=this.shape.node&&(this.shape.node.style.display=\"none\"));else if(this.isLabel)this.label.x=this.currentPoint.x,this.label.y=this.currentPoint.y;else{this.points=this.getPreviewPoints(this.currentPoint,b);a=this.isSource||this.isTarget?this.getPreviewTerminalState(b):\nnull;if(null!=c.currentConstraint&&null!=c.currentFocus&&null!=c.currentPoint)this.currentPoint=c.currentPoint.clone();else if(this.outlineConnect){var d=this.isSource||this.isTarget?this.isOutlineConnectEvent(b):!1;d?a=this.marker.highlight.state:null!=a&&a!=b.getState()&&this.graph.isCellConnectable(b.getCell())&&null!=this.marker.highlight.shape&&(this.marker.highlight.shape.stroke=\"transparent\",this.marker.highlight.repaint(),a=null)}null==a||this.isCellEnabled(a.cell)||(a=null,this.marker.reset());\nc=this.clonePreviewState(this.currentPoint,null!=a?a.cell:null);this.updatePreviewState(c,this.currentPoint,a,b,d);this.setPreviewColor(null==this.error?this.marker.validColor:this.marker.invalidColor);this.abspoints=c.absolutePoints;this.active=!0;this.updateHint(b,this.currentPoint,c)}this.drawPreview();mxEvent.consume(b.getEvent());b.consume()}else mxEvent.isShiftDown(b.getEvent())||null==this.handle||null==this.mouseDownX||null==this.mouseDownY||(d=this.graph.tolerance,(Math.abs(this.mouseDownX-\nb.getX())>d||Math.abs(this.mouseDownY-b.getY())>d)&&this.start(this.mouseDownX,this.mouseDownY,this.handle))};\nmxEdgeHandler.prototype.mouseUp=function(a,b){if(null!=this.index&&null!=this.marker){null!=this.shape&&null!=this.shape.node&&(this.shape.node.style.display=\"\");a=this.state.cell;var c=this.index;this.index=null;if(b.getX()!=this.startX||b.getY()!=this.startY){var d=!this.graph.isIgnoreTerminalEvent(b.getEvent())&&this.cloneEnabled&&this.graph.isCloneEvent(b.getEvent())&&this.graph.isCellsCloneable();if(null!=this.error)0mxEvent.VIRTUAL_HANDLE){if(null!=this.customHandles){var e=this.graph.getModel();e.beginUpdate();try{this.customHandles[mxEvent.CUSTOM_HANDLE-c].execute(b),null!=this.shape&&null!=this.shape.node&&(this.shape.apply(this.state),this.shape.redraw())}finally{e.endUpdate()}}}else if(this.isLabel)this.moveLabel(this.state,this.label.x,this.label.y);else if(this.isSource||this.isTarget)if(c=null,null!=this.constraintHandler&&null!=this.constraintHandler.currentConstraint&&null!=this.constraintHandler.currentFocus&&\n(c=this.constraintHandler.currentFocus.cell),null==c&&this.marker.hasValidState()&&null!=this.marker.highlight&&null!=this.marker.highlight.shape&&\"transparent\"!=this.marker.highlight.shape.stroke&&\"white\"!=this.marker.highlight.shape.stroke&&(c=this.marker.validState.cell),null!=c){e=this.graph.getModel();var f=e.getParent(a);e.beginUpdate();try{if(d){var g=e.getGeometry(a),k=this.graph.cloneCell(a);e.add(f,k,e.getChildCount(f));null!=g&&(g=g.clone(),e.setGeometry(k,g));var l=e.getTerminal(a,!this.isSource);\nthis.graph.connectCell(k,l,!this.isSource);a=k}a=this.connect(a,c,this.isSource,d,b)}finally{e.endUpdate()}}else this.graph.isAllowDanglingEdges()&&(g=this.abspoints[this.isSource?0:this.abspoints.length-1],g.x=this.roundLength(g.x/this.graph.view.scale-this.graph.view.translate.x),g.y=this.roundLength(g.y/this.graph.view.scale-this.graph.view.translate.y),k=this.graph.getView().getState(this.graph.getModel().getParent(a)),null!=k&&(g.x-=k.origin.x,g.y-=k.origin.y),g.x-=this.graph.panDx/this.graph.view.scale,\ng.y-=this.graph.panDy/this.graph.view.scale,a=this.changeTerminalPoint(a,g,this.isSource,d));else this.active?a=this.changePoints(a,this.points,d):(this.graph.getView().invalidate(this.state.cell),this.graph.getView().validate(this.state.cell))}else this.graph.isToggleEvent(b.getEvent())&&this.graph.selectCellForEvent(this.state.cell,b.getEvent());null!=this.marker&&(this.reset(),a!=this.state.cell&&this.graph.setSelectionCell(a));b.consume()}else null==this.handle||null==this.bends||mxEvent.isAltDown(b.getEvent())||\n0!=this.handle&&this.handle!=this.bends.length-1||(c=this.state.getVisibleTerminal(0==this.handle),null!=c&&(this.graph.selectCellForEvent(c,b.getEvent()),b.consume()));this.mouseDownY=this.mouseDownX=this.handle=null};\nmxEdgeHandler.prototype.reset=function(){this.active&&this.refresh();this.snapPoint=this.mouseDownY=this.mouseDownX=this.startY=this.startX=this.handle=this.points=this.label=this.index=this.error=null;this.active=this.isTarget=this.isSource=this.isLabel=!1;if(this.livePreview&&null!=this.sizers)for(var a=0;a=mxGraphHandler.prototype.maxCells||this.graph.getSelectionCount()<=mxGraphHandler.prototype.maxCells)};\nmxEdgeHandler.prototype.refresh=function(){if(null!=this.state){var a=this.state.getVisibleTerminalState(!0),b=this.state.getVisibleTerminalState(!1),c=this.graph.getCellGeometry(this.state.cell);this.edgeStyle=null!=c?this.graph.view.getEdgeStyle(this.state,c.points,a,b):null;this.abspoints=this.getSelectionPoints(this.state);this.points=[];null!=this.shape&&(this.shape.isDashed=this.isSelectionDashed(),this.shape.stroke=this.getSelectionColor(),this.shape.isShadow=!1,this.shape.redraw());null!=\nthis.bends&&(this.destroyBends(this.bends),this.bends=null);this.isHandlesVisible()&&(this.bends=this.createBends());null!=this.virtualBends&&(this.destroyBends(this.virtualBends),this.virtualBends=null);this.isHandlesVisible()&&this.edgeStyle!=mxEdgeStyle.EntityRelation&&(this.virtualBends=this.createVirtualBends());null!=this.customHandles&&(this.destroyBends(this.customHandles),this.customHandles=null);this.isHandlesVisible()&&(this.customHandles=this.createCustomHandles());null!=this.labelShape&&\n(this.labelShape.destroy(),this.labelShape=null);this.isHandlesVisible()&&(this.labelShape=this.createLabelShape(),null!=this.labelShape&&null!=this.labelShape.node&&null!=this.labelShape.node.parentNode&&this.labelShape.node.parentNode.appendChild(this.labelShape.node))}};mxEdgeHandler.prototype.isDestroyed=function(){return null==this.shape};mxEdgeHandler.prototype.destroyBends=function(a){if(null!=a)for(var b=0;bl.name?1:0});for(e=0;e=v)m.push(new Uint8Array(b,p,2)),p+=2;else{var u=f.getUint16(p+2),D=65506===v;65505===v||D||m.push(new Uint8Array(b,p,2+u));p+=2+u}}b=m.reduce(function(I,V){return I+V.length},0);b=new Uint8Array(b);f=0;m=$jscomp.makeIterator(m);for(p=m.next();!p.done;p=m.next())p=p.value,b.set(p,f),f+=p.length;\nreturn b};\nEditor.stripPngMetadata=function(b){var f=new DataView(b),m=new Set(\"eXIf tEXt iTXt zTXt iCCP tIME\".split(\" \")),p=[];p.push(new Uint8Array(b,0,8));for(var v=8;v\");0>p&&(p=b.indexOf(\"\"));return 0<=p&&v>p?[mxUtils.trim(f(b.substring(0,p))),b.substring(p,v+15),mxUtils.trim(m(b.substring(v+15)))]:[b,\"\",\"\"]};\nEditor.extractGraphModelFromPng=function(b){var f=null;try{var m=b.substring(b.indexOf(\",\")+1),p=window.atob&&!mxClient.IS_SF?atob(m):Base64.decode(m,!0);EditorUi.parsePng(p,mxUtils.bind(this,function(v,u,D){v=p.substring(v+8,v+8+D);\"zTXt\"==u?(D=v.indexOf(String.fromCharCode(0)),\"mxGraphModel\"==v.substring(0,D)&&(v=pako.inflateRaw(Graph.stringToArrayBuffer(v.substring(D+2)),{to:\"string\"}).replace(/\\+/g,\" \"),null!=v&&0=u&&\"0\"!=\"01230120022455012603010202\"[u]&&(\"01230120022455012603010202\"[u]!=f[m-1]&&(f[m]=\"01230120022455012603010202\"[u],m++),3=m)for(;3>=m;)f[m]=\"0\",m++;return f.join(\"\")};\nEditor.selectFilename=function(b){var f=b.value.lastIndexOf(\".\");if(0\"));f.innerHTML=Graph.sanitizeHtml(m);N.appendChild(f);m=document.createElement(\"div\");m.style.marginTop=\"12px\";m.style.textAlign=\"center\";null!=u&&(f=mxUtils.button(mxResources.get(\"tryAgain\"),\nfunction(){b.hideDialog();u()}),f.className=\"geBtn\",m.appendChild(f),m.style.textAlign=\"center\");null!=F&&(F=mxUtils.button(F,function(){null!=Q&&Q()}),F.className=\"geBtn\",m.appendChild(F));var g=mxUtils.button(p,function(){V&&b.hideDialog();null!=v&&v()});g.className=\"geBtn\";m.appendChild(g);null!=D&&(p=mxUtils.button(D,function(){V&&b.hideDialog();null!=I&&I()}),p.className=\"geBtn gePrimaryBtn\",m.appendChild(p));this.init=function(){g.focus()};N.appendChild(m);this.container=N},PrintDialog=function(b,\nf,m,p){this.create(b,f,m,p)};\nPrintDialog.prototype.create=function(b){function f(g){var l=D.checked||F.checked,t=parseInt(N.value)/100;isNaN(t)&&(t=1,N.value=\"100%\");mxClient.IS_SF&&(t*=.75);var B=m.pageFormat||mxConstants.PAGE_FORMAT_A4_PORTRAIT,E=1/m.pageScale;if(l){var H=D.checked?1:parseInt(Q.value);isNaN(H)||(E=mxUtils.getScaleForPageCount(H,m,B))}var K=H=0;B=mxRectangle.fromRectangle(B);B.width=Math.ceil(B.width*t);B.height=Math.ceil(B.height*t);E*=t;!l&&m.pageVisible?(t=m.getPageLayout(),H-=t.x*B.width,K-=t.y*B.height):\nl=!0;l=PrintDialog.createPrintPreview(m,E,B,0,H,K,l);l.open();g&&PrintDialog.printPreview(l)}var m=b.editor.graph,p=document.createElement(\"table\");p.style.width=\"100%\";p.style.height=\"100%\";var v=document.createElement(\"tbody\");var u=document.createElement(\"tr\");var D=document.createElement(\"input\");D.setAttribute(\"type\",\"checkbox\");var I=document.createElement(\"td\");I.setAttribute(\"colspan\",\"2\");I.style.fontSize=\"10pt\";I.appendChild(D);var V=document.createElement(\"span\");mxUtils.write(V,\" \"+mxResources.get(\"fitPage\"));\nI.appendChild(V);mxEvent.addListener(V,\"click\",function(g){D.checked=!D.checked;F.checked=!D.checked;mxEvent.consume(g)});mxEvent.addListener(D,\"change\",function(){F.checked=!D.checked});u.appendChild(I);v.appendChild(u);u=u.cloneNode(!1);var F=document.createElement(\"input\");F.setAttribute(\"type\",\"checkbox\");I=document.createElement(\"td\");I.style.fontSize=\"10pt\";I.appendChild(F);V=document.createElement(\"span\");mxUtils.write(V,\" \"+mxResources.get(\"posterPrint\")+\":\");I.appendChild(V);mxEvent.addListener(V,\n\"click\",function(g){F.checked=!F.checked;D.checked=!F.checked;mxEvent.consume(g)});u.appendChild(I);var Q=document.createElement(\"input\");Q.setAttribute(\"value\",\"1\");Q.setAttribute(\"type\",\"number\");Q.setAttribute(\"min\",\"1\");Q.setAttribute(\"size\",\"4\");Q.setAttribute(\"disabled\",\"disabled\");Q.style.width=\"50px\";I=document.createElement(\"td\");I.style.fontSize=\"10pt\";I.appendChild(Q);mxUtils.write(I,\" \"+mxResources.get(\"pages\")+\" (max)\");u.appendChild(I);v.appendChild(u);mxEvent.addListener(F,\"change\",\nfunction(){F.checked?Q.removeAttribute(\"disabled\"):Q.setAttribute(\"disabled\",\"disabled\");D.checked=!F.checked});u=u.cloneNode(!1);I=document.createElement(\"td\");mxUtils.write(I,mxResources.get(\"pageScale\")+\":\");u.appendChild(I);I=document.createElement(\"td\");var N=document.createElement(\"input\");N.setAttribute(\"value\",\"100 %\");N.setAttribute(\"size\",\"5\");N.style.width=\"50px\";I.appendChild(N);u.appendChild(I);v.appendChild(u);u=document.createElement(\"tr\");I=document.createElement(\"td\");I.colSpan=2;\nI.style.paddingTop=\"30px\";I.setAttribute(\"align\",\"right\");V=mxUtils.button(mxResources.get(\"cancel\"),function(){b.hideDialog()});V.className=\"geBtn\";b.editor.cancelFirst&&I.appendChild(V);if(PrintDialog.previewEnabled){var c=mxUtils.button(mxResources.get(\"preview\"),function(){b.hideDialog();f(!1)});c.className=\"geBtn\";I.appendChild(c)}c=mxUtils.button(mxResources.get(PrintDialog.previewEnabled?\"print\":\"ok\"),function(){b.hideDialog();f(!0)});c.className=\"geBtn gePrimaryBtn\";I.appendChild(c);b.editor.cancelFirst||\nI.appendChild(V);u.appendChild(I);v.appendChild(u);p.appendChild(v);u=document.createElement(\"div\");u.style.paddingBottom=\"20px\";u.appendChild(p);this.container=u};PrintDialog.printPreview=function(b){try{null!=b.wnd&&window.setTimeout(function(){b.wnd.focus();b.wnd.print();b.wnd.close()},500)}catch(f){}};\nPrintDialog.createPrintPreview=function(b,f,m,p,v,u,D){f=new mxPrintPreview(b,f,m,p,v,u);f.title=mxResources.get(\"preview\");f.addPageCss=!mxClient.IS_SF;f.printBackgroundImage=!0;f.autoOrigin=D;D=b.background;if(null==D||\"\"==D||D==mxConstants.NONE)D=\"#ffffff\";f.backgroundColor=D;var I=f.isTextLabel;f.isTextLabel=function(F){return\"geHint\"==!F.className&&I.apply(this,arguments)};var V=f.getLinkForCellState;f.getLinkForCellState=function(F){return b.getAbsoluteUrl(V.apply(this,arguments))};return f};\nPrintDialog.previewEnabled=!0;\nvar PageSetupDialog=function(b){function f(){var H=c;null!=H&&null!=H.originalSrc&&(H=b.createImageForPageLink(H.originalSrc,null));null!=H&&null!=H.src?(N.style.backgroundImage=\"url(\"+H.src+\")\",N.style.display=\"inline-block\"):(N.style.backgroundImage=\"\",N.style.display=\"none\");N.style.backgroundColor=\"\";null!=g&&g!=mxConstants.NONE&&(N.style.backgroundColor=g,N.style.display=\"inline-block\")}var m=b.editor.graph,p=document.createElement(\"div\"),v=function(H){H.style.minWidth=\"140px\";return H},u=function(H){H.style.marginLeft=\n\"8px\";return H},D=document.createElement(\"h3\");mxUtils.write(D,mxResources.get(\"pageSetup\"));D.style.cssText=\"width:100%;text-align:center;margin-top:0px;margin-bottom:10px\";p.appendChild(D);D=document.createElement(\"div\");D.className=\"geDialogSection\";var I=document.createElement(\"div\");I.className=\"geDialogFormRow\";var V=document.createElement(\"span\");V.className=\"geDialogFormLabel\";mxUtils.write(V,mxResources.get(\"paperSize\")+\":\");I.appendChild(v(V));V=document.createElement(\"div\");V.style.flex=\n\"1\";V.style.minWidth=\"0\";var F=PageSetupDialog.addPageFormatPanel(V,\"pagesetupdialog\",m.pageFormat);I.appendChild(u(V));D.appendChild(I);p.appendChild(D);D=document.createElement(\"div\");D.className=\"geDialogSection\";I=document.createElement(\"div\");I.className=\"geDialogFormRow\";V=document.createElement(\"span\");V.className=\"geDialogFormLabel\";mxUtils.write(V,mxResources.get(\"gridSize\")+\":\");I.appendChild(v(V));var Q=document.createElement(\"input\");Q.setAttribute(\"type\",\"number\");Q.setAttribute(\"min\",\n\"0\");Q.style.width=\"60px\";Q.style.flex=\"0 0 auto\";Q.value=m.getGridSize();I.appendChild(u(Q));mxEvent.addListener(Q,\"change\",function(){var H=parseInt(Q.value);Q.value=Math.max(1,isNaN(H)?m.getGridSize():H)});D.appendChild(I);p.appendChild(D);D=document.createElement(\"div\");D.className=\"geDialogSection\";I=document.createElement(\"div\");I.className=\"geDialogFormRow\";V=document.createElement(\"span\");V.className=\"geDialogFormLabel\";mxUtils.write(V,mxResources.get(\"background\")+\":\");I.appendChild(v(V));\nV=document.createElement(\"div\");V.style.display=\"flex\";V.style.alignItems=\"center\";V.style.gap=\"8px\";V.style.flex=\"1\";V.style.minWidth=\"0\";u(V);var N=document.createElement(\"div\");N.style.display=\"inline-block\";N.style.verticalAlign=\"middle\";N.style.backgroundPosition=\"center center\";N.style.backgroundRepeat=\"no-repeat\";N.style.backgroundSize=\"contain\";N.style.border=\"1px solid lightGray\";N.style.borderRadius=\"4px\";N.style.height=\"32px\";N.style.width=\"64px\";N.style.cursor=\"pointer\";N.style.padding=\n\"4px\";N.style.flexShrink=\"0\";var c=m.backgroundImage,g=m.background,l=m.shadowVisible,t=function(H){b.showBackgroundImageDialog(function(K,O,W,L){O||(null!=K&&null!=K.src&&Graph.isPageLink(K.src)&&(K={originalSrc:K.src}),c=K,l=L);g=W;f()},c,g,!0);mxEvent.consume(H)};mxEvent.addListener(N,\"click\",t);var B=document.createElement(\"button\");B.className=\"geBtn\";B.style.margin=\"0px\";mxUtils.write(B,mxResources.get(\"change\")+\"...\");mxEvent.addListener(B,\"click\",t);f();V.appendChild(N);V.appendChild(B);I.appendChild(V);\nD.appendChild(I);p.appendChild(D);D=document.createElement(\"div\");D.className=\"geDialogSection\";I=document.createElement(\"div\");I.className=\"geDialogFormRow\";V=document.createElement(\"span\");V.className=\"geDialogFormLabel\";mxUtils.write(V,mxResources.get(\"adaptiveColors\")+\":\");I.appendChild(v(V));var E=document.createElement(\"select\");u(E);v=function(H,K){var O=document.createElement(\"option\");O.setAttribute(\"value\",H);mxUtils.write(O,K);E.appendChild(O)};v(\"default\",mxResources.get(\"default\")+\" (\"+\nmxResources.get(Graph.getDefaultAdaptiveColorsKey())+\")\");v(\"auto\",mxResources.get(\"automatic\"));v(\"simple\",mxResources.get(\"simple\"));v(\"none\",mxResources.get(\"none\"));E.value=null==m.adaptiveColors?\"default\":m.adaptiveColors;I.appendChild(E);null==b.menus||null==b.menus.createHelpLink||b.isOffline()&&!mxClient.IS_CHROMEAPP&&!EditorUi.isElectronApp||(v=b.menus.createHelpLink(\"https://github.com/jgraph/drawio/discussions/4713\"),v.style.marginLeft=\"8px\",I.appendChild(v));D.appendChild(I);p.appendChild(D);\nthis.container=(new CustomDialog(b,p,function(){var H=parseInt(Q.value);isNaN(H)||m.gridSize===H||m.setGridSize(H);H=new ChangePageSetup(b,g,c,F.get());H.ignoreColor=m.background==g;H.ignoreImage=(null!=m.backgroundImage?m.backgroundImage.src:null)===(null!=c?c.src:null);null!=l&&(H.shadowVisible=l);var K=E.value,O=K!=(null==m.adaptiveColors?\"default\":m.adaptiveColors);O&&(H.adaptiveColors=K);m.pageFormat.width==H.previousFormat.width&&m.pageFormat.height==H.previousFormat.height&&H.ignoreColor&&\nH.ignoreImage&&H.shadowVisible==m.shadowVisible&&!O||m.model.execute(H)},null,mxResources.get(\"apply\"))).container};\nPageSetupDialog.addPageFormatPanel=function(b,f,m,p){f=\"format-\"+f;var v=document.createElement(\"input\");v.setAttribute(\"name\",f);v.setAttribute(\"type\",\"radio\");v.setAttribute(\"value\",\"portrait\");var u=document.createElement(\"input\");u.setAttribute(\"name\",f);u.setAttribute(\"type\",\"radio\");u.setAttribute(\"value\",\"landscape\");var D=document.createElement(\"select\");D.style.position=\"relative\";D.style.marginBottom=\"4px\";D.style.borderRadius=\"4px\";D.style.borderWidth=\"1px\";D.style.borderStyle=\"solid\";\nD.style.boxSizing=\"border-box\";D.style.padding=\"2px\";D.style.width=\"210px\";var I=document.createElement(\"div\");I.style.alignItems=\"center\";I.style.whiteSpace=\"nowrap\";I.style.marginLeft=\"4px\";I.style.width=\"210px\";I.style.height=\"24px\";v.style.margin=\"0 6px 0 0\";I.appendChild(v);f=document.createElement(\"span\");f.style.maxWidth=\"100px\";mxUtils.write(f,mxResources.get(\"portrait\"));I.appendChild(f);u.style.margin=\"0 6px 0 10px\";I.appendChild(u);var V=document.createElement(\"span\");V.style.width=\"100px\";\nmxUtils.write(V,mxResources.get(\"landscape\"));I.appendChild(V);var F=document.createElement(\"div\");F.style.whiteSpace=\"nowrap\";F.style.fontSize=\"12px\";F.style.width=\"210px\";F.style.height=\"24px\";var Q=document.createElement(\"input\");Q.setAttribute(\"size\",\"6\");Q.setAttribute(\"title\",mxResources.get(\"width\"));Q.style.textAlign=\"right\";F.appendChild(Q);mxUtils.write(F,\" x \");var N=document.createElement(\"input\");N.setAttribute(\"size\",\"6\");N.setAttribute(\"title\",mxResources.get(\"height\"));N.style.textAlign=\n\"right\";F.appendChild(N);var c=document.createElement(\"select\");c.style.marginLeft=\"4px\";c.style.boxSizing=\"border-box\";for(var g=[{label:mxResources.get(\"points\"),unit:mxConstants.POINTS},{label:mxResources.get(\"inches\"),unit:mxConstants.INCHES},{label:mxResources.get(\"millimeters\"),unit:mxConstants.MILLIMETERS}],l=0;l=O)Q.value=Editor.toUnit(m.width,c.value);O=parseFloat(N.value);if(isNaN(O)||0>=O)N.value=Editor.toUnit(m.height,c.value);O=new mxRectangle(0,0,Math.floor(Editor.fromUnit(parseFloat(Q.value),c.value)),Math.floor(Editor.fromUnit(parseFloat(N.value),c.value)));W||O.width==m.width&&O.height==m.height||(m=O,null!=p&&p(m))};mxEvent.addListener(f,\"click\",function(O){v.checked=!0;K(O);mxEvent.consume(O)});\nmxEvent.addListener(V,\"click\",function(O){u.checked=!0;K(O);mxEvent.consume(O)});mxEvent.addListener(Q,\"blur\",K);mxEvent.addListener(Q,\"click\",K);mxEvent.addListener(N,\"blur\",K);mxEvent.addListener(N,\"click\",K);mxEvent.addListener(u,\"change\",K);mxEvent.addListener(v,\"change\",K);mxEvent.addListener(D,\"change\",function(O){K(O,\"custom\"==D.value);mxEvent.consume(O)});mxEvent.addListener(c,\"change\",function(O){Q.value=Editor.toUnit(Editor.fromUnit(Q.value,Editor.pageSizeUnit),c.value);N.value=Editor.toUnit(Editor.fromUnit(N.value,\nEditor.pageSizeUnit),c.value);Editor.pageSizeUnit=c.value;K(O,!0);mxEvent.consume(O)});K(null,!0);return{set:function(O){m=O;H(null,null,!0)},get:function(){return m},widthInput:Q,heightInput:N}};\nPageSetupDialog.getFormats=function(){return[{key:\"letter\",title:'US-Letter (8,5\" x 11\")',format:mxConstants.PAGE_FORMAT_LETTER_PORTRAIT},{key:\"legal\",title:'US-Legal (8,5\" x 14\")',format:new mxRectangle(0,0,850,1400)},{key:\"tabloid\",title:'US-Tabloid (11\" x 17\")',format:new mxRectangle(0,0,1100,1700)},{key:\"executive\",title:'US-Executive (7\" x 10\")',format:new mxRectangle(0,0,700,1E3)},{key:\"a0\",title:\"A0 (841 mm x 1189 mm)\",format:new mxRectangle(0,0,3300,4681)},{key:\"a1\",title:\"A1 (594 mm x 841 mm)\",\nformat:new mxRectangle(0,0,2339,3300)},{key:\"a2\",title:\"A2 (420 mm x 594 mm)\",format:new mxRectangle(0,0,1654,2336)},{key:\"a3\",title:\"A3 (297 mm x 420 mm)\",format:new mxRectangle(0,0,1169,1654)},{key:\"a4\",title:\"A4 (210 mm x 297 mm)\",format:mxConstants.PAGE_FORMAT_A4_PORTRAIT},{key:\"a5\",title:\"A5 (148 mm x 210 mm)\",format:new mxRectangle(0,0,583,827)},{key:\"a6\",title:\"A6 (105 mm x 148 mm)\",format:new mxRectangle(0,0,413,583)},{key:\"a7\",title:\"A7 (74 mm x 105 mm)\",format:new mxRectangle(0,0,291,413)},\n{key:\"b4\",title:\"B4 (250 mm x 353 mm)\",format:new mxRectangle(0,0,980,1390)},{key:\"b5\",title:\"B5 (176 mm x 250 mm)\",format:new mxRectangle(0,0,690,980)},{key:\"16-9\",title:\"16:9 (1600 x 900)\",format:new mxRectangle(0,0,900,1600)},{key:\"16-10\",title:\"16:10 (1920 x 1200)\",format:new mxRectangle(0,0,1200,1920)},{key:\"4-3\",title:\"4:3 (1600 x 1200)\",format:new mxRectangle(0,0,1200,1600)},{key:\"custom\",title:mxResources.get(\"custom\"),format:null}]};\nvar FilenameDialog=function(b,f,m,p,v,u,D,I,V,F,Q){V=null!=V?V:!0;var N=document.createElement(\"div\"),c=document.createElement(\"div\");c.style.width=\"100%\";c.style.display=\"grid\";c.style.gap=\"5px 8px\";c.style.gridAutoColumns=\"auto 1fr\";c.style.boxSizing=\"border-box\";c.style.padding=\"3px\";var g=document.createElement(\"div\");g.style.display=\"inline-flex\";g.style.alignItems=\"center\";g.style.justifyContent=\"flex-end\";g.style.minWidth=\"0\";var l=document.createElement(\"div\");l.style.display=\"inline-block\";\nl.style.textOverflow=\"ellipsis\";l.style.whiteSpace=\"nowrap\";l.style.overflow=\"hidden\";l.style.fontSize=\"10pt\";l.style.padding=\"2px 0\";l.setAttribute(\"title\",v||mxResources.get(\"filename\"));mxUtils.write(l,(v||mxResources.get(\"filename\"))+\":\");g.appendChild(l);c.appendChild(g);var t=document.createElement(\"input\");t.setAttribute(\"value\",f||\"\");t.style.flexGrow=\"1\";var B=mxUtils.button(m,function(){if(null==u||u(t.value))V&&b.hideDialog(),p(t.value)});B.className=\"geBtn gePrimaryBtn\";this.init=function(){if(null!=\nv||null==D)if(null!=Q?Editor.selectFilename(t):(t.focus(),mxClient.IS_GC||mxClient.IS_FF||5<=document.documentMode?t.select():document.execCommand(\"selectAll\",!1,null)),Graph.fileSupport){var E=c.parentNode;if(null!=E){var H=null;mxEvent.addListener(E,\"dragleave\",function(K){null!=H&&(H.style.backgroundColor=\"\",H=null);K.stopPropagation();K.preventDefault()});mxEvent.addListener(E,\"dragover\",mxUtils.bind(this,function(K){null==H&&(H=t,H.style.backgroundColor=\"#ebf2f9\");K.stopPropagation();K.preventDefault()}));\nmxEvent.addListener(E,\"drop\",mxUtils.bind(this,function(K){null!=H&&(H.style.backgroundColor=\"\",H=null);0<=mxUtils.indexOf(K.dataTransfer.types,\"text/uri-list\")&&(t.value=decodeURIComponent(K.dataTransfer.getData(\"text/uri-list\")),B.click());K.stopPropagation();K.preventDefault()}))}}};g=document.createElement(\"div\");g.style.display=\"inline-flex\";g.style.alignItems=\"center\";g.style.gap=\"4px\";g.style.whiteSpace=\"nowrap\";g.appendChild(t);c.appendChild(g);null==v&&null!=D||null==Q||null==b.editor.diagramFileTypes||\n(g=document.createElement(\"div\"),g.style.display=\"inline-flex\",g.style.alignItems=\"center\",g.style.textOverflow=\"ellipsis\",g.style.justifyContent=\"flex-end\",g.style.fontSize=\"10pt\",mxUtils.write(g,mxResources.get(\"type\")+\":\"),c.appendChild(g),g=document.createElement(\"div\"),g.style.display=\"inline-flex\",g.style.alignItems=\"center\",g.style.whiteSpace=\"nowrap\",c.appendChild(g),f=FilenameDialog.createFileTypes(b,t,b.editor.diagramFileTypes),f.style.width=\"100%\",g.appendChild(f));null!=D&&c.appendChild(D);\nrow=document.createElement(\"div\");row.style.gridColumn=\"1 / span 2\";row.style.paddingTop=null!=Q?\"6px\":\"14px\";row.style.whiteSpace=\"nowrap\";row.style.display=\"inline-flex\";row.style.alignItems=\"center\";row.style.justifyContent=\"flex-end\";row.setAttribute(\"align\",\"right\");g=mxUtils.button(mxResources.get(\"cancel\"),function(){b.hideDialog();null!=F&&F()});g.className=\"geBtn\";null==I||b.isOffline()||row.appendChild(b.createHelpIcon(I));b.editor.cancelFirst&&row.appendChild(g);mxEvent.addListener(t,\"keypress\",\nfunction(E){13==E.keyCode&&B.click()});row.appendChild(B);b.editor.cancelFirst||row.appendChild(g);c.appendChild(row);N.appendChild(c);this.container=N};FilenameDialog.filenameHelpLink=null;\nFilenameDialog.createFileTypes=function(b,f,m){var p=document.createElement(\"select\");for(b=0;b'};var b=mxGraph.prototype.panGraph;mxGraph.prototype.panGraph=function(D,I){b.apply(this,arguments);\nif(null!=this.shiftPreview1){var V=this.view.canvas;null!=V.ownerSVGElement&&(V=V.ownerSVGElement);var F=this.gridSize*this.view.scale*this.view.gridSteps;F=-Math.round(F-mxUtils.mod(this.view.translate.x*this.view.scale+D,F))+\"px \"+-Math.round(F-mxUtils.mod(this.view.translate.y*this.view.scale+I,F))+\"px\";V.style.backgroundPosition=F}};mxGraph.prototype.updatePageBreaks=function(D,I,V){var F=this.view.scale,Q=this.view.translate,N=this.pageFormat,c=F*this.pageScale,g=this.view.getBackgroundPageBounds();\nI=g.width;V=g.height;var l=new mxRectangle(F*Q.x,F*Q.y,N.width*c,N.height*c),t=(D=D&&Math.min(l.width,l.height)>this.minPageBreakDist)?Math.ceil(V/l.height)-1:0,B=D?Math.ceil(I/l.width)-1:0,E=g.x+I,H=g.y+V;null==this.horizontalPageBreaks&&0M?S.style.display=\"none\":S.style.display=\"\";S=S.nextSibling}}),W=!1,L=null,ca=null,ha=null,ia=mxUtils.bind(this,function(){if(null!=this.toolbar&&W!=v.cellEditor.isContentEditing()){for(var M=this.toolbar.container.firstChild,S=[];null!=M;){var Z=M.nextSibling;M.parentNode.removeChild(M);S.push(M);M=Z}M=this.toolbar.fontMenu;Z=this.toolbar.sizeMenu;if(null==ha)this.toolbar.createTextToolbar();\nelse{for(var aa=0;aab.container.tabIndex)b.container.tabIndex=-1;mxEvent.addListener(b.container,\"focus\",mxUtils.bind(this,function(){this.updateTypingShim()}));var v=b.cellEditor.focusContainer;b.cellEditor.focusContainer=mxUtils.bind(this,function(){b.isSelectionEmpty()||b.isEditing()?v.apply(b.cellEditor):this.updateTypingShim()})};\nEditorUi.prototype.updateTypingShim=function(){var b=this.editor.graph;b.isEditing()||b.isSelectionEmpty()||!b.isEnabled()||b.isCellLocked(b.getSelectionCell())?this.hideTypingShim():this.showTypingShim()};\nEditorUi.prototype.showTypingShim=function(){var b=this.editor.graph,f=this.typingShim;if(null!=f&&!b.isEditing()){var m=b.getSelectionCell();m=b.getView().getState(m);null!=m&&(f.style.left=Math.round(m.x)+\"px\",f.style.top=Math.round(m.y)+\"px\",f.parentNode!==b.container&&b.container.appendChild(f),f.value=\"\",m=document.activeElement,null!=m&&m!==document.body&&m!==b.container&&!b.container.contains(m)||null!=m&&\"true\"===m.contentEditable||f.focus({preventScroll:!0}))}};\nEditorUi.prototype.hideTypingShim=function(){var b=this.typingShim;null!=b&&null!=b.parentNode&&b.parentNode.removeChild(b)};EditorUi.prototype.isImmediateEditingEvent=function(b){return!0};EditorUi.prototype.updateCssForMarker=function(b,f,m,p,v){m=this.getImageForMarker(p,v,m);b.innerHTML=\"\";null==m?b.innerHTML=mxUtils.htmlEntities(mxResources.get(\"none\")):(p=document.createElement(\"img\"),p.setAttribute(\"src\",m),\"end\"==f&&mxUtils.setPrefixedStyle(p.style,\"transform\",\"scaleX(-1)\"),b.appendChild(p))};\nEditorUi.prototype.getImageForMarker=function(b,f,m){return\"flexArrow\"==m?null!=b&&b!=mxConstants.NONE?Format.blockMarkerImage.src:null:b==mxConstants.ARROW_CLASSIC?\"1\"!=f?Format.classicMarkerImage.src:Format.classicFilledMarkerImage.src:b==mxConstants.ARROW_CLASSIC_THIN?\"1\"!=f?Format.classicThinMarkerImage.src:Format.openThinFilledMarkerImage.src:b==mxConstants.ARROW_OPEN?Format.openFilledMarkerImage.src:b==mxConstants.ARROW_OPEN_THIN?Format.openThinFilledMarkerImage.src:b==mxConstants.ARROW_BLOCK?\n\"1\"!=f?Format.blockMarkerImage.src:Format.blockFilledMarkerImage.src:b==mxConstants.ARROW_BLOCK_THIN?\"1\"!=f?Format.blockThinMarkerImage.src:Format.blockThinFilledMarkerImage.src:b==mxConstants.ARROW_OVAL?\"1\"!=f?Format.ovalMarkerImage.src:Format.ovalFilledMarkerImage.src:b==mxConstants.ARROW_DIAMOND?\"1\"!=f?Format.diamondMarkerImage.src:Format.diamondFilledMarkerImage.src:b==mxConstants.ARROW_DIAMOND_THIN?\"1\"!=f?Format.diamondThinMarkerImage.src:Format.diamondThinFilledMarkerImage.src:\"doubleBlock\"==\nb?\"1\"!=f?Format.doubleBlockMarkerImage.src:Format.doubleBlockFilledMarkerImage.src:\"box\"==b?Format.boxMarkerImage.src:\"halfCircle\"==b?Format.halfCircleMarkerImage.src:\"openAsync\"==b?Format.openAsyncFilledMarkerImage.src:\"async\"==b?\"1\"!=f?Format.asyncMarkerImage.src:Format.asyncFilledMarkerImage.src:\"dash\"==b?Format.dashMarkerImage.src:\"baseDash\"==b?Format.baseDashMarkerImage.src:\"cross\"==b?Format.crossMarkerImage.src:\"circle\"==b?Format.circleMarkerImage.src:\"circlePlus\"==b?Format.circlePlusMarkerImage.src:\n\"ERone\"==b?Format.EROneMarkerImage.src:\"ERmandOne\"==b?Format.ERmandOneMarkerImage.src:\"ERmany\"==b?Format.ERmanyMarkerImage.src:\"ERoneToMany\"==b?Format.ERoneToManyMarkerImage.src:\"ERzeroToOne\"==b?Format.ERzeroToOneMarkerImage.src:\"ERzeroToMany\"==b?Format.ERzeroToManyMarkerImage.src:null};\nEditorUi.prototype.getImageForEdgeShape=function(b){var f=Format.connectionImage.src;\"link\"==b.shape?f=Format.linkEdgeImage.src:\"flexArrow\"==b.shape?f=Format.arrowImage.src:\"arrow\"==b.shape?f=Format.simpleArrowImage.src:\"filledEdge\"==b.shape?f=Format.filledEdgeImage.src:\"pipe\"==b.shape?f=Format.pipeEdgeImage.src:\"wire\"==b.shape&&(f=Format.wireEdgeImage.src);return f};\nEditorUi.prototype.getImageForEdgeStyle=function(b){var f=Format.orthogonalImage.src,m=mxUtils.getValue(b,mxConstants.STYLE_EDGE,null);\"1\"==mxUtils.getValue(b,mxConstants.STYLE_NOEDGESTYLE,null)&&(m=null);\"orthogonalEdgeStyle\"==m&&\"1\"==mxUtils.getValue(b,mxConstants.STYLE_CURVED,null)?f=Format.curvedImage.src:\"straight\"==m||\"none\"==m||null==m?f=Format.straightImage.src:\"entityRelationEdgeStyle\"==m?f=Format.entityImage.src:\"elbowEdgeStyle\"==m?f=\"vertical\"==mxUtils.getValue(b,mxConstants.STYLE_ELBOW,\nnull)?Format.verticalElbowImage.src:Format.horizontalElbowImage.src:\"isometricEdgeStyle\"==m&&(f=\"vertical\"==mxUtils.getValue(b,mxConstants.STYLE_ELBOW,null)?Format.verticalIsometricImage.src:Format.horizontalIsometricImage.src);return f};EditorUi.prototype.createMenus=function(){return null};\nEditorUi.prototype.updatePasteActionStates=function(){var b=this.editor.graph,f=this.actions.get(\"paste\"),m=this.actions.get(\"pasteHere\");f.setEnabled(this.editor.graph.cellEditor.isContentEditing()||(null!=navigator.clipboard||!mxClipboard.isEmpty())&&b.isEnabled()&&!b.isCellLocked(b.getDefaultParent()));m.setEnabled(f.isEnabled())};\nEditorUi.prototype.initClipboard=function(){var b=this,f=mxClipboard.cut;mxClipboard.cut=function(u){u.cellEditor.isContentEditing()?document.execCommand(\"cut\",!1,null):f.apply(this,arguments);b.updatePasteActionStates()};mxClipboard.copy=function(u){var D=null;if(u.cellEditor.isContentEditing())document.execCommand(\"copy\",!1,null);else{D=D||u.getSelectionCells();D=u.getExportableCells(u.model.getTopmostCells(D));for(var I={},V=u.createCellLookup(D),F=u.cloneCells(D,null,I),Q=new mxGraphModel,N=Q.getChildAt(Q.getRoot(),\n0),c=0;c=this.view.scale*this.cumulativeZoomFactor?this.cumulativeZoomFactor*=(this.view.scale+.05)/this.view.scale:(this.cumulativeZoomFactor*=\nja,this.cumulativeZoomFactor=Math.round(this.view.scale*this.cumulativeZoomFactor*100)/100/this.view.scale):.15>=this.view.scale*this.cumulativeZoomFactor?this.cumulativeZoomFactor*=(this.view.scale-.05)/this.view.scale:(this.cumulativeZoomFactor/=ja,this.cumulativeZoomFactor=Math.round(this.view.scale*this.cumulativeZoomFactor*100)/100/this.view.scale);this.cumulativeZoomFactor=Math.max(.05,Math.min(this.view.scale*this.cumulativeZoomFactor,160))/this.view.scale;if(b.isFastZoomEnabled()){null==S&&\n\"\"!=ba.getAttribute(\"filter\")&&(S=ba.getAttribute(\"filter\"),ba.removeAttribute(\"filter\"));J=new mxPoint(b.container.scrollLeft,b.container.scrollTop);aa=Math.round(Math.round(this.view.scale*this.cumulativeZoomFactor*100)/100*20)/(20*this.view.scale);ja=T||null==z?b.container.scrollLeft+b.container.clientWidth/2:z.x+b.container.scrollLeft-b.container.offsetLeft;var ka=T||null==z?b.container.scrollTop+b.container.clientHeight/2:z.y+b.container.scrollTop-b.container.offsetTop;ba.style.transformOrigin=\nja+\"px \"+ka+\"px\";ba.style.transform=\"scale(\"+aa+\")\";pa.style.transformOrigin=ja+\"px \"+ka+\"px\";pa.style.transform=\"scale(\"+aa+\")\";null!=b.view.backgroundPageShape&&null!=b.view.backgroundPageShape.node?(ja=b.view.backgroundPageShape.node,mxUtils.setPrefixedStyle(ja.style,\"transform-origin\",(T||null==z?b.container.clientWidth/2+b.container.scrollLeft-ja.offsetLeft+\"px\":z.x+b.container.scrollLeft-ja.offsetLeft-b.container.offsetLeft+\"px\")+\" \"+(T||null==z?b.container.clientHeight/2+b.container.scrollTop-\nja.offsetTop+\"px\":z.y+b.container.scrollTop-ja.offsetTop-b.container.offsetTop+\"px\")),mxUtils.setPrefixedStyle(ja.style,\"transform\",\"scale(\"+aa+\")\")):b.view.validateBackgroundStyles(aa,ja,ka);b.view.getDecoratorPane().style.opacity=\"0\";b.view.getOverlayPane().style.opacity=\"0\";T=b.container.querySelectorAll(\".geHint\");for(ja=0;jaMath.abs(aa.deltaY)&&Math.round(aa.deltaY)!=aa.deltaY?Y=1+Math.abs(aa.deltaY)/20*(Y-1):null!=aa.movementY&&\"pointermove\"==aa.type&&(Y=1+Math.max(1,Math.abs(aa.movementY))/20*(Y-1),ra=-1);b.lazyZoom(T,null,ra,Y);null!=ka&&1!=oa&&b.isFastZoomEnabled()&&b.scrollbars&&(T=b.cumulativeZoomFactor,Y=T/oa,ra=1-T,.001f.scrollLeft+.9*f.clientWidth&&(f.scrollLeft=Math.min(m.x+\nm.width-f.clientWidth,m.x-10)),m.y>f.scrollTop+.9*f.clientHeight&&(f.scrollTop=Math.min(m.y+m.height-f.clientHeight,m.y-10)))}else if(m=b.getGraphBounds(),0==m.width&&0==m.height)f.scrollLeft=(f.scrollWidth-f.clientWidth)/2,f.scrollTop=(f.scrollHeight-f.clientHeight)/2;else{var p=Math.max(m.height,b.scrollTileSize.height*b.view.scale);f.scrollLeft=Math.floor(Math.max(0,m.x-Math.max(0,(f.clientWidth-Math.max(m.width,b.scrollTileSize.width*b.view.scale))/2)));f.scrollTop=Math.floor(Math.max(0,m.y-Math.max(20,\n(f.clientHeight-p)/4)))}else{m=mxRectangle.fromRectangle(b.pageVisible?b.view.getBackgroundPageBounds():b.getGraphBounds());p=b.view.translate;var v=b.view.scale;m.x=m.x/v-p.x;m.y=m.y/v-p.y;m.width/=v;m.height/=v;p=b.pageVisible?0:Math.max(0,(f.clientHeight-m.height)/4);\"1\"==urlParams.embedInline?b.view.setTranslate(Math.floor(-m.x+2),Math.floor(p-m.y+1)):b.view.setTranslate(Math.floor(Math.max(0,(f.clientWidth-m.width)/2)-m.x+2),Math.floor(p-m.y+1))}};\nEditorUi.prototype.setPageVisible=function(b){var f=this.editor.graph,m=mxUtils.hasScrollbars(f.container),p=0,v=0;m&&(p=f.view.translate.x*f.view.scale-f.container.scrollLeft,v=f.view.translate.y*f.view.scale-f.container.scrollTop);f.pageVisible=b;f.pageBreaksVisible=b;f.preferPageSize=b;f.view.validateBackground();if(m){var u=f.getSelectionCells();f.clearSelection();f.setSelectionCells(u)}f.sizeDidChange();m&&(f.container.scrollLeft=f.view.translate.x*f.view.scale-p,f.container.scrollTop=f.view.translate.y*\nf.view.scale-v);f.defaultPageVisible=b;this.fireEvent(new mxEventObject(\"pageViewChanged\"))};\nEditorUi.prototype.installResizeHandler=function(b,f,m){f&&(b.window.setSize=function(v,u){if(!this.minimized){var D=window.innerWidth||document.body.clientWidth||document.documentElement.clientWidth,I=window.innerHeight||document.body.clientHeight||document.documentElement.clientHeight,V=this.getX(),F=this.getY(),Q=V+v>D?Math.max(0,D-v):V,N=F+u>I?Math.max(0,I-u):F;Q==V&&N==F||mxWindow.prototype.setLocation.call(this,Q,N);v=Math.min(v,D);u=Math.min(u,I)}mxWindow.prototype.setSize.apply(this,arguments)});\nb.window.setLocation=function(v,u){if(null!=this.div){var D=window.innerWidth||document.body.clientWidth||document.documentElement.clientWidth,I=window.innerHeight||document.body.clientHeight||document.documentElement.clientHeight,V=parseInt(this.div.style.width),F=parseInt(this.div.style.height);v=Math.max(0,Math.min(v,Math.max(0,D-V)));u=Math.max(0,Math.min(u,Math.max(0,I-F)));this.getX()==v&&this.getY()==u||mxWindow.prototype.setLocation.apply(this,arguments);f&&!this.minimized&&(D=Math.min(V,\nD),I=Math.min(F,I),D==V&&I==F||this.setSize(D,I))}};var p=mxUtils.bind(this,function(){var v=b.window.getX(),u=b.window.getY();b.window.setLocation(v,u)});mxEvent.addListener(window,\"resize\",p);b.destroy=function(){mxEvent.removeListener(window,\"resize\",p);b.window.destroy();null!=m&&m()}};function ChangeGridColor(b,f){this.ui=b;this.color=f}ChangeGridColor.prototype.execute=function(){var b=this.ui.editor.graph.view.gridColor;this.ui.setGridColor(this.color);this.color=b};\nfunction ChangePageSetup(b,f,m,p,v){this.ui=b;this.previousColor=this.color=f;this.previousImage=this.image=m;this.previousFormat=this.format=p;this.previousPageScale=this.pageScale=v;this.ignoreImage=this.ignoreColor=!1}\nChangePageSetup.prototype.execute=function(){var b=this.ui.editor.graph;if(!this.ignoreColor){this.color=this.previousColor;var f=b.background;this.ui.setBackgroundColor(this.previousColor);this.previousColor=f}if(!this.ignoreImage){this.image=this.previousImage;f=b.backgroundImage;var m=this.previousImage;null!=m&&Graph.isPageLink(m.src)&&(m=this.ui.createImageForPageLink(m.src,this.ui.currentPage));this.ui.setBackgroundImage(m);this.previousImage=f}null!=this.previousFormat&&(this.format=this.previousFormat,\nf=b.pageFormat,this.previousFormat.width!=f.width||this.previousFormat.height!=f.height)&&(this.ui.setPageFormat(this.previousFormat),this.previousFormat=f);null!=this.foldingEnabled&&this.foldingEnabled!=this.ui.editor.graph.foldingEnabled&&(this.ui.setFoldingEnabled(this.foldingEnabled),this.foldingEnabled=!this.foldingEnabled);null!=this.previousPageScale&&(b=this.ui.editor.graph.pageScale,this.previousPageScale!=b&&(this.ui.setPageScale(this.previousPageScale),this.previousPageScale=b))};\nEditorUi.prototype.setBackgroundColor=function(b){this.editor.graph.background=b;this.editor.graph.view.validateBackground();this.fireEvent(new mxEventObject(\"backgroundColorChanged\"))};EditorUi.prototype.setFoldingEnabled=function(b){this.editor.graph.foldingEnabled=b;this.editor.graph.view.revalidate();this.fireEvent(new mxEventObject(\"foldingEnabledChanged\"))};\nEditorUi.prototype.setPageFormat=function(b,f){f=null!=f?f:\"1\"==urlParams.sketch;this.editor.graph.pageFormat=b;f||(this.editor.graph.pageVisible?(this.editor.graph.view.validateBackground(),this.editor.graph.sizeDidChange()):this.actions.get(\"pageView\").funct());this.fireEvent(new mxEventObject(\"pageFormatChanged\"))};\nEditorUi.prototype.setPageScale=function(b){this.editor.graph.pageScale=b;this.editor.graph.pageVisible?(this.editor.graph.view.validateBackground(),this.editor.graph.sizeDidChange()):this.actions.get(\"pageView\").funct();this.fireEvent(new mxEventObject(\"pageScaleChanged\"))};\nEditorUi.prototype.setGridColor=function(b,f){f=null!=f?f:Editor.isDarkMode();var m=this.editor.graph;f?m.view.defaultDarkGridColor=b:m.view.defaultGridColor=b;m.view.gridColor=\"light-dark(\"+m.view.defaultGridColor+\", \"+m.view.defaultDarkGridColor+\")\";m.view.validateBackground();this.fireEvent(new mxEventObject(\"gridColorChanged\"))};\nEditorUi.prototype.addUndoListener=function(){var b=this.editor.undoManager,f=mxUtils.bind(this,function(){this.updateActionStates()});b.addListener(mxEvent.ADD,f);b.addListener(mxEvent.UNDO,f);b.addListener(mxEvent.REDO,f);b.addListener(mxEvent.CLEAR,f);var m=this.editor.graph.cellEditor.startEditing;this.editor.graph.cellEditor.startEditing=function(){m.apply(this,arguments);f()};var p=this.editor.graph.cellEditor.stopEditing;this.editor.graph.cellEditor.stopEditing=function(v,u){p.apply(this,arguments);\nf()};f()};\nEditorUi.prototype.updateActionStates=function(){for(var b=this.editor.graph,f=this.getSelectionState(),m=b.isEnabled()&&!b.isCellLocked(b.getDefaultParent()),p=!this.editor.chromeless||this.editor.editable,v=\"cut copy bold italic underline delete duplicate editStyle editTooltip editLink backgroundColor borderColor edit toFront toBack solid dashed pasteSize dotted fillColor gradientColor shadow fontColor formattedText rounded toggleRounded strokeColor sharp snapToGrid\".split(\" \"),u=0;um&&(f=b.substring(m,p+21).replace(/>/g,\">\").replace(/</g,\"<\").replace(/\\\\"/g,'\"').replace(/\\n/g,\"\"))}}catch(v){}return f};\nEditorUi.prototype.extractGraphModelFromEvent=function(b){var f=null,m=null;null!=b&&(b=null!=b.dataTransfer?b.dataTransfer:b.clipboardData,null!=b&&(m=0<=mxUtils.indexOf(b.types,\"text/html\")?b.getData(\"text/html\"):null,0<=mxUtils.indexOf(b.types,\"text/plain\")&&(null==m||0==m.length)&&(m=b.getData(\"text/plain\")),null!=m&&(m=Graph.zapGremlins(mxUtils.trim(m)),b=this.extractGraphModelFromHtml(m),null!=b&&(m=b))));null!=m&&this.isCompatibleString(m)&&(f=m);return f};\nEditorUi.prototype.isCompatibleString=function(b){return!1};EditorUi.prototype.saveFile=function(b){b||null==this.editor.filename?(b=new FilenameDialog(this,this.editor.getOrCreateFilename(),mxResources.get(\"save\"),mxUtils.bind(this,function(f){this.save(f)}),null,mxUtils.bind(this,function(f){if(null!=f&&0M||Math.abs(D.y-z.getGraphY())>M){var S=null;mxEvent.isShiftDown(z.getEvent())||(S=this.selectionCellsHandler.getHandler(J.cell));if(null!=S&&null!=\nS.bends&&0';Graph.svgDoctype='';Graph.svgFileComment=\"\\x3c!-- Do not edit this file with editors other than draw.io --\\x3e\";\nGraph.colorStyles=[mxConstants.STYLE_FONTCOLOR,mxConstants.STYLE_FILLCOLOR,mxConstants.STYLE_GRADIENTCOLOR,mxConstants.STYLE_STROKECOLOR,mxConstants.STYLE_IMAGE_BORDER,mxConstants.STYLE_IMAGE_BACKGROUND,mxConstants.STYLE_LABEL_BORDERCOLOR,mxConstants.STYLE_SWIMLANE_FILLCOLOR,mxConstants.STYLE_LABEL_BACKGROUNDCOLOR];Graph.textStyles=\"fontFamily fontSource fontSize fontColor fontStyle textOpacity labelBorderColor labelBackgroundColor autosize resizable horizontal textDirection autosizeText\".split(\" \");\nGraph.pasteTextStyles=\"fontFamily fontSource fontSize fontColor fontStyle textOpacity align verticalAlign spacingLeft spacingRight spacingTop spacingBottom spacing labelBorderColor labelBackgroundColor horizontal textDirection\".split(\" \");Graph.edgeStyles=\"edgeStyle elbow jumpStyle jumpSize startArrow startFill startSize endArrow endFill endSize flowAnimation flowAnimationDirection flowAnimationTimingFunction flowAnimationDuration sourcePerimeterSpacing targetPerimeterSpacing curved linecap linejoin\".split(\" \");\nGraph.cellStyleGroups=[[\"startArrow\",\"startFill\",\"endArrow\",\"endFill\"],[\"startSize\",\"endSize\"],[\"sourcePerimeterSpacing\",\"targetPerimeterSpacing\"],[\"fillColor\",\"gradientColor\",\"gradientDirection\"]];\nGraph.cellStyles=mxUtils.addItems(mxUtils.addItems(mxUtils.addItems(\"rounded shadow glass dashed dashPattern comic sketch fillWeight hachureGap hachureAngle jiggle disableMultiStroke disableMultiStrokeFill fillStyle curveFitting simplification sketchStyle pointerEvents opacity strokeColor strokeWidth align verticalAlign spacingLeft spacingRight spacingTop spacingBottom spacing arcSize absoluteArcSize comicStyle swimlaneFillColor shadowOffsetX shadowOffsetY shadowBlur shadowColor shadowOpacity\".split(\" \"),Graph.textStyles),\nGraph.edgeStyles),Graph.cellStyleGroups);Graph.layoutNames=\"mxHierarchicalLayout mxCircleLayout mxCompactTreeLayout mxEdgeLabelLayout mxFastOrganicLayout mxParallelEdgeLayout mxPartitionLayout mxRadialTreeLayout mxStackLayout\".split(\" \");Graph.backupStyleAttribute=\"data-mx-given-colors\";Graph.newColorPlaceholder=\"rgba(0, 0, 0, 0)\";Graph.newBackgroundPlaceholder=\"rgb(0, 0, 0)\";Graph.defaultAdaptiveColors=\"0\"==urlParams.contrast?\"simple\":\"auto\";\nGraph.getDefaultAdaptiveColorsKey=function(){return\"auto\"==Graph.defaultAdaptiveColors?\"automatic\":Graph.defaultAdaptiveColors};\nGraph.createOffscreenGraph=function(b){var f=new Graph(document.createElement(\"div\"));f.stylesheet.styles=mxUtils.clone(b.styles);f.resetViewOnRootChange=!1;f.setConnectable(!1);f.gridEnabled=!1;f.autoScroll=!1;f.setTooltips(!1);f.setEnabled(!1);f.container.style.visibility=\"hidden\";f.container.style.position=\"absolute\";f.container.style.overflow=\"hidden\";f.container.style.height=\"1px\";f.container.style.width=\"1px\";return f};\nGraph.createSvgImage=function(b,f,m,p,v){m=unescape(encodeURIComponent(Graph.svgDoctype+''+m+\"\"));return new mxImage(\"data:image/svg+xml;base64,\"+(window.btoa?btoa(m):Base64.encode(m,!0)),b,f)};\nGraph.getSvgFromDataUri=function(b){return null!=b&&\"data:image/svg\"==b.substring(0,14)?Graph.xmlDeclaration+\"\\n\"+Graph.svgDoctype+\"\\n\"+decodeURIComponent(escape(atob(b.substring(b.indexOf(\",\")+1)))):null};\nGraph.createSvgNode=function(b,f,m,p,v){var u=mxUtils.createXmlDocument(),D=null!=u.createElementNS?u.createElementNS(mxConstants.NS_SVG,\"svg\"):u.createElement(\"svg\");null!=v&&D.setAttribute(\"style\",\"background: \"+v.light+\"; background-color: \"+v.cssText+\";\");null==u.createElementNS?(D.setAttribute(\"xmlns\",mxConstants.NS_SVG),D.setAttribute(\"xmlns:xlink\",mxConstants.NS_XLINK)):D.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\"xmlns:xlink\",mxConstants.NS_XLINK);D.setAttribute(\"version\",\"1.1\");D.setAttribute(\"width\",\nm+\"px\");D.setAttribute(\"height\",p+\"px\");D.setAttribute(\"viewBox\",b+\" \"+f+\" \"+m+\" \"+p);u.appendChild(D);return D};\nGraph.htmlToPng=function(b,f,m,p,v,u){v=null!=v?v:\"\";u=null!=u?u:Editor.htmlRasterScale;var D=document.createElement(\"canvas\");D.width=f*u;D.height=m*u;var I=document.createElement(\"img\");I.onload=mxUtils.bind(this,function(){try{var F=D.getContext(\"2d\");F.scale(u,u);F.drawImage(I,0,0);p(D.toDataURL())}catch(Q){p(null)}});var V=mxUtils.createXmlDocument();V=null!=V.createElementNS?V.createElementNS(mxConstants.NS_SVG,\"svg\"):V.createElement(\"svg\");b=(new mxSvgCanvas2D(V)).convertHtml(b);I.onerror=\nfunction(F){p(null)};I.src=\"data:image/svg+xml,\"+encodeURIComponent(''+(\"\"!=v?'\":\"\")+'
'+b+\"
\")};\nGraph.addLightDarkColors=function(b,f,m,p){function v(c,g,l){return null!=p?p(c,g,l):mxUtils.isValidColor(l)?(null==D&&null!=f&&(D={}),null!=D&&(D[g]=c.style.getPropertyValue(g)),l=mxUtils.getLightDarkColor(l,null,null,m),c.style.setProperty(g,l.cssText),!0):!1}for(var u=b.getElementsByTagName(\"*\"),D=null,I=!1,V=0;VpageSize){var O=c.startIndex||0;H=E.slice(Math.max(0,O),Math.min(E.length,O+pageSize))}H=p.getOpposites(H,B).concat(H);var W=N.cloneCells(H);for(K=0;KL.geometry.x?-.8:.5(c.startIndex||0)+pageSize){var ha=N.createVertex(null,null,mxResources.get(\"nextPage\")+\" (\"+(Math.ceil((c.startIndex||0)/pageSize)+2)+\"/\"+Math.ceil(E.length/pageSize)+\")\",0,0,120,30,\"fillColor=green;fontColor=white;strokeColor=green;rounded=1;\");ha.referenceCell=t;ha.startIndex=(c.startIndex||0)+pageSize;W.splice(0,0,ha)}for(var ia in N.getModel().cells){var ea=N.getModel().getCell(ia);ea!=N.rootCell&&\n!N.getModel().isAncestor(N.rootCell,ea)&&N.getModel().isVertex(ea)&&N.removeCells([ea])}N.addCells(W);var la=N.getModel().getGeometry(N.rootCell);null!=la&&(la=la.clone(),la.x=g-la.width/2,la.y=l-la.height/3,N.getModel().setGeometry(N.rootCell,la));c=[];for(ia in N.getModel().cells)ea=N.getModel().getCell(ia),ea!=N.rootCell&&N.getModel().isVertex(ea)&&N.getModel().getParent(ea)==N.getDefaultParent()&&(c.push(ea),la=N.getModel().getGeometry(ea),null!=la&&(la.x=g-la.width/2,la.y=l-la.height/2));var pa=\nc.length,ba=2*Math.PI/pa,k=Math.max(minSize,Math.min(N.container.offsetWidth/2-40,N.container.offsetHeight/2-80));for(g=0;g=f.attributes.length){for(;null!=f.firstChild;)b.insertBefore(f.firstChild,f);b.removeChild(f)}else if(\"SPAN\"==b.nodeName&&Graph.stylesCompatible(p,b.getAttribute(\"style\"))&&\n1==f.attributes.length){for(;null!=f.firstChild;)b.insertBefore(f.firstChild,f);b.removeChild(f)}}f=m}b.normalize()}};Graph.sanitizeLink=function(b){if(null==b)return null;var f=document.createElement(\"a\");f.setAttribute(\"href\",b);Graph.sanitizeNode(f);return f.getAttribute(\"href\")};Graph.sanitizeNode=function(b){return Graph.domPurify(b,!0)};\nGraph.isStyleAllowed=function(b){b=b.replace(/\\\\([0-9a-fA-F]{1,6})\\s?/g,function(v,u){return String.fromCharCode(parseInt(u,16))}).replace(/\\\\(.)/g,\"$1\");for(var f=/url\\s*\\(\\s*(['\"]?)(.*?)\\1\\s*\\)/gi,m;null!==(m=f.exec(b));){var p=m[2].trim();m=!/^([a-z][a-z0-9+.-]*:|\\/\\/)/i.test(p);p=p.toLowerCase().startsWith(\"data:\");if(!m&&!p)return!1}return/@import/i.test(b)?!1:!0};\nDOMPurify.addHook(\"afterSanitizeAttributes\",function(b){\"use\"==b.nodeName&&(null!=b.getAttribute(\"xlink:href\")&&!b.getAttribute(\"xlink:href\").startsWith(\"#\")||null!=b.getAttribute(\"href\")&&!b.getAttribute(\"href\").startsWith(\"#\"))?b.remove():\"style\"==b.nodeName.toLowerCase()&&(Graph.isStyleAllowed(b.textContent)||b.remove())});DOMPurify.addHook(\"uponSanitizeAttribute\",function(b,f){\"style\"!=f.attrName||Graph.isStyleAllowed(f.attrValue)||(f.keepAttr=!1)});\nGraph.domPurify=function(b,f){window.DOM_PURIFY_CONFIG.IN_PLACE=f;return DOMPurify.sanitize(b,window.DOM_PURIFY_CONFIG)};\nGraph.clipSvgDataUri=function(b){if(null!=b&&\"data:image/svg+xml;base64,\"==b.substring(0,26))try{var f=document.createElement(\"div\");f.style.position=\"absolute\";f.style.visibility=\"hidden\";var m=decodeURIComponent(escape(atob(b.substring(26)))),p=m.indexOf(\"t.x&&0>t.y&&t.width>u&&t.height>D?f.getElementsByTagName(\"svg\")[0].setAttribute(\"viewBox\",\"0 0 \"+u+\" \"+D):(f.getElementsByTagName(\"svg\")[0].setAttribute(\"viewBox\",\nt.x+\" \"+t.y+\" \"+t.width+\" \"+t.height),f.getElementsByTagName(\"svg\")[0].setAttribute(\"width\",t.width/m),f.getElementsByTagName(\"svg\")[0].setAttribute(\"height\",t.height/p))}}catch(E){}finally{document.body.removeChild(f)}b=Editor.createSvgDataUri(mxUtils.getXml(v[0]))}}}catch(E){}return b};\nGraph.createRemoveIcon=function(b,f){var m=document.createElement(\"img\");m.setAttribute(\"src\",Dialog.prototype.clearImage);m.setAttribute(\"title\",b);m.setAttribute(\"width\",\"13\");m.setAttribute(\"height\",\"10\");m.style.marginLeft=\"4px\";m.style.marginBottom=\"-1px\";m.style.cursor=\"pointer\";mxEvent.addListener(m,\"click\",f);return m};Graph.isPageLink=function(b){return null!=b&&\"data:page/id,\"==b.substring(0,13)};\nGraph.rewritePageLinks=function(b,f){function m(v,u){var D=v.getAttribute(u);if(null!=D){Graph.isPageLink(D)&&(D=\"#\"+D.substring(D.indexOf(\":\")+1));var I=u;f&&\"xlink:href\"==u&&(v.removeAttribute(u),I=\"href\");v.setAttribute(I,D)}}b=b.getElementsByTagName(\"a\");if(null!=b)for(var p=0;p div\");for(v=p=0;vmxUtils.indexOf(V,\nQ)})),this.updateCellStyles(D,I))};Graph.prototype.copyCellStyles=function(D,I,V,F,Q,N,c){var g=!1,l=!1;if(0mxUtils.indexOf(Graph.edgeStyles,O))null!=ea?(g=mxUtils.setStyle(g,O,ea),\"fontFamily\"==O&&null==l.fontSource&&(g=mxUtils.setStyle(g,\"fontSource\",null)),ia&&\"rounded\"==O&&\"1\"==ea&&null==l.curved&&(g=mxUtils.setStyle(g,\"curved\",null))):F&&(g=mxUtils.setStyle(g,O,null))}Editor.simpleLabels&&(g=mxUtils.setStyle(mxUtils.setStyle(g,\"html\",null),\"whiteSpace\",null));this.model.setStyle(c,g)}}finally{this.model.endUpdate()}return D};Graph.prototype.pasteTextStyles=\nfunction(D,I){this.model.beginUpdate();try{for(var V=0;Vc?\"a\":\"p\",tt:12>c?\"am\":\"pm\",T:12>c?\"A\":\"P\",TT:12>c?\"AM\":\"PM\",Z:m?\"UTC\":(String(b).match(v)||[\"\"]).pop().replace(u,\"\"),o:(0u&&\"%\"==f.charAt(v.index-1))I=D.substring(1);else{var V=D.substring(1,D.length-1),F={mm:mxConstants.MILLIMETERS,\"in\":mxConstants.INCHES,m:mxConstants.METERS,current:this.view.unit};if(\"id\"==V)I=b.id;else if(\"width\"===V.substring(0,5)&&this.model.isVertex(b)){var Q=this.getCellGeometry(b);null!=Q&&(I=Q.width,\n5V.indexOf(\"{\"))for(F=b;null==I&&null!=F;)null!=F.value&&\n\"object\"==typeof F.value&&(Graph.translateDiagram&&null!=Graph.diagramLanguage&&(I=F.getAttribute(V+\"_\"+Graph.diagramLanguage)),null==I&&(I=F.hasAttribute(V)?null!=F.getAttribute(V)?F.getAttribute(V):\"\":null)),F=this.model.getParent(F);null==I&&(\"pagecount\"==V.substring(0,9)&&9I[0].indexOf(\"=\")&&(I=I.slice(1));this.model.setStyle(f[u],I.join(\";\"))}this.setCellStyles(mxConstants.STYLE_PERIMETER,null,[f[u]]);this.setCellStyles(\"points\",null,[f[u]]);this.pasteStyle(v,[f[u]],null,!0)}else v=this.copyStyle(f[u]),this.model.setStyle(f[u],p),this.pasteStyle(v,[f[u]]);\"1\"==mxUtils.getValue(this.getCellStyle(f[u],!1),\"composite\",\"0\")&&this.removeChildCells(f[u])}}finally{this.model.endUpdate()}};\nGraph.prototype.selectCellsForConnectVertex=function(b,f,m){2==b.length&&this.model.isVertex(b[1])?(this.setSelectionCell(b[1]),this.scrollCellToVisible(b[1]),null!=m&&(mxEvent.isTouchEvent(f)?m.update(m.getState(this.view.getState(b[1]))):m.reset())):this.setSelectionCells(b)};Graph.prototype.isCloneConnectSource=function(b){var f=null;null!=this.layoutManager&&(f=this.layoutManager.getLayout(this.model.getParent(b)));return this.isTableRow(b)||this.isTableCell(b)||null!=f&&f.constructor==mxStackLayout};\nGraph.prototype.insertEdgeBeforeCell=function(b,f){for(var m=f;null!=m.parent&&null!=m.geometry&&m.geometry.relative&&m.parent!=b.parent;)m=this.model.getParent(m);null!=m&&null!=m.parent&&m.parent==b.parent&&(f=m.parent.getIndex(m),this.model.add(m.parent,b,f))};\nGraph.prototype.connectVertex=function(b,f,m,p,v,u,D,I){u=u?u:!1;if(b.geometry.relative&&this.model.isEdge(b.parent))return[];for(;b.geometry.relative&&this.model.isVertex(b.parent);)b=b.parent;var V=this.isCloneConnectSource(b),F=V?b:this.getCompositeParent(b),Q=b.geometry.relative&&null!=b.parent.geometry?new mxPoint(b.parent.geometry.width*b.geometry.x,b.parent.geometry.height*b.geometry.y):new mxPoint(F.geometry.x,F.geometry.y);f==mxConstants.DIRECTION_NORTH?(Q.x+=F.geometry.width/2,Q.y-=m):f==\nmxConstants.DIRECTION_SOUTH?(Q.x+=F.geometry.width/2,Q.y+=F.geometry.height+m):(Q.x=f==mxConstants.DIRECTION_WEST?Q.x-m:Q.x+(F.geometry.width+m),Q.y+=F.geometry.height/2);var N=this.view.getState(this.model.getParent(b));m=this.view.scale;var c=this.view.translate;F=c.x*m;c=c.y*m;null!=N&&this.model.isVertex(N.cell)&&(F=N.x,c=N.y);this.model.isVertex(b.parent)&&b.geometry.relative&&(Q.x+=b.parent.geometry.x,Q.y+=b.parent.geometry.y);u=u?null:(new mxRectangle(F+Q.x*m,c+Q.y*m)).grow(40*m);u=null!=u?\nthis.getCells(0,0,0,0,null,null,u,null,!0):null;N=this.view.getState(b);var g=null,l=null;if(null!=u){u=u.reverse();for(var t=0;tthis.view.scale?this.zoom((this.view.scale+.01)/this.view.scale):this.zoom(Math.round(this.view.scale*this.zoomFactor*20)/20/this.view.scale)};\nGraph.prototype.zoomOut=function(){.15>=this.view.scale?this.zoom((this.view.scale-.01)/this.view.scale):this.zoom(Math.round(1/this.zoomFactor*this.view.scale*20)/20/this.view.scale)};\nGraph.prototype.fitPages=function(b,f){var m=1;null==b&&(m=this.getPageLayout(),b=m.width,m=m.height);var p=this.pageScale,v=this.pageFormat,u=this.container.clientHeight-10;b=(this.container.clientWidth-10)/(b*v.width)/p;this.zoomTo(Math.floor(20*(f?b:Math.min(b,u/(m*v.height)/p)))/20);mxUtils.hasScrollbars(this.container)&&(b=this.getPagePadding(),this.container.scrollLeft=Math.min(b.x*this.view.scale,(this.container.scrollWidth-this.container.clientWidth)/2),f||(this.container.scrollTop=Math.min(b.y*\nthis.view.scale,(this.container.scrollHeight-this.container.clientHeight)/2)))};\nGraph.prototype.fitWindow=function(b,f,m,p,v){f=null!=f?f:10;var u=this.container.clientWidth-f,D=this.container.clientHeight-f,I=Math.floor(20*Math.min(u/b.width,D/b.height))/20;null!=m&&(I=Math.min(I,m));if(!p||ImxUtils.indexOf(m,b[v].nodeName))&&0D.name?1:0});for(v=0;v\"+mxUtils.htmlEntities(p[v].name)+\": \":\"\")+mxUtils.htmlEntities(p[v].value)+\"\\n\");0'+f+\"\"))}return f};\nGraph.prototype.addFlowAnimationToNode=function(b,f,m,p){if(null!=b&&null!=p){var v=b.getAttribute(\"stroke-dasharray\");if(\"\"==v||null==v){var u=String(mxUtils.getValue(f,mxConstants.STYLE_DASH_PATTERN,\"8\")).split(\" \");v=1==mxUtils.getValue(f,mxConstants.STYLE_FIX_DASH,!1)||null==f.dashPattern?1:mxUtils.getNumber(f,mxConstants.STYLE_STROKEWIDTH,1);if(0');\nHoverIcons.prototype.triangleRight=Graph.createSvgImage(26,18,'');HoverIcons.prototype.triangleDown=Graph.createSvgImage(18,26,'');\nHoverIcons.prototype.triangleLeft=Graph.createSvgImage(28,18,'');HoverIcons.prototype.roundDrop=Graph.createSvgImage(26,26,'');\nHoverIcons.prototype.refreshTarget=new mxImage(\"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjM2cHgiIGhlaWdodD0iMzZweCI+PGVsbGlwc2UgZmlsbD0iIzI5YjZmMiIgY3g9IjEyIiBjeT0iMTIiIHJ4PSIxMiIgcnk9IjEyIi8+PHBhdGggdHJhbnNmb3JtPSJzY2FsZSgwLjgpIHRyYW5zbGF0ZSgyLjQsIDIuNCkiIHN0cm9rZT0iI2ZmZiIgZmlsbD0iI2ZmZiIgZD0iTTEyIDZ2M2w0LTQtNC00djNjLTQuNDIgMC04IDMuNTgtOCA4IDAgMS41Ny40NiAzLjAzIDEuMjQgNC4yNkw2LjcgMTQuOGMtLjQ1LS44My0uNy0xLjc5LS43LTIuOCAwLTMuMzEgMi42OS02IDYtNnptNi43NiAxLjc0TDE3LjMgOS4yYy40NC44NC43IDEuNzkuNyAyLjggMCAzLjMxLTIuNjkgNi02IDZ2LTNsLTQgNCA0IDR2LTNjNC40MiAwIDgtMy41OCA4LTggMC0xLjU3LS40Ni0zLjAzLTEuMjQtNC4yNnoiLz48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PC9zdmc+Cg==\",38,\n38);HoverIcons.prototype.tolerance=mxClient.IS_TOUCH?6:0;\nHoverIcons.prototype.init=function(){this.arrowUp=this.createArrow(this.triangleUp,mxResources.get(\"plusTooltip\"),mxConstants.DIRECTION_NORTH);this.arrowRight=this.createArrow(this.triangleRight,mxResources.get(\"plusTooltip\"),mxConstants.DIRECTION_EAST);this.arrowDown=this.createArrow(this.triangleDown,mxResources.get(\"plusTooltip\"),mxConstants.DIRECTION_SOUTH);this.arrowLeft=this.createArrow(this.triangleLeft,mxResources.get(\"plusTooltip\"),mxConstants.DIRECTION_WEST);this.elts=[this.arrowUp,this.arrowRight,\nthis.arrowDown,this.arrowLeft];this.resetHandler=mxUtils.bind(this,function(){this.reset()});this.repaintHandler=mxUtils.bind(this,function(){this.repaint()});this.graph.selectionModel.addListener(mxEvent.CHANGE,this.resetHandler);this.graph.model.addListener(mxEvent.CHANGE,this.repaintHandler);this.graph.view.addListener(mxEvent.SCALE_AND_TRANSLATE,this.repaintHandler);this.graph.view.addListener(mxEvent.TRANSLATE,this.repaintHandler);this.graph.view.addListener(mxEvent.SCALE,this.repaintHandler);\nthis.graph.view.addListener(mxEvent.DOWN,this.repaintHandler);this.graph.view.addListener(mxEvent.UP,this.repaintHandler);this.graph.addListener(mxEvent.ROOT,this.repaintHandler);this.graph.addListener(mxEvent.ESCAPE,this.resetHandler);mxEvent.addListener(this.graph.container,\"scroll\",this.resetHandler);this.graph.addListener(mxEvent.ESCAPE,mxUtils.bind(this,function(){this.mouseDownPoint=null}));mxEvent.addListener(this.graph.container,\"mouseleave\",mxUtils.bind(this,function(m){null!=m.relatedTarget&&\nmxEvent.getSource(m)==this.graph.container&&this.setDisplay(\"none\")}));this.graph.addListener(mxEvent.START_EDITING,mxUtils.bind(this,function(m){this.reset()}));var b=this.graph.click;this.graph.click=mxUtils.bind(this,function(m){b.apply(this.graph,arguments);null==this.currentState||this.graph.isCellSelected(this.currentState.cell)||!mxEvent.isTouchEvent(m.getEvent())||this.graph.model.isVertex(m.getCell())||this.reset()});var f=!1;this.graph.addMouseListener({mouseDown:mxUtils.bind(this,function(m,\np){f=!1;m=p.getEvent();this.isResetEvent(m)?this.reset():this.isActive()||(p=this.getState(p.getState()),null==p&&mxEvent.isTouchEvent(m)||this.update(p));this.setDisplay(\"none\")}),mouseMove:mxUtils.bind(this,function(m,p){m=p.getEvent();this.isResetEvent(m)?this.reset():this.graph.isMouseDown||mxEvent.isTouchEvent(m)||this.update(this.getState(p.getState()),p.getGraphX(),p.getGraphY());null!=this.graph.connectionHandler&&null!=this.graph.connectionHandler.shape&&(f=!0)}),mouseUp:mxUtils.bind(this,\nfunction(m,p){m=p.getEvent();mxUtils.convertPoint(this.graph.container,mxEvent.getClientX(m),mxEvent.getClientY(m));this.isResetEvent(m)?this.reset():this.isActive()&&!f&&null!=this.mouseDownPoint?this.click(this.currentState,this.getDirection(),p):this.isActive()?1==this.graph.getSelectionCount()&&this.graph.model.isEdge(this.graph.getSelectionCell())?this.reset():this.update(this.getState(this.graph.view.getState(this.graph.getCellAt(p.getGraphX(),p.getGraphY())))):mxEvent.isTouchEvent(m)||null!=\nthis.bbox&&mxUtils.contains(this.bbox,p.getGraphX(),p.getGraphY())?(this.setDisplay(\"\"),this.repaint()):mxEvent.isTouchEvent(m)||this.reset();f=!1;this.resetActiveArrow()})})};HoverIcons.prototype.isResetEvent=function(b,f){return mxEvent.isAltDown(b)||null==this.activeArrow&&mxEvent.isShiftDown(b)||mxEvent.isPopupTrigger(b)&&!this.graph.isCloneEvent(b)};\nHoverIcons.prototype.createArrow=function(b,f,m){var p=null;p=mxUtils.createImage(b.src);p.style.width=b.width+\"px\";p.style.height=b.height+\"px\";p.style.padding=this.tolerance+\"px\";null!=f&&p.setAttribute(\"title\",f);p.style.position=\"absolute\";p.style.cursor=this.cssCursor;mxEvent.addGestureListeners(p,mxUtils.bind(this,function(v){null==this.currentState||this.isResetEvent(v)||(this.mouseDownPoint=mxUtils.convertPoint(this.graph.container,mxEvent.getClientX(v),mxEvent.getClientY(v)),this.drag(v,\nthis.mouseDownPoint.x,this.mouseDownPoint.y),this.activeArrow=p,this.setDisplay(\"none\"),mxEvent.consume(v))}));mxEvent.redirectMouseEvents(p,this.graph,this.currentState);mxEvent.addListener(p,\"mouseenter\",mxUtils.bind(this,function(v){mxEvent.isMouseEvent(v)&&(null!=this.activeArrow&&this.activeArrow!=p&&mxUtils.setOpacity(this.activeArrow,this.inactiveOpacity),this.graph.connectionHandler.constraintHandler.reset(),mxUtils.setOpacity(p,100),this.activeArrow=p,this.fireEvent(new mxEventObject(\"focus\",\n\"arrow\",p,\"direction\",m,\"event\",v)))}));mxEvent.addListener(p,\"mouseleave\",mxUtils.bind(this,function(v){mxEvent.isMouseEvent(v)&&this.fireEvent(new mxEventObject(\"blur\",\"arrow\",p,\"direction\",m,\"event\",v));this.graph.isMouseDown||this.resetActiveArrow()}));return p};HoverIcons.prototype.resetActiveArrow=function(){null!=this.activeArrow&&(mxUtils.setOpacity(this.activeArrow,this.inactiveOpacity),this.activeArrow=null)};\nHoverIcons.prototype.getDirection=function(){var b=mxConstants.DIRECTION_EAST;this.activeArrow==this.arrowUp?b=mxConstants.DIRECTION_NORTH:this.activeArrow==this.arrowDown?b=mxConstants.DIRECTION_SOUTH:this.activeArrow==this.arrowLeft&&(b=mxConstants.DIRECTION_WEST);return b};HoverIcons.prototype.visitNodes=function(b){for(var f=0;fthis.activationDelay)&&this.currentState!=b&&(p>this.updateDelay&&null!=\nb||null==this.bbox||null==f||null==m||!mxUtils.contains(this.bbox,f,m))&&(null!=b&&this.graph.isEnabled()?(this.removeNodes(),this.setCurrentState(b),this.repaint(),this.graph.connectionHandler.constraintHandler.currentFocus!=b&&this.graph.connectionHandler.constraintHandler.reset()):this.reset())}};\nHoverIcons.prototype.setCurrentState=function(b){\"eastwest\"!=b.style.portConstraint&&(null!=this.arrowUp&&this.graph.container.appendChild(this.arrowUp),null!=this.arrowDown&&this.graph.container.appendChild(this.arrowDown));null!=this.arrowRight&&this.graph.container.appendChild(this.arrowRight);null!=this.arrowLeft&&this.graph.container.appendChild(this.arrowLeft);this.currentState=b};\nGraph.prototype.removeTextStyleForCell=function(b,f){var m=this.getCurrentCellStyle(b),p=!1;this.getModel().beginUpdate();try{if(\"1\"==mxUtils.getValue(m,\"html\",\"0\")){var v=this.convertValueToString(b);\"0\"!=mxUtils.getValue(m,\"nl2Br\",\"1\")&&(v=v.replace(/\\n/g,\"\").replace(//g,\"\\n\"));v=Editor.convertHtmlToText(v);this.cellLabelChanged(b,v);p=!0}f&&(this.setCellStyles(\"fontSource\",null,[b]),this.setCellStyles(mxConstants.STYLE_FONTFAMILY,null,[b]),this.setCellStyles(mxConstants.STYLE_FONTSIZE,\nnull,[b]),this.setCellStyles(mxConstants.STYLE_FONTSTYLE,null,[b]),this.setCellStyles(mxConstants.STYLE_FONTCOLOR,null,[b]),this.setCellStyles(mxConstants.STYLE_LABEL_BORDERCOLOR,null,[b]),this.setCellStyles(mxConstants.STYLE_LABEL_BACKGROUNDCOLOR,null,[b]))}finally{this.getModel().endUpdate()}return p};Graph.prototype.createParent=function(b,f,m,p,v){b=this.cloneCell(b);for(var u=0;um.length)){f.beginUpdate();try{for(var p=0,v=0;vp.length)){f.beginUpdate();try{for(var v=0,u=0;uB||Math.abs(pa.y-K.y)>B)&&(Math.abs(pa.x-H.x)>B||Math.abs(pa.y-H.y)>B)&&(null==ia||mxUtils.ptLineDist(K.x,K.y,\nH.x,H.y,ia.x,ia.y)>B||mxUtils.ptLineDist(K.x,K.y,H.x,H.y,la.x,la.y)>B)&&(null==W||mxUtils.ptLineDist(K.x,K.y,H.x,H.y,W.x,W.y)>B||mxUtils.ptLineDist(K.x,K.y,H.x,H.y,ea.x,ea.y)>B)){W=pa.x-K.x;ia=pa.y-K.y;pa={distSq:W*W+ia*ia,x:pa.x,y:pa.y};for(W=0;Wpa.distSq){O.splice(W,0,pa);pa=null;break}null==pa||0!=O.length&&O[O.length-1].x===pa.x&&O[O.length-1].y===pa.y||O.push(pa)}ia=la}}}for(ca=0;cat*t&&0t*t&&(ia=new mxPoint(ha.x-W.x,ha.y-W.y),ca=new mxPoint(ha.x+W.x,ha.y+W.y),O.push(ia),this.addPoints(N,O,g,l,!1,null,E),O=0>Math.round(W.x)||0==Math.round(W.x)&&0>=Math.round(W.y)?1:-1,E=!1,\"sharp\"==B?(N.lineTo(ia.x-W.y*O,ia.y+W.x*O),N.lineTo(ca.x-W.y*O,ca.y+W.x*O),N.lineTo(ca.x,ca.y)):\"line\"==B?(N.moveTo(ia.x+W.y*O,ia.y-W.x*O),N.lineTo(ia.x-W.y*O,ia.y+W.x*O),N.moveTo(ca.x-W.y*O,ca.y+W.x*O),N.lineTo(ca.x+W.y*\nO,ca.y-W.x*O),N.moveTo(ca.x,ca.y)):\"arc\"==B?(O*=1.3,N.curveTo(ia.x-W.y*O,ia.y+W.x*O,ca.x-W.y*O,ca.y+W.x*O,ca.x,ca.y)):(N.moveTo(ca.x,ca.y),E=!0),O=[ca],ia=!0))}else W=null;ia||(O.push(ha),H=ha)}this.addPoints(N,O,g,l,!1,null,E);N.stroke()}};var I=mxGraphView.prototype.getFixedTerminalPoint;mxGraphView.prototype.getFixedTerminalPoint=function(N,c,g,l){return null!=c&&\"centerPerimeter\"==c.style[mxConstants.STYLE_PERIMETER]?new mxPoint(c.getCenterX(),c.getCenterY()):I.apply(this,arguments)};var V=mxGraphView.prototype.updateFloatingTerminalPoint;\nmxGraphView.prototype.updateFloatingTerminalPoint=function(N,c,g,l){if(null==c||null==N||\"1\"!=c.style.snapToPoint&&\"1\"!=N.style.snapToPoint)V.apply(this,arguments);else{c=this.getTerminalPort(N,c,l);var t=this.getNextPoint(N,g,l),B=this.graph.isOrthogonal(N),E=mxUtils.toRadians(Number(c.style[mxConstants.STYLE_ROTATION]||\"0\")),H=new mxPoint(c.getCenterX(),c.getCenterY());if(0!=E){var K=Math.cos(-E),O=Math.sin(-E);t=mxUtils.getRotatedPoint(t,K,O,H)}K=parseFloat(N.style[mxConstants.STYLE_PERIMETER_SPACING]||\n0);K+=parseFloat(N.style[l?mxConstants.STYLE_SOURCE_PERIMETER_SPACING:mxConstants.STYLE_TARGET_PERIMETER_SPACING]||0);t=this.getPerimeterPoint(c,t,0==E&&B,K);0!=E&&(K=Math.cos(E),O=Math.sin(E),t=mxUtils.getRotatedPoint(t,K,O,H));N.setAbsoluteTerminalPoint(this.snapToAnchorPoint(N,c,g,l,t),l)}};mxGraphView.prototype.snapToAnchorPoint=function(N,c,g,l,t){if(null!=c&&null!=N){N=this.graph.getAllConnectionConstraints(c);l=g=null;if(null!=N)for(var B=0;B=u.getStatus()&&eval.call(window,u.getText())}}catch(D){null!=window.console&&console.log(\"error in getStencil:\",b,m,f,v,D)}}mxStencilRegistry.packages[m]=1}}else m=m.replace(\"_-_\",\"_\"),mxStencilRegistry.loadStencilSet(STENCIL_PATH+\"/\"+m+\".xml\",null);f=mxStencilRegistry.stencils[b]}}return f};\nmxStencilRegistry.getBasenameForStencil=function(b){var f=null;if(null!=b&&\"string\"===typeof b&&(b=b.split(\".\"),0=m.getStatus()?m.getXml():null)}),mxUtils.bind(this,function(m){f(null)}));else return mxUtils.load(b).getXml()};mxStencilRegistry.parseStencilSets=function(b){for(var f=0;f').src;mxWindow.prototype.minimizeImage=Graph.createSvgImage(14,10,'').src;mxWindow.prototype.normalizeImage=Graph.createSvgImage(14,10,'').src;mxWindow.prototype.resizeImage=Graph.createSvgImage(10,10,'').src;\nmxEdgeHandler.prototype.snapToTerminals=!0;mxGraphHandler.prototype.guidesEnabled=!0;mxGraphHandler.prototype.removeEmptyParents=!0;mxRubberband.prototype.fadeOut=!0;mxGuide.prototype.isEnabledForEvent=function(A){return!mxEvent.isAltDown(A)||mxEvent.isShiftDown(A)};var m=mxGraphLayout.prototype.isVertexIgnored;mxGraphLayout.prototype.isVertexIgnored=function(A){return m.apply(this,arguments)||this.graph.isTableRow(A)||this.graph.isTableCell(A)};var p=mxGraphLayout.prototype.isEdgeIgnored;mxGraphLayout.prototype.isEdgeIgnored=\nfunction(A){return p.apply(this,arguments)||this.graph.isEdgeIgnored(A)};var v=mxConnectionHandler.prototype.isCreateTarget;mxConnectionHandler.prototype.isCreateTarget=function(A){return this.graph.isCloneEvent(A)!=v.apply(this,arguments)};mxConstraintHandler.prototype.createHighlightShape=function(){var A=new mxEllipse(null,this.highlightColor,this.highlightColor,0);A.opacity=mxConstants.HIGHLIGHT_OPACITY;return A};mxConnectionHandler.prototype.livePreview=!0;mxConnectionHandler.prototype.cursor=\n\"crosshair\";mxConnectionHandler.prototype.createEdgeState=function(A){A=this.graph.createCurrentEdgeStyle();A=this.graph.createEdge(null,null,null,null,null,A);A=new mxCellState(this.graph.view,A,this.graph.getCellStyle(A));for(var R in this.graph.currentEdgeStyle)A.style[R]=this.graph.currentEdgeStyle[R];if(null!=this.previous){var U=this.previous.style.newEdgeStyle;if(null!=U)try{var X=JSON.parse(U);for(R in X)A.style[R]=X[R]}catch(da){}}A.style=this.graph.postProcessCellStyle(A.cell,A.style);return A};\nvar u=mxConnectionHandler.prototype.createShape;mxConnectionHandler.prototype.createShape=function(){var A=u.apply(this,arguments);A.isDashed=\"1\"==this.graph.currentEdgeStyle[mxConstants.STYLE_DASHED];return A};mxConnectionHandler.prototype.updatePreview=function(A){};var D=mxConnectionHandler.prototype.createMarker;mxConnectionHandler.prototype.createMarker=function(){var A=D.apply(this,arguments),R=A.getCell;A.getCell=mxUtils.bind(this,function(U){var X=R.apply(this,arguments);this.error=null;return X});\nreturn A};Graph.prototype.defaultVertexStyle={};Graph.prototype.defaultEdgeStyle={edgeStyle:\"orthogonalEdgeStyle\",rounded:\"0\",jettySize:\"auto\",orthogonalLoop:\"1\"};Graph.prototype.createCurrentEdgeStyle=function(){for(var A=\"edgeStyle=\"+(this.currentEdgeStyle.edgeStyle||\"none\")+\";\",R=\"shape curved rounded comic sketch fillWeight hachureGap hachureAngle jiggle disableMultiStroke disableMultiStrokeFill fillStyle curveFitting simplification comicStyle jumpStyle jumpSize\".split(\" \"),U=0;U=va.x&&this.model.remove(nb[cb]);var ob=this.model.getTerminal(U,!1);if(null!=ob){var lb=this.getCurrentCellStyle(ob);null!=lb&&\"1\"==lb.snapToPoint&&(this.setCellStyles(mxConstants.STYLE_EXIT_X,null,[A]),this.setCellStyles(mxConstants.STYLE_EXIT_Y,null,[A]),this.setCellStyles(mxConstants.STYLE_ENTRY_X,\nnull,[U]),this.setCellStyles(mxConstants.STYLE_ENTRY_Y,null,[U]))}}finally{this.model.endUpdate()}return U};var c=Graph.prototype.selectCell;Graph.prototype.selectCell=function(A,R,U){if(R||U)c.apply(this,arguments);else{var X=this.getSelectionCell(),da=null,ma=[],qa=mxUtils.bind(this,function(wa){if(null!=this.view.getState(wa)&&(this.model.isVertex(wa)||this.model.isEdge(wa)))if(ma.push(wa),wa==X)da=ma.length-1;else if(A&&null==X&&0da||!A&&0ob)for(lb=0;lb>ob;lb--)this.model.remove(mb[mb.length+lb-1]);mb=this.model.getChildCells(A[va],!0);for(lb=0;lbmxUtils.indexOf(A,ma)&&0>mxUtils.indexOf(U,ma)&&U.push(ma):this.labelChanged(A[X],\"\")}else{if(this.isTableRow(A[X])&&(ma=this.model.getParent(A[X]),0>mxUtils.indexOf(A,ma)&&0>mxUtils.indexOf(U,ma))){for(var qa=this.model.getChildCells(ma,!0),wa=0,va=0;va=da||0>=R?null:{availW:da,availH:R}};Graph.prototype.computeAutosizeTextFontSize=function(A,R,U,X,da,ma){var qa=1,wa=84,va=ma?R:null,xa=null;if(ma){for(var Ra=A.replace(//gi,\"\\n\").replace(/<[^>]*>/g,\n\"\").split(/[\\s\\n]+/),Wa=[],cb=0;cb\"))}for(;qaR&&(Wa=!1),Wa?qa=Ra:wa=Ra-1;return Math.max(6,qa)};Graph.prototype.updateAutosizeTextFontSize=function(A,R){var U=this.view.getState(A);null==U&&(U=this.view.createState(A));\nR=R||U.style;var X=this.getCellGeometry(A);if(null!=X&&!this.model.isEdge(A)){var da=this.cellRenderer.getLabelValue(U);null!=da&&0!=da.length&&(U=this.getAutosizeTextAvailableSpace(R,X.width,X.height,U),null!=U&&(this.isHtmlLabel(A)||(da=mxUtils.htmlEntities(da,!1)),da=da.replace(/\\n/g,\"
\"),R=this.computeAutosizeTextFontSize(da,U.availW,U.availH,R[mxConstants.STYLE_FONTFAMILY]||mxConstants.DEFAULT_FONTFAMILY,R[mxConstants.STYLE_FONTSTYLE],\"wrap\"==R[mxConstants.STYLE_WHITE_SPACE]),this.setCellStyles(mxConstants.STYLE_FONTSIZE,\nR,[A])))}};Graph.prototype.cellsResized=function(A,R,U){var X=mxGraph.prototype.cellsResized.apply(this,arguments);if(null!=A)for(var da=0;damxUtils.indexOf(A,ma))return null}ma=B.apply(this,arguments);var qa=!0;for(da=0;dama||xa>ma)&&this.clear());else{for(va=xa.getSource();null!=va&&\"a\"!=va.nodeName.toLowerCase();)va=va.parentNode;null!=va?this.clear():(null!=qa.tooltipHandler&&null!=this.currentLink&&null!=this.currentState&&qa.tooltipHandler.reset(xa,!0,this.currentState),(null==this.currentState||xa.getState()!=this.currentState&&null!=xa.sourceState||!qa.intersects(this.currentState,xa.getGraphX(),xa.getGraphY()))&&\nthis.updateCurrentState(xa))}},mouseUp:function(va,xa){var Ra=xa.getSource();for(va=xa.getEvent();null!=Ra&&\"a\"!=Ra.nodeName.toLowerCase();)Ra=Ra.parentNode;null==Ra&&Math.abs(this.scrollLeft-qa.container.scrollLeft)x&&d++;n++}Fb.length=ma.length)R.remove(U);else{var qa=ma.length-1;this.isTableCell(A)&&(qa=mxUtils.indexOf(ma,A));for(X=A=0;X=da.length)R.remove(U);else{this.isTableRow(X)||(X=da[da.length-1]);R.remove(X);A=0;var ma=this.getCellGeometry(X);null!=ma&&(A=ma.height);var qa=this.getCellGeometry(U);null!=qa&&(qa=qa.clone(),qa.height-=A,R.setGeometry(U,qa))}}finally{R.endUpdate()}};Graph.prototype.insertRow=function(A,R){for(var U=A.tBodies[0],X=U.rows[0].cells,da=\nA=0;daR&&A[U].deleteCell(R)}};Graph.prototype.pasteHtmlAtCaret=function(A){if(window.getSelection){var R=window.getSelection();if(R.getRangeAt&&R.rangeCount){R=R.getRangeAt(0);R.deleteContents();var U=document.createElement(\"div\");U.innerHTML=A;A=document.createDocumentFragment();for(var X;X=U.firstChild;)lastNode=A.appendChild(X);R.insertNode(A)}}else(R=document.selection)&&\n\"Control\"!=R.type&&R.createRange().pasteHTML(A)};Graph.prototype.createLinkForHint=function(A,R,U){function X(ma,qa){ma.length>qa&&(ma=ma.substring(0,Math.round(qa/2))+\"...\"+ma.substring(ma.length-Math.round(qa/4)));return ma}A=null!=A?A:\"javascript:void(0);\";if(null==R||0==R.length)R=this.isCustomLink(A)?this.getLinkTitle(A):A;var da=document.createElement(\"a\");da.setAttribute(\"rel\",this.linkRelation);da.setAttribute(\"href\",this.getAbsoluteUrl(A));da.setAttribute(\"title\",X(this.isCustomLink(A)?this.getLinkTitle(A):\nA,80));null!=this.linkTarget&&da.setAttribute(\"target\",this.linkTarget);mxUtils.write(da,X(R,40));this.isCustomLink(A)&&mxEvent.addListener(da,\"click\",mxUtils.bind(this,function(ma){this.customLinkClicked(A,U);mxEvent.consume(ma)}));return da};Graph.prototype.initTouch=function(){this.connectionHandler.marker.isEnabled=function(){return null!=this.graph.connectionHandler.first};this.addListener(mxEvent.START_EDITING,function(ma,qa){this.popupMenuHandler.hideMenu()});var A=this.updateMouseEvent;this.updateMouseEvent=\nfunction(ma){ma=A.apply(this,arguments);if(mxEvent.isTouchEvent(ma.getEvent())&&null==ma.getState()){var qa=this.getCellAt(ma.graphX,ma.graphY);null!=qa&&this.isSwimlane(qa)&&this.hitsSwimlaneContent(qa,ma.graphX,ma.graphY)||(ma.state=this.view.getState(qa),null!=ma.state&&null!=ma.state.shape&&(this.container.style.cursor=ma.state.shape.node.style.cursor))}null==ma.getState()&&this.isEnabled()&&(this.container.style.cursor=\"default\");return ma};var R=!1,U=!1,X=!1,da=this.fireMouseEvent;this.fireMouseEvent=\nfunction(ma,qa,wa){ma==mxEvent.MOUSE_DOWN&&(qa=this.updateMouseEvent(qa),R=this.isCellSelected(qa.getCell()),U=this.isSelectionEmpty(),X=this.popupMenuHandler.isMenuShowing());da.apply(this,arguments)};this.popupMenuHandler.mouseUp=mxUtils.bind(this,function(ma,qa){if(null!=this.freehand&&!this.freehand.isDrawing()){var wa=mxEvent.isMouseEvent(qa.getEvent());this.popupMenuHandler.popupTrigger=!this.isEditing()&&this.isEnabled()&&(null==qa.getState()||!qa.isSource(qa.getState().control))&&(this.popupMenuHandler.popupTrigger||\n!X&&!wa&&(U&&null==qa.getCell()&&this.isSelectionEmpty()||R&&this.isCellSelected(qa.getCell())));wa=!R||wa?null:mxUtils.bind(this,function(va){window.setTimeout(mxUtils.bind(this,function(){if(!this.isEditing()&&this.isTouchPopupMenuEnabled()){var xa=mxUtils.getScrollOrigin();this.popupMenuHandler.popup(qa.getX()+xa.x+1,qa.getY()+xa.y+1,va,qa.getEvent())}}),300)});mxPopupMenuHandler.prototype.mouseUp.apply(this.popupMenuHandler,[ma,qa,wa])}})};Graph.prototype.isTouchPopupMenuEnabled=function(){return!0};\nmxCellEditor.prototype.isContentEditing=function(){var A=this.graph.view.getState(this.editingCell);return null!=A&&1==A.style.html};mxCellEditor.prototype.isTableSelected=function(){return null!=this.graph.getParentByName(this.graph.getSelectedElement(),\"TABLE\",this.textarea)};mxCellEditor.prototype.isTextSelected=function(){var A=\"\";window.getSelection?A=window.getSelection():document.getSelection?A=document.getSelection():document.selection&&(A=document.selection.createRange().text);return\"\"!=\nA};mxCellEditor.prototype.insertTab=function(A){var R=this.textarea.ownerDocument.defaultView.getSelection(),U=R.getRangeAt(0);A=Graph.createTabNode(A);U.insertNode(A);U.setStartAfter(A);U.setEndAfter(A);R.removeAllRanges();R.addRange(U)};mxCellEditor.prototype.alignText=function(A,R){var U=this.graph.getView().getState(this.editingCell);if(null!=U){U=mxUtils.getValue(U.style,mxConstants.STYLE_TEXT_DIRECTION,mxConstants.DEFAULT_TEXT_DIRECTION);var X=null!=U&&\"vertical-\"==U.substring(0,9),da=null!=\nR&&mxEvent.isShiftDown(R);if(da||null!=window.getSelection&&null!=window.getSelection().containsNode){var ma=!0;this.graph.processElements(this.textarea,function(qa){da||X||window.getSelection().containsNode(qa,!0)?(qa.removeAttribute(\"align\"),qa.style.textAlign=null):ma=!1});(ma||X)&&this.graph.cellEditor.setAlign(A)}X||document.execCommand(\"justify\"+A.toLowerCase(),!1,null)}};mxCellEditor.prototype.saveSelection=function(){if(window.getSelection){var A=window.getSelection();if(A.getRangeAt&&A.rangeCount){for(var R=\n[],U=0,X=A.rangeCount;U\"):wa,!0);this.textarea.className=\"mxCellEditor geContentEditable\";va=mxUtils.getValue(A.style,mxConstants.STYLE_FONTSIZE,mxConstants.DEFAULT_FONTSIZE);R=mxUtils.getValue(A.style,mxConstants.STYLE_FONTFAMILY,mxConstants.DEFAULT_FONTFAMILY);var X=mxUtils.getValue(A.style,mxConstants.STYLE_ALIGN,mxConstants.ALIGN_LEFT),da=(mxUtils.getValue(A.style,mxConstants.STYLE_FONTSTYLE,0)&\nmxConstants.FONT_BOLD)==mxConstants.FONT_BOLD,ma=(mxUtils.getValue(A.style,mxConstants.STYLE_FONTSTYLE,0)&mxConstants.FONT_ITALIC)==mxConstants.FONT_ITALIC,qa=[];(mxUtils.getValue(A.style,mxConstants.STYLE_FONTSTYLE,0)&mxConstants.FONT_UNDERLINE)==mxConstants.FONT_UNDERLINE&&qa.push(\"underline\");(mxUtils.getValue(A.style,mxConstants.STYLE_FONTSTYLE,0)&mxConstants.FONT_STRIKETHROUGH)==mxConstants.FONT_STRIKETHROUGH&&qa.push(\"line-through\");this.textarea.style.lineHeight=mxConstants.ABSOLUTE_LINE_HEIGHT?\nMath.round(va*mxConstants.LINE_HEIGHT)+\"px\":mxConstants.LINE_HEIGHT;this.textarea.style.fontSize=Math.round(va)+\"px\";this.textarea.style.textDecoration=qa.join(\" \");this.textarea.style.fontWeight=da?\"bold\":\"normal\";this.textarea.style.fontStyle=ma?\"italic\":\"\";this.textarea.style.fontFamily=R;this.textarea.style.textAlign=X;this.textarea.style.padding=\"0px\";this.textarea.innerHTML!=wa&&(this.textarea.innerHTML=wa,0==this.textarea.innerHTML.length&&(this.textarea.innerHTML=this.getEmptyLabelText(),\nthis.clearOnChange=0
\"));\nwa=Graph.sanitizeHtml(R?wa.replace(/\\n/g,\"\").replace(/<br\\s*.?>/g,\"
\"):wa,!0);this.textarea.className=\"mxCellEditor mxPlainTextEditor\";var va=mxConstants.DEFAULT_FONTSIZE;this.textarea.style.lineHeight=mxConstants.ABSOLUTE_LINE_HEIGHT?Math.round(va*mxConstants.LINE_HEIGHT)+\"px\":mxConstants.LINE_HEIGHT;this.textarea.style.fontSize=Math.round(va)+\"px\";this.textarea.style.textDecoration=\"\";this.textarea.style.fontWeight=\"normal\";this.textarea.style.fontStyle=\"\";this.textarea.style.fontFamily=\nmxConstants.DEFAULT_FONTFAMILY;this.textarea.style.textAlign=\"left\";this.textarea.style.width=\"\";this.textarea.style.padding=\"2px\";this.textarea.innerHTML!=wa&&(this.textarea.innerHTML=wa);this.codeViewMode=!0}this.textarea.focus();null!=this.switchSelectionState&&this.restoreSelection(this.switchSelectionState);this.switchSelectionState=U;this.resize()}};var O=mxCellEditor.prototype.resize;mxCellEditor.prototype.resize=function(A,R){if(null!=this.textarea)if(A=this.graph.getView().getState(this.editingCell),\nthis.codeViewMode&&null!=A){var U=A.view.scale;this.bounds=mxRectangle.fromRectangle(A);if(0==this.bounds.width&&0==this.bounds.height){this.bounds.width=160*U;this.bounds.height=60*U;var X=null!=A.text?A.text.margin:null;null==X&&(X=mxUtils.getAlignmentAsPoint(mxUtils.getValue(A.style,mxConstants.STYLE_ALIGN,mxConstants.ALIGN_CENTER),mxUtils.getValue(A.style,mxConstants.STYLE_VERTICAL_ALIGN,mxConstants.ALIGN_MIDDLE)));this.bounds.x+=X.x*this.bounds.width;this.bounds.y+=X.y*this.bounds.height}this.textarea.style.width=\nMath.round((this.bounds.width-4)/U)+\"px\";this.textarea.style.height=Math.round((this.bounds.height-4)/U)+\"px\";this.textarea.style.overflow=\"auto\";this.textarea.clientHeight\"),X=this.graph.computeAutosizeTextFontSize(U,da.availW,da.availH,X[mxConstants.STYLE_FONTFAMILY]||mxConstants.DEFAULT_FONTFAMILY,X[mxConstants.STYLE_FONTSTYLE],\"wrap\"==X[mxConstants.STYLE_WHITE_SPACE]),this.textarea.style.fontSize=X+\"px\",this.textarea.style.lineHeight=mxConstants.ABSOLUTE_LINE_HEIGHT?\nX*mxConstants.LINE_HEIGHT+\"px\":mxConstants.LINE_HEIGHT))}}else this.textarea.style.width=\"\",this.textarea.style.height=\"\",this.textarea.style.overflow=\"\",this.textarea.style.boxSizing=\"\",this.textarea.style.borderWidth=\"\",this.textarea.style.paddingTop=\"\",this.textarea.style.paddingBottom=\"\",this.textarea.style.paddingLeft=\"\",this.textarea.style.paddingRight=\"\",this.textarea.style.display=\"\",this.textarea.style.flexDirection=\"\",this.textarea.style.justifyContent=\"\",this.textarea.style.verticalAlign=\n\"\",O.apply(this,arguments)};mxCellEditorGetInitialValue=mxCellEditor.prototype.getInitialValue;mxCellEditor.prototype.getInitialValue=function(A,R){if(\"0\"==mxUtils.getValue(A.style,\"html\",\"0\"))return mxCellEditorGetInitialValue.apply(this,arguments);var U=this.graph.getEditingValue(A.cell,R);\"1\"==mxUtils.getValue(A.style,\"nl2Br\",\"1\")&&(U=U.replace(/\\n/g,\"
\"));return Graph.sanitizeHtml(U,!0)};mxCellEditorSetEditingValue=mxCellEditor.prototype.setEditingValue;mxCellEditor.prototype.setEditingValue=\nfunction(A,R){mxCellEditorSetEditingValue.apply(this,arguments);\"1\"==mxUtils.getValue(A.style,\"html\",\"0\")&&Graph.addLightDarkColors(this.textarea,Graph.backupStyleAttribute,\"simple\"==this.graph.getAdaptiveColors())};mxCellEditorGetCurrentValue=mxCellEditor.prototype.getCurrentValue;mxCellEditor.prototype.getCurrentValue=function(A){if(\"0\"==mxUtils.getValue(A.style,\"html\",\"0\"))return mxCellEditorGetCurrentValue.apply(this,arguments);Graph.removeLightDarkColors(this.textarea,Graph.backupStyleAttribute);\nvar R=Graph.sanitizeHtml(this.textarea.innerHTML,!0);Editor.optimizeHtmlLabels&&(R=Graph.optimizeHtml(R));\"1\"==mxUtils.getValue(A.style,\"nl2Br\",\"1\")?(R=R.replace(/\\r\\n/g,\"
\").replace(/\\n/g,\"
\"),0\"==R.substring(R.length-5)||\"
\"==R.substring(R.length-4))&&(R=R.substring(0,R.lastIndexOf(\"
\")):R=R.replace(/\\r\\n/g,\"\").replace(/\\n/g,\"\");return R};var W=mxCellEditor.prototype.stopEditing;mxCellEditor.prototype.stopEditing=function(A){this.codeViewMode&&\nthis.toggleViewMode();W.apply(this,arguments);this.focusContainer()};mxCellEditor.prototype.focusContainer=function(){try{this.graph.container.focus()}catch(A){}};var L=mxCellEditor.prototype.applyValue;mxCellEditor.prototype.applyValue=function(A,R){this.graph.getModel().beginUpdate();try{L.apply(this,arguments),\"\"==R&&this.graph.isCellDeletable(A.cell)&&0==this.graph.model.getChildCount(A.cell)&&this.graph.isTransparentState(A)&&this.graph.removeCells([A.cell],!1)}finally{this.graph.getModel().endUpdate()}};\nmxCellEditor.prototype.getBackgroundColor=function(A){A=mxUtils.getValue(A.style,mxConstants.STYLE_LABEL_BACKGROUNDCOLOR,null);A==mxConstants.NONE&&(A=null);return A};mxCellEditor.prototype.getBorderColor=function(A){A=mxUtils.getValue(A.style,mxConstants.STYLE_LABEL_BORDERCOLOR,null);A==mxConstants.NONE&&(A=null);return A};mxCellEditor.prototype.getMinimumSize=function(A){var R=this.graph.getView().scale;return new mxRectangle(0,0,null==A.text?30:A.text.size*R+20,30)};mxGraphHandlerIsValidDropTarget=\nmxGraphHandler.prototype.isValidDropTarget;mxGraphHandler.prototype.isValidDropTarget=function(A,R){return mxGraphHandlerIsValidDropTarget.apply(this,arguments)&&!mxEvent.isAltDown(R.getEvent)};var ca=mxGraphHandler.prototype.getInitialCellForEvent;mxGraphHandler.prototype.getInitialCellForEvent=function(A){var R=ca.apply(this,arguments);if(null!=R){var U=this.graph.getLockedGroupAncestor(this.graph.model.getParent(R));null!=U&&(R=U)}return R};mxGraphView.prototype.formatUnitText=function(A){return A?\nf(A,this.unit):A};mxGraphHandler.prototype.updateHint=function(A){if(null!=this.pBounds&&(null!=this.shape||this.livePreviewActive)){null==this.hint&&(this.hint=b(),this.graph.container.appendChild(this.hint));var R=this.graph.view.translate,U=this.graph.view.scale;A=this.roundLength((this.bounds.x+this.currentDx)/U-R.x);R=this.roundLength((this.bounds.y+this.currentDy)/U-R.y);U=this.graph.view.unit;this.hint.innerHTML=f(A,U)+\", \"+f(R,U);this.hint.style.left=this.pBounds.x+this.currentDx+Math.round((this.pBounds.width-\nthis.hint.clientWidth)/2)+\"px\";this.hint.style.top=this.pBounds.y+this.currentDy+this.pBounds.height+Editor.hintOffset+\"px\"}};mxGraphHandler.prototype.removeHint=function(){null!=this.hint&&(null!=this.hint.parentNode&&this.hint.parentNode.removeChild(this.hint),this.hint=null)};var ha=mxStackLayout.prototype.resizeCell;mxStackLayout.prototype.resizeCell=function(A,R){ha.apply(this,arguments);var U=this.graph.getCellStyle(A);if(null==U.childLayout){var X=this.graph.model.getParent(A),da=null!=X?this.graph.getCellGeometry(X):\nnull;if(null!=da&&(U=this.graph.getCellStyle(X),\"stackLayout\"==U.childLayout)){var ma=parseFloat(mxUtils.getValue(U,\"stackBorder\",mxStackLayout.prototype.border));U=\"1\"==mxUtils.getValue(U,\"horizontalStack\",\"1\");var qa=this.graph.getActualStartSize(X);da=da.clone();U?da.height=R.height+qa.y+qa.height+2*ma:da.width=R.width+qa.x+qa.width+2*ma;this.graph.model.setGeometry(X,da)}}};var ia=mxSelectionCellsHandler.prototype.getHandledSelectionCells;mxSelectionCellsHandler.prototype.getHandledSelectionCells=\nfunction(){function A(wa){U.get(wa)||(U.put(wa,!0),da.push(wa))}for(var R=ia.apply(this,arguments),U=new mxDictionary,X=this.graph.model,da=[],ma=0;maA;A++){var R=new mxRectangleShape(new mxRectangle(0,0,6,6),\"#ffffff\",mxConstants.HANDLE_STROKECOLOR);R.dialect=mxConstants.DIALECT_SVG;R.init(this.graph.view.getOverlayPane());this.cornerHandles.push(R)}}this.graph.isTable(this.state.cell)&&this.graph.isCellMovable(this.state.cell)&&this.refreshMoveHandles();\nA=this.graph.getLinkForCell(this.state.cell);R=this.graph.getLinksForState(this.state);this.updateLinkHint(A,R)};var Y=mxVertexHandler.prototype.getHandlePadding;mxVertexHandler.prototype.getHandlePadding=function(){var A=new mxPoint(0,0),R=this.tolerance,U=this.state.style.shape;null==mxCellRenderer.defaultShapes[U]&&mxStencilRegistry.getStencil(U);U=this.graph.isTable(this.state.cell)||this.graph.cellEditor.getEditingCell()==this.state.cell;if(!U&&null!=this.customHandles)for(var X=0;X',\n9,9);Graph.prototype.collapsedImage=Graph.createSvgImage(A,A,'',9,9)};Graph.updateFoldingImages(9);mxEdgeHandler.prototype.removeHint=mxVertexHandler.prototype.removeHint;\nHoverIcons.prototype.mainHandle=Graph.createSvgImage(18,18,'');HoverIcons.prototype.endMainHandle=Graph.createSvgImage(18,18,'');HoverIcons.prototype.secondaryHandle=Graph.createSvgImage(16,16,'');HoverIcons.prototype.fixedHandle=Graph.createSvgImage(22,22,'');\nHoverIcons.prototype.endFixedHandle=Graph.createSvgImage(22,22,'');HoverIcons.prototype.terminalHandle=Graph.createSvgImage(22,22,'');HoverIcons.prototype.endTerminalHandle=Graph.createSvgImage(22,22,'');HoverIcons.prototype.rotationHandle=Graph.createSvgImage(16,16,'',\n24,24);HoverIcons.prototype.connectHandle=Graph.createSvgImage(16,16,'',24,24);HoverIcons.prototype.lockHandle=Graph.createSvgImage(16,16,'',\n24,24);HoverIcons.prototype.unlockHandle=Graph.createSvgImage(16,16,'',24,24);HoverIcons.prototype.editHandle=\nGraph.createSvgImage(16,16,'',24,24);mxConstraintHandler.prototype.pointImage=Graph.createSvgImage(5,5,'');mxVertexHandler.TABLE_HANDLE_COLOR=\"#fca000\";mxVertexHandler.prototype.handleImage=HoverIcons.prototype.mainHandle;mxVertexHandler.prototype.secondaryHandleImage=HoverIcons.prototype.secondaryHandle;mxVertexHandler.prototype.rowHandleImage=Graph.createSvgImage(14,12,'');mxEdgeHandler.prototype.handleImage=HoverIcons.prototype.mainHandle;mxEdgeHandler.prototype.endHandleImage=HoverIcons.prototype.endMainHandle;mxEdgeHandler.prototype.terminalHandleImage=HoverIcons.prototype.terminalHandle;mxEdgeHandler.prototype.endTerminalHandleImage=HoverIcons.prototype.endTerminalHandle;mxEdgeHandler.prototype.fixedHandleImage=HoverIcons.prototype.fixedHandle;mxEdgeHandler.prototype.endFixedHandleImage=HoverIcons.prototype.endFixedHandle;mxEdgeHandler.prototype.labelHandleImage=\nHoverIcons.prototype.secondaryHandle;mxOutline.prototype.sizerImage=HoverIcons.prototype.mainHandle;null!=window.Sidebar&&(Sidebar.prototype.triangleUp=HoverIcons.prototype.triangleUp,Sidebar.prototype.triangleRight=HoverIcons.prototype.triangleRight,Sidebar.prototype.triangleDown=HoverIcons.prototype.triangleDown,Sidebar.prototype.triangleLeft=HoverIcons.prototype.triangleLeft,Sidebar.prototype.refreshTarget=HoverIcons.prototype.refreshTarget,Sidebar.prototype.roundDrop=HoverIcons.prototype.roundDrop);\nmxVertexHandler.prototype.rotationEnabled=!0;mxVertexHandler.prototype.manageSizers=!0;mxVertexHandler.prototype.livePreview=!0;mxGraphHandler.prototype.maxLivePreview=32;mxGraphHandler.prototype.previewColor=\"light-dark(#000000, #cccccc)\";mxRubberband.prototype.defaultOpacity=30;mxConnectionHandler.prototype.outlineConnect=!0;mxCellHighlight.prototype.keepOnTop=!0;mxVertexHandler.prototype.parentHighlightEnabled=!0;mxEdgeHandler.prototype.parentHighlightEnabled=!0;mxEdgeHandler.prototype.dblClickRemoveEnabled=\n!0;mxEdgeHandler.prototype.straightRemoveEnabled=!0;mxEdgeHandler.prototype.virtualBendsEnabled=!0;mxEdgeHandler.prototype.mergeRemoveEnabled=!0;mxEdgeHandler.prototype.manageLabelHandle=!0;mxEdgeHandler.prototype.outlineConnect=!0;mxEdgeHandler.prototype.isAddVirtualBendEvent=function(A){return!mxEvent.isShiftDown(A.getEvent())};mxEdgeHandler.prototype.isCustomHandleEvent=function(A){return!mxEvent.isShiftDown(A.getEvent())};if(Graph.touchStyle){if(mxClient.IS_TOUCH||0ma||Math.abs(da)>ma)null==this.div&&(this.div=this.createShape()),mxUtils.clearSelection(),this.update(A,U),this.isSpaceEvent(R)?(A=this.x+this.width,U=this.y+this.height,X=this.graph.view.scale,mxEvent.isAltDown(R.getEvent())||(this.width=this.graph.snap(this.width/X)*X,this.height=this.graph.snap(this.height/X)*X,this.graph.isGridEnabled()||(this.width=this.width?\"0px 1px 0px 0px\":\"0px 1px 0px 1px\",null==this.secondDiv&&(this.secondDiv=\nthis.div.cloneNode(!0),this.div.parentNode.appendChild(this.secondDiv)),this.secondDiv.style.left=this.x+\"px\",this.secondDiv.style.top=this.y+\"px\",this.secondDiv.style.width=this.graph.container.clientWidth+\"px\",this.secondDiv.style.height=Math.max(0,this.height)+\"px\",this.secondDiv.style.borderWidth=0>=this.height?\"1px 0px 0px 0px\":\"1px 0px 1px 0px\"):(this.div.style.backgroundColor=\"\",this.div.style.borderWidth=\"\",this.div.style.borderStyle=\"\",null!=this.secondDiv&&(this.secondDiv.parentNode.removeChild(this.secondDiv),\nthis.secondDiv=null)),R.consume()}};var ra=mxRubberband.prototype.reset;mxRubberband.prototype.reset=function(){null!=this.secondDiv&&(this.secondDiv.parentNode.removeChild(this.secondDiv),this.secondDiv=null);ra.apply(this,arguments)};var ta=(new Date).getTime(),za=0,Ia=mxEdgeHandler.prototype.updatePreviewState;mxEdgeHandler.prototype.updatePreviewState=function(A,R,U,X){Ia.apply(this,arguments);U!=this.currentTerminalState?(ta=(new Date).getTime(),za=0):za=(new Date).getTime()-ta;this.currentTerminalState=\nU};var ya=mxEdgeHandler.prototype.isOutlineConnectEvent;mxEdgeHandler.prototype.isOutlineConnectEvent=function(A){return mxEvent.isShiftDown(A.getEvent())&&mxEvent.isAltDown(A.getEvent())?!1:null!=this.currentTerminalState&&A.getState()==this.currentTerminalState&&2E3=this.state.absolutePoints.length-1||this.constructor==mxElbowEdgeHandler&&2==A)?this.graph.getConnectionConstraint(this.state,X,R):null;U=null!=(null!=A?this.graph.getConnectionPoint(this.state.getVisibleTerminalState(R),A):null)?U?this.endFixedHandleImage:this.fixedHandleImage:null!=A&&null!=X?U?this.endTerminalHandleImage:this.terminalHandleImage:U?this.endHandleImage:this.handleImage;if(null!=U)return U=new mxImageShape(new mxRectangle(0,0,\nU.width,U.height),U.src),U.preserveImageAspect=!1,U;U=mxConstants.HANDLE_SIZE;this.preferHtml&&--U;return new mxRectangleShape(new mxRectangle(0,0,U,U),mxConstants.HANDLE_FILLCOLOR,mxConstants.HANDLE_STROKECOLOR)};var Da=mxVertexHandler.prototype.createSizerShape;mxVertexHandler.prototype.createSizerShape=function(A,R,U,X){X=R==mxEvent.ROTATION_HANDLE?HoverIcons.prototype.rotationHandle:R==mxEvent.CONNECT_HANDLE?HoverIcons.prototype.connectHandle:R==mxEvent.LABEL_HANDLE?this.secondaryHandleImage:\nX;return Da.apply(this,arguments)};var Va=mxVertexHandler.prototype.createSizers;mxVertexHandler.prototype.createSizers=function(){var A=Va.apply(this,arguments);null==this.connectShape&&this.isConnectHandleVisible()&&(this.connectShape=this.createSizer(\"copy\",mxEvent.CONNECT_HANDLE,mxConstants.HANDLE_SIZE+3,mxConstants.HANDLE_FILLCOLOR),A.push(this.connectShape));return A};var Ea=mxVertexHandler.prototype.destroySizers;mxVertexHandler.prototype.destroySizers=function(){Ea.apply(this,arguments);this.connectShape=\nnull};var Pa=mxVertexHandler.prototype.getHandleForEvent;mxVertexHandler.prototype.getHandleForEvent=function(A){if(null!=this.connectShape&&null!=this.connectShape.node&&\"hidden\"!=this.connectShape.node.style.visibility){var R=mxEvent.isMouseEvent(A.getEvent())?1:this.tolerance;R=this.allowHandleBoundsCheck&&0R.width&&2>R.height&&null!=R.text&&null!=R.text.boundingBox))return mxRectangle.fromRectangle(R.text.boundingBox)}return La.apply(this,\narguments)};var Ba=mxGraphHandler.prototype.getGuideStates;mxGraphHandler.prototype.getGuideStates=function(){for(var A=Ba.apply(this,arguments),R=[],U=0;UA.width&&2>A.height&&null!=\nA.text&&null!=A.text.boundingBox?(R=A.text.unrotatedBoundingBox||A.text.boundingBox,new mxRectangle(Math.round(R.x),Math.round(R.y),Math.round(R.width),Math.round(R.height))):Qa.apply(this,arguments)};var Na=mxVertexHandler.prototype.mouseDown;mxVertexHandler.prototype.mouseDown=function(A,R){var U=this.graph.getModel(),X=U.getParent(this.state.cell),da=this.graph.getCellGeometry(this.state.cell),ma=this.getHandleForEvent(R);(ma==mxEvent.ROTATION_HANDLE||ma==mxEvent.CONNECT_HANDLE||!U.isEdge(X)||\nnull==da||!da.relative||null==this.state||2<=this.state.width||2<=this.state.height)&&Na.apply(this,arguments)};mxVertexHandler.prototype.rotateClick=function(){var A=mxUtils.getValue(this.state.style,mxConstants.STYLE_STROKECOLOR,mxConstants.NONE),R=mxUtils.getValue(this.state.style,mxConstants.STYLE_FILLCOLOR,mxConstants.NONE);this.state.view.graph.model.isVertex(this.state.cell)&&A==mxConstants.NONE&&R==mxConstants.NONE?(A=mxUtils.mod(mxUtils.getValue(this.state.style,mxConstants.STYLE_ROTATION,\n0)+90,360),this.state.view.graph.setCellStyles(mxConstants.STYLE_ROTATION,A,[this.state.cell])):this.state.view.graph.turnShapes([this.state.cell])};var db=mxVertexHandler.prototype.updateLivePreview;mxVertexHandler.prototype.updateLivePreview=function(A){if(null!=this.state&&this.graph.isAutosizeTextState(this.state)){var R=this.graph.view.scale,U=this.state.style,X=this.roundLength(this.bounds.width/R),da=this.roundLength(this.bounds.height/R);R=this.graph.cellRenderer.getLabelValue(this.state);\nif(null!=R&&0\");X=this.graph.computeAutosizeTextFontSize(R,X.availW,X.availH,U[mxConstants.STYLE_FONTFAMILY]||mxConstants.DEFAULT_FONTFAMILY,U[mxConstants.STYLE_FONTSTYLE],\"wrap\"==U[mxConstants.STYLE_WHITE_SPACE]);R=U[mxConstants.STYLE_FONTSIZE];U[mxConstants.STYLE_FONTSIZE]=X;db.apply(this,arguments);this.state.style[mxConstants.STYLE_FONTSIZE]=\nR;return}}db.apply(this,arguments)};var Oa=mxVertexHandler.prototype.mouseMove;mxVertexHandler.prototype.mouseMove=function(A,R){Oa.apply(this,arguments);null!=this.graph.graphHandler.first&&(null!=this.rotationShape&&null!=this.rotationShape.node&&(this.rotationShape.node.style.display=\"none\"),null!=this.connectShape&&null!=this.connectShape.node&&(this.connectShape.node.style.display=\"none\"),null!=this.linkHint&&\"none\"!=this.linkHint.style.display&&(this.linkHint.style.display=\"none\"))};var Ca=\nmxVertexHandler.prototype.mouseUp;mxVertexHandler.prototype.mouseUp=function(A,R){Ca.apply(this,arguments);null!=this.rotationShape&&null!=this.rotationShape.node&&(this.rotationShape.node.style.display=1==this.graph.getSelectionCount()?\"\":\"none\");null!=this.connectShape&&null!=this.connectShape.node&&(this.connectShape.node.style.display=1==this.graph.getSelectionCount()?\"\":\"none\");null!=this.linkHint&&\"none\"==this.linkHint.style.display&&1==this.graph.getSelectionCount()&&(this.linkHint.style.display=\n\"\");this.blockDelayedSelection=null};mxVertexHandler.prototype.updateLinkHint=function(A,R){try{if(null==A&&(null==R||0==R.length))null!=this.linkHint&&(this.linkHint.parentNode.removeChild(this.linkHint),this.linkHint=null);else if(null!=A||null!=R&&0G?\"#FFFFFF\":\"#000000\"),d.begin(),d.moveTo(0,0),d.lineTo(q-C,0),d.lineTo(q,C),d.lineTo(C,C),d.close(),d.fill()),0!=fa&&(d.setFillAlpha(Math.abs(fa)),d.setFillColor(0>fa?\"#FFFFFF\":\"#000000\"),d.begin(),d.moveTo(0,0),d.lineTo(C,C),d.lineTo(C,y),d.lineTo(0,y-C),d.close(),d.fill()),d.begin(),d.moveTo(C,y),d.lineTo(C,\nC),d.lineTo(0,0),d.moveTo(C,C),d.lineTo(q,C),d.end(),d.stroke())};p.prototype.getLabelMargins=function(d){return mxUtils.getValue(this.style,\"boundedLbl\",!1)?(d=parseFloat(mxUtils.getValue(this.style,\"size\",this.size))*this.scale,new mxRectangle(d,d,0,0)):null};mxCellRenderer.registerShape(\"cube\",p);var ib=Math.tan(mxUtils.toRadians(30)),qb=(.5-ib)/2;mxCellRenderer.registerShape(\"isoRectangle\",D);mxUtils.extend(v,mxConnector);v.prototype.paintEdgeShape=function(d,n){var x=this.createMarker(d,n,!0),\nq=this.createMarker(d,n,!1);d.setDashed(!1);mxPolyline.prototype.paintEdgeShape.apply(this,arguments);null!=this.isDashed&&d.setDashed(this.isDashed,null!=this.style?1==mxUtils.getValue(this.style,mxConstants.STYLE_FIX_DASH,!1):!1);d.setShadow(!1);d.setStrokeColor(this.fill);mxPolyline.prototype.paintEdgeShape.apply(this,arguments);d.setStrokeColor(this.stroke);d.setFillColor(this.stroke);d.setDashed(!1);null!=x&&x();null!=q&&q()};mxCellRenderer.registerShape(\"wire\",v);mxUtils.extend(u,mxCylinder);\nu.prototype.size=6;u.prototype.paintVertexShape=function(d,n,x,q,y){d.setFillColor(this.stroke);var C=Math.max(0,parseFloat(mxUtils.getValue(this.style,\"size\",this.size))-2)+2*this.strokewidth;d.ellipse(n+.5*(q-C),x+.5*(y-C),C,C);d.fill();d.setFillColor(mxConstants.NONE);d.rect(n,x,q,y);d.fill()};mxCellRenderer.registerShape(\"waypoint\",u);mxUtils.extend(D,mxActor);D.prototype.size=20;D.prototype.redrawPath=function(d,n,x,q,y){n=Math.min(q,y/ib);d.translate((q-n)/2,(y-n)/2+n/4);d.moveTo(0,.25*n);d.lineTo(.5*\nn,n*qb);d.lineTo(n,.25*n);d.lineTo(.5*n,(.5-qb)*n);d.lineTo(0,.25*n);d.close();d.end()};mxCellRenderer.registerShape(\"isoRectangle\",D);mxUtils.extend(I,mxCylinder);I.prototype.size=20;I.prototype.redrawPath=function(d,n,x,q,y,C){n=Math.min(q,y/(.5+ib));C?(d.moveTo(0,.25*n),d.lineTo(.5*n,(.5-qb)*n),d.lineTo(n,.25*n),d.moveTo(.5*n,(.5-qb)*n),d.lineTo(.5*n,(1-qb)*n)):(d.translate((q-n)/2,(y-n)/2),d.moveTo(0,.25*n),d.lineTo(.5*n,n*qb),d.lineTo(n,.25*n),d.lineTo(n,.75*n),d.lineTo(.5*n,(1-qb)*n),d.lineTo(0,\n.75*n),d.close());d.end()};mxCellRenderer.registerShape(\"isoCube\",I);mxUtils.extend(V,mxCylinder);V.prototype.redrawPath=function(d,n,x,q,y,C){n=Math.min(y/2,Math.round(y/8)+this.strokewidth-1);if(C&&null!=this.fill||!C&&null==this.fill)d.moveTo(0,n),d.curveTo(0,2*n,q,2*n,q,n),C||(d.stroke(),d.begin()),d.translate(0,n/2),d.moveTo(0,n),d.curveTo(0,2*n,q,2*n,q,n),C||(d.stroke(),d.begin()),d.translate(0,n/2),d.moveTo(0,n),d.curveTo(0,2*n,q,2*n,q,n),C||(d.stroke(),d.begin()),d.translate(0,-n);C||(d.moveTo(0,\nn),d.curveTo(0,-n/3,q,-n/3,q,n),d.lineTo(q,y-n),d.curveTo(q,y+n/3,0,y+n/3,0,y-n),d.close())};V.prototype.getLabelMargins=function(d){return new mxRectangle(0,2.5*Math.min(d.height/2,Math.round(d.height/8)+this.strokewidth-1),0,0)};mxCellRenderer.registerShape(\"datastore\",V);mxUtils.extend(F,mxCylinder);F.prototype.size=30;F.prototype.darkOpacity=0;F.prototype.paintVertexShape=function(d,n,x,q,y){var C=Math.max(0,Math.min(q,Math.min(y,parseFloat(mxUtils.getValue(this.style,\"size\",this.size))))),G=\nMath.max(-1,Math.min(1,parseFloat(mxUtils.getValue(this.style,\"darkOpacity\",this.darkOpacity))));d.translate(n,x);d.begin();d.moveTo(0,0);d.lineTo(q-C,0);d.lineTo(q,C);d.lineTo(q,y);d.lineTo(0,y);d.lineTo(0,0);d.close();d.end();d.fillAndStroke();this.outline||(d.setShadow(!1),0!=G&&(d.setFillAlpha(Math.abs(G)),d.setFillColor(0>G?\"#FFFFFF\":\"#000000\"),d.begin(),d.moveTo(q-C,0),d.lineTo(q-C,C),d.lineTo(q,C),d.close(),d.fill()),d.begin(),d.moveTo(q-C,0),d.lineTo(q-C,C),d.lineTo(q,C),d.end(),d.stroke())};\nmxCellRenderer.registerShape(\"note\",F);mxUtils.extend(Q,F);mxCellRenderer.registerShape(\"note2\",Q);Q.prototype.getLabelMargins=function(d){if(mxUtils.getValue(this.style,\"boundedLbl\",!1)){var n=mxUtils.getValue(this.style,\"size\",15);return new mxRectangle(0,Math.min(d.height*this.scale,n*this.scale),0,0)}return null};mxUtils.extend(N,mxShape);N.prototype.isoAngle=15;N.prototype.paintVertexShape=function(d,n,x,q,y){var C=Math.max(.01,Math.min(94,parseFloat(mxUtils.getValue(this.style,\"isoAngle\",this.isoAngle))))*\nMath.PI/200;C=Math.min(q*Math.tan(C),.5*y);d.translate(n,x);d.begin();d.moveTo(.5*q,0);d.lineTo(q,C);d.lineTo(q,y-C);d.lineTo(.5*q,y);d.lineTo(0,y-C);d.lineTo(0,C);d.close();d.fillAndStroke();d.setShadow(!1);d.begin();d.moveTo(0,C);d.lineTo(.5*q,2*C);d.lineTo(q,C);d.moveTo(.5*q,2*C);d.lineTo(.5*q,y);d.stroke()};mxCellRenderer.registerShape(\"isoCube2\",N);mxUtils.extend(c,mxShape);c.prototype.size=15;c.prototype.paintVertexShape=function(d,n,x,q,y){var C=Math.max(0,Math.min(.5*y,parseFloat(mxUtils.getValue(this.style,\n\"size\",this.size))));d.translate(n,x);0==C?(d.rect(0,0,q,y),d.fillAndStroke()):(d.begin(),d.moveTo(0,C),d.arcTo(.5*q,C,0,0,1,.5*q,0),d.arcTo(.5*q,C,0,0,1,q,C),d.lineTo(q,y-C),d.arcTo(.5*q,C,0,0,1,.5*q,y),d.arcTo(.5*q,C,0,0,1,0,y-C),d.close(),d.fillAndStroke(),d.setShadow(!1),d.begin(),d.moveTo(q,C),d.arcTo(.5*q,C,0,0,1,.5*q,2*C),d.arcTo(.5*q,C,0,0,1,0,C),d.stroke())};mxCellRenderer.registerShape(\"cylinder2\",c);mxUtils.extend(g,mxCylinder);g.prototype.size=15;g.prototype.paintVertexShape=function(d,\nn,x,q,y){var C=Math.max(0,Math.min(.5*y,parseFloat(mxUtils.getValue(this.style,\"size\",this.size)))),G=mxUtils.getValue(this.style,\"lid\",!0);d.translate(n,x);0==C?(d.rect(0,0,q,y),d.fillAndStroke()):(d.begin(),G?(d.moveTo(0,C),d.arcTo(.5*q,C,0,0,1,.5*q,0),d.arcTo(.5*q,C,0,0,1,q,C)):(d.moveTo(0,0),d.arcTo(.5*q,C,0,0,0,.5*q,C),d.arcTo(.5*q,C,0,0,0,q,0)),d.lineTo(q,y-C),d.arcTo(.5*q,C,0,0,1,.5*q,y),d.arcTo(.5*q,C,0,0,1,0,y-C),d.close(),d.fillAndStroke(),d.setShadow(!1),G&&(d.begin(),d.moveTo(q,C),d.arcTo(.5*\nq,C,0,0,1,.5*q,2*C),d.arcTo(.5*q,C,0,0,1,0,C),d.stroke()))};mxCellRenderer.registerShape(\"cylinder3\",g);mxUtils.extend(l,mxActor);l.prototype.redrawPath=function(d,n,x,q,y){d.moveTo(0,0);d.quadTo(q/2,.5*y,q,0);d.quadTo(.5*q,y/2,q,y);d.quadTo(q/2,.5*y,0,y);d.quadTo(.5*q,y/2,0,0);d.end()};mxCellRenderer.registerShape(\"switch\",l);mxUtils.extend(t,mxCylinder);t.prototype.tabWidth=60;t.prototype.tabHeight=20;t.prototype.tabPosition=\"right\";t.prototype.arcSize=.1;t.prototype.isRoundable=function(){return!0};\nt.prototype.paintVertexShape=function(d,n,x,q,y){d.translate(n,x);n=Math.max(0,Math.min(q,parseFloat(mxUtils.getValue(this.style,\"tabWidth\",this.tabWidth))));x=Math.max(0,Math.min(y,parseFloat(mxUtils.getValue(this.style,\"tabHeight\",this.tabHeight))));var C=mxUtils.getValue(this.style,\"tabPosition\",this.tabPosition),G=mxUtils.getValue(this.style,\"rounded\",!1),fa=mxUtils.getValue(this.style,\"absoluteArcSize\",!1),P=parseFloat(mxUtils.getValue(this.style,\"arcSize\",this.arcSize));fa||(P*=Math.min(q,y));\nP=Math.min(P,.5*q,.5*(y-x));n=Math.max(n,P);n=Math.min(q-P,n);G||(P=0);d.begin();\"left\"==C?(d.moveTo(Math.max(P,0),x),d.lineTo(Math.max(P,0),0),d.lineTo(n,0),d.lineTo(n,x)):(d.moveTo(q-n,x),d.lineTo(q-n,0),d.lineTo(q-Math.max(P,0),0),d.lineTo(q-Math.max(P,0),x));G?(d.moveTo(0,P+x),d.arcTo(P,P,0,0,1,P,x),d.lineTo(q-P,x),d.arcTo(P,P,0,0,1,q,P+x),d.lineTo(q,y-P),d.arcTo(P,P,0,0,1,q-P,y),d.lineTo(P,y),d.arcTo(P,P,0,0,1,0,y-P)):(d.moveTo(0,x),d.lineTo(q,x),d.lineTo(q,y),d.lineTo(0,y));d.close();d.fillAndStroke();\nd.setShadow(!1);\"triangle\"==mxUtils.getValue(this.style,\"folderSymbol\",null)&&(d.begin(),d.moveTo(q-30,x+20),d.lineTo(q-20,x+10),d.lineTo(q-10,x+20),d.close(),d.stroke())};mxCellRenderer.registerShape(\"folder\",t);t.prototype.getLabelMargins=function(d){if(mxUtils.getValue(this.style,\"boundedLbl\",!1)){var n=mxUtils.getValue(this.style,\"tabHeight\",15)*this.scale;if(mxUtils.getValue(this.style,\"labelInHeader\",!1)){var x=mxUtils.getValue(this.style,\"tabWidth\",15)*this.scale;n=mxUtils.getValue(this.style,\n\"tabHeight\",15)*this.scale;var q=mxUtils.getValue(this.style,\"rounded\",!1),y=mxUtils.getValue(this.style,\"absoluteArcSize\",!1),C=parseFloat(mxUtils.getValue(this.style,\"arcSize\",this.arcSize));y||(C*=Math.min(d.width,d.height));C=Math.min(C,.5*d.width,.5*(d.height-n));q||(C=0);return\"left\"==mxUtils.getValue(this.style,\"tabPosition\",this.tabPosition)?new mxRectangle(C,0,Math.min(d.width,d.width-x),Math.min(d.height,d.height-n)):new mxRectangle(Math.min(d.width,d.width-x),0,C,Math.min(d.height,d.height-\nn))}return new mxRectangle(0,Math.min(d.height,n),0,0)}return null};mxUtils.extend(B,mxCylinder);B.prototype.arcSize=.1;B.prototype.paintVertexShape=function(d,n,x,q,y){d.translate(n,x);var C=mxUtils.getValue(this.style,\"rounded\",!1),G=mxUtils.getValue(this.style,\"absoluteArcSize\",!1);n=parseFloat(mxUtils.getValue(this.style,\"arcSize\",this.arcSize));x=mxUtils.getValue(this.style,\"umlStateConnection\",null);G||(n*=Math.min(q,y));n=Math.min(n,.5*q,.5*y);C||(n=0);C=0;null!=x&&(C=10);d.begin();d.moveTo(C,\nn);d.arcTo(n,n,0,0,1,C+n,0);d.lineTo(q-n,0);d.arcTo(n,n,0,0,1,q,n);d.lineTo(q,y-n);d.arcTo(n,n,0,0,1,q-n,y);d.lineTo(C+n,y);d.arcTo(n,n,0,0,1,C,y-n);d.close();d.fillAndStroke();d.setShadow(!1);\"collapseState\"==mxUtils.getValue(this.style,\"umlStateSymbol\",null)&&(d.roundrect(q-40,y-20,10,10,3,3),d.stroke(),d.roundrect(q-20,y-20,10,10,3,3),d.stroke(),d.begin(),d.moveTo(q-30,y-15),d.lineTo(q-20,y-15),d.stroke());\"connPointRefEntry\"==x?(d.ellipse(0,.5*y-10,20,20),d.fillAndStroke()):\"connPointRefExit\"==\nx&&(d.ellipse(0,.5*y-10,20,20),d.fillAndStroke(),d.begin(),d.moveTo(5,.5*y-5),d.lineTo(15,.5*y+5),d.moveTo(15,.5*y-5),d.lineTo(5,.5*y+5),d.stroke())};B.prototype.getLabelMargins=function(d){return mxUtils.getValue(this.style,\"boundedLbl\",!1)&&null!=mxUtils.getValue(this.style,\"umlStateConnection\",null)?new mxRectangle(10*this.scale,0,0,0):null};mxCellRenderer.registerShape(\"umlState\",B);mxUtils.extend(E,mxShape);E.prototype.featureColor=\"#666666\";E.prototype.paintVertexShape=function(d,n,x,q,y){var C=\nmxUtils.getValue(this.style,\"smileyType\",\"happy\"),G=mxUtils.getValue(this.style,\"smileyFeatureColor\",E.prototype.featureColor);d.translate(n,x);n=Math.min(q,y)/2;d.ellipse(q/2-n,y/2-n,2*n,2*n);d.fillAndStroke();n=Math.min(q,y)/30;q/=2;y/=2;d.setFillColor(G);d.setStrokeColor(G);d.setStrokeWidth(2*n);G=1.5*n;x=5*n;var fa=5*n;d.ellipse(q-x-G,y-fa-G,2*G,2*G);d.fillAndStroke();d.ellipse(q+x-G,y-fa-G,2*G,2*G);d.fillAndStroke();d.setStrokeWidth(1*n);d.setFillColor(\"#000000\");\"happy\"==C?(C=q,G=y+2*n,d.begin(),\nd.moveTo(C+7.5*n,G),d.arcTo(7.5*n,7.5*n,0,1,1,C-7.5*n,G),d.lineTo(C-6.818*n,G),d.arcTo(6.818*n,6.818*n,0,1,0,C+6.818*n,G),d.close(),d.fillAndStroke()):\"sad\"==C?(C=q,G=y+7*n,d.begin(),d.moveTo(C-7.5*n,G),d.arcTo(7.5*n,7.5*n,0,1,1,C+7.5*n,G),d.lineTo(C+6.818*n,G),d.arcTo(6.818*n,6.818*n,0,1,0,C-6.818*n,G),d.close(),d.fillAndStroke()):(d.begin(),d.moveTo(q-5*n,y+7*n),d.lineTo(q+5*n,y+7*n),d.stroke())};mxCellRenderer.registerShape(\"smileyFace\",E);mxUtils.extend(H,mxActor);H.prototype.size=30;H.prototype.isRoundable=\nfunction(){return!0};H.prototype.redrawPath=function(d,n,x,q,y){n=Math.max(0,Math.min(q,Math.min(y,parseFloat(mxUtils.getValue(this.style,\"size\",this.size)))));x=mxUtils.getValue(this.style,mxConstants.STYLE_ARCSIZE,mxConstants.LINE_ARCSIZE)/2;this.addPoints(d,[new mxPoint(n,0),new mxPoint(q,0),new mxPoint(q,y),new mxPoint(0,y),new mxPoint(0,n)],this.isRounded,x,!0);d.end()};mxCellRenderer.registerShape(\"card\",H);mxUtils.extend(K,mxActor);K.prototype.size=.4;K.prototype.redrawPath=function(d,n,x,\nq,y){n=y*Math.max(0,Math.min(1,parseFloat(mxUtils.getValue(this.style,\"size\",this.size))));d.moveTo(0,n/2);d.quadTo(q/4,1.4*n,q/2,n/2);d.quadTo(3*q/4,n*(1-1.4),q,n/2);d.lineTo(q,y-n/2);d.quadTo(3*q/4,y-1.4*n,q/2,y-n/2);d.quadTo(q/4,y-n*(1-1.4),0,y-n/2);d.lineTo(0,n/2);d.close();d.end()};K.prototype.getLabelBounds=function(d){if(mxUtils.getValue(this.style,\"boundedLbl\",!1)){var n=mxUtils.getValue(this.style,\"size\",this.size),x=d.width,q=d.height;if(null==this.direction||this.direction==mxConstants.DIRECTION_EAST||\nthis.direction==mxConstants.DIRECTION_WEST)return n*=q,new mxRectangle(d.x,d.y+n,x,q-2*n);n*=x;return new mxRectangle(d.x+n,d.y,x-2*n,q)}return d};mxCellRenderer.registerShape(\"tape\",K);mxUtils.extend(O,mxActor);O.prototype.size=.3;O.prototype.getLabelMargins=function(d){return mxUtils.getValue(this.style,\"boundedLbl\",!1)?new mxRectangle(0,0,0,parseFloat(mxUtils.getValue(this.style,\"size\",this.size))*d.height):null};O.prototype.redrawPath=function(d,n,x,q,y){n=y*Math.max(0,Math.min(1,parseFloat(mxUtils.getValue(this.style,\n\"size\",this.size))));d.moveTo(0,0);d.lineTo(q,0);d.lineTo(q,y-n/2);d.quadTo(3*q/4,y-1.4*n,q/2,y-n/2);d.quadTo(q/4,y-n*(1-1.4),0,y-n/2);d.lineTo(0,n/2);d.close();d.end()};mxCellRenderer.registerShape(\"document\",O);var tb=mxCylinder.prototype.getCylinderSize;mxCylinder.prototype.getCylinderSize=function(d,n,x,q){var y=mxUtils.getValue(this.style,\"size\");return null!=y?q*Math.max(0,Math.min(1,y)):tb.apply(this,arguments)};mxCylinder.prototype.getLabelMargins=function(d){if(mxUtils.getValue(this.style,\n\"boundedLbl\",!1)){var n=2*mxUtils.getValue(this.style,\"size\",.15);return new mxRectangle(0,Math.min(this.maxHeight*this.scale,d.height*n),0,0)}return null};g.prototype.getLabelMargins=function(d){if(mxUtils.getValue(this.style,\"boundedLbl\",!1)){var n=mxUtils.getValue(this.style,\"size\",15);mxUtils.getValue(this.style,\"lid\",!0)||(n/=2);return new mxRectangle(0,Math.min(d.height*this.scale,2*n*this.scale),0,Math.max(0,.3*n*this.scale))}return null};t.prototype.getLabelMargins=function(d){if(mxUtils.getValue(this.style,\n\"boundedLbl\",!1)){var n=mxUtils.getValue(this.style,\"tabHeight\",15)*this.scale;if(mxUtils.getValue(this.style,\"labelInHeader\",!1)){var x=mxUtils.getValue(this.style,\"tabWidth\",15)*this.scale;n=mxUtils.getValue(this.style,\"tabHeight\",15)*this.scale;var q=mxUtils.getValue(this.style,\"rounded\",!1),y=mxUtils.getValue(this.style,\"absoluteArcSize\",!1),C=parseFloat(mxUtils.getValue(this.style,\"arcSize\",this.arcSize));y||(C*=Math.min(d.width,d.height));C=Math.min(C,.5*d.width,.5*(d.height-n));q||(C=0);return\"left\"==\nmxUtils.getValue(this.style,\"tabPosition\",this.tabPosition)?new mxRectangle(C,0,Math.min(d.width,d.width-x),Math.min(d.height,d.height-n)):new mxRectangle(Math.min(d.width,d.width-x),0,C,Math.min(d.height,d.height-n))}return new mxRectangle(0,Math.min(d.height,n),0,0)}return null};B.prototype.getLabelMargins=function(d){return mxUtils.getValue(this.style,\"boundedLbl\",!1)&&null!=mxUtils.getValue(this.style,\"umlStateConnection\",null)?new mxRectangle(10*this.scale,0,0,0):null};Q.prototype.getLabelMargins=\nfunction(d){if(mxUtils.getValue(this.style,\"boundedLbl\",!1)){var n=mxUtils.getValue(this.style,\"size\",15);return new mxRectangle(0,Math.min(d.height*this.scale,n*this.scale),0,Math.max(0,n*this.scale))}return null};mxUtils.extend(W,mxActor);W.prototype.size=.2;W.prototype.fixedSize=20;W.prototype.isRoundable=function(){return!0};W.prototype.redrawPath=function(d,n,x,q,y){n=\"0\"!=mxUtils.getValue(this.style,\"fixedSize\",\"0\")?Math.max(0,Math.min(q,parseFloat(mxUtils.getValue(this.style,\"size\",this.fixedSize)))):\nq*Math.max(0,Math.min(1,parseFloat(mxUtils.getValue(this.style,\"size\",this.size))));x=mxUtils.getValue(this.style,mxConstants.STYLE_ARCSIZE,mxConstants.LINE_ARCSIZE)/2;this.addPoints(d,[new mxPoint(0,y),new mxPoint(n,0),new mxPoint(q,0),new mxPoint(q-n,y)],this.isRounded,x,!0);d.end()};mxCellRenderer.registerShape(\"parallelogram\",W);mxUtils.extend(L,mxActor);L.prototype.size=.2;L.prototype.fixedSize=20;L.prototype.isRoundable=function(){return!0};L.prototype.redrawPath=function(d,n,x,q,y){n=\"0\"!=\nmxUtils.getValue(this.style,\"fixedSize\",\"0\")?Math.max(0,Math.min(.5*q,parseFloat(mxUtils.getValue(this.style,\"size\",this.fixedSize)))):q*Math.max(0,Math.min(.5,parseFloat(mxUtils.getValue(this.style,\"size\",this.size))));x=mxUtils.getValue(this.style,mxConstants.STYLE_ARCSIZE,mxConstants.LINE_ARCSIZE)/2;this.addPoints(d,[new mxPoint(0,y),new mxPoint(n,0),new mxPoint(q-n,0),new mxPoint(q,y)],this.isRounded,x,!0)};mxCellRenderer.registerShape(\"trapezoid\",L);mxUtils.extend(ca,mxActor);ca.prototype.size=\n.5;ca.prototype.redrawPath=function(d,n,x,q,y){d.setFillColor(null);n=q*Math.max(0,Math.min(1,parseFloat(mxUtils.getValue(this.style,\"size\",this.size))));x=mxUtils.getValue(this.style,mxConstants.STYLE_ARCSIZE,mxConstants.LINE_ARCSIZE)/2;this.addPoints(d,[new mxPoint(q,0),new mxPoint(n,0),new mxPoint(n,y/2),new mxPoint(0,y/2),new mxPoint(n,y/2),new mxPoint(n,y),new mxPoint(q,y)],this.isRounded,x,!1);d.end()};mxCellRenderer.registerShape(\"curlyBracket\",ca);mxUtils.extend(ha,mxActor);ha.prototype.redrawPath=\nfunction(d,n,x,q,y){d.setStrokeWidth(1);d.setFillColor(this.stroke);n=q/5;d.rect(0,0,n,y);d.fillAndStroke();d.rect(2*n,0,n,y);d.fillAndStroke();d.rect(4*n,0,n,y);d.fillAndStroke()};mxCellRenderer.registerShape(\"parallelMarker\",ha);ia.prototype.moveTo=function(d,n){this.originalMoveTo.apply(this.canvas,arguments);this.lastX=d;this.lastY=n;this.firstX=d;this.firstY=n};ia.prototype.close=function(){null!=this.firstX&&null!=this.firstY&&(this.lineTo(this.firstX,this.firstY),this.originalClose.apply(this.canvas,\narguments));this.originalClose.apply(this.canvas,arguments)};ia.prototype.quadTo=function(d,n,x,q){this.originalQuadTo.apply(this.canvas,arguments);this.lastX=x;this.lastY=q};ia.prototype.curveTo=function(d,n,x,q,y,C){this.originalCurveTo.apply(this.canvas,arguments);this.lastX=y;this.lastY=C};ia.prototype.arcTo=function(d,n,x,q,y,C,G){this.originalArcTo.apply(this.canvas,arguments);this.lastX=C;this.lastY=G};ia.prototype.lineTo=function(d,n){if(null!=this.lastX&&null!=this.lastY){var x=function(na){return\"number\"===\ntypeof na?na?0>na?-1:1:na===na?0:NaN:NaN},q=Math.abs(d-this.lastX),y=Math.abs(n-this.lastY),C=Math.sqrt(q*q+y*y);if(2>C){this.originalLineTo.apply(this.canvas,arguments);this.lastX=d;this.lastY=n;return}var G=Math.round(C/10),fa=this.defaultVariation;5>G&&(G=5,fa/=3);var P=x(d-this.lastX)*q/G;x=x(n-this.lastY)*y/G;q/=C;y/=C;for(C=0;CG+P?d.y=x.y:d.x=x.x);return mxUtils.getPerimeterPoint(fa,d,x)};mxStyleRegistry.putValue(\"parallelogramPerimeter\",mxPerimeter.ParallelogramPerimeter);mxPerimeter.TrapezoidPerimeter=function(d,n,x,q){var y=\"0\"!=\nmxUtils.getValue(n.style,\"fixedSize\",\"0\"),C=y?L.prototype.fixedSize:L.prototype.size;null!=n&&(C=mxUtils.getValue(n.style,\"size\",C));y&&(C*=n.view.scale);var G=d.x,fa=d.y,P=d.width,ua=d.height;n=null!=n?mxUtils.getValue(n.style,mxConstants.STYLE_DIRECTION,mxConstants.DIRECTION_EAST):mxConstants.DIRECTION_EAST;n==mxConstants.DIRECTION_EAST?(y=y?Math.max(0,Math.min(.5*P,C)):P*Math.max(0,Math.min(1,C)),fa=[new mxPoint(G+y,fa),new mxPoint(G+P-y,fa),new mxPoint(G+P,fa+ua),new mxPoint(G,fa+ua),new mxPoint(G+\ny,fa)]):n==mxConstants.DIRECTION_WEST?(y=y?Math.max(0,Math.min(P,C)):P*Math.max(0,Math.min(1,C)),fa=[new mxPoint(G,fa),new mxPoint(G+P,fa),new mxPoint(G+P-y,fa+ua),new mxPoint(G+y,fa+ua),new mxPoint(G,fa)]):n==mxConstants.DIRECTION_NORTH?(y=y?Math.max(0,Math.min(ua,C)):ua*Math.max(0,Math.min(1,C)),fa=[new mxPoint(G,fa+y),new mxPoint(G+P,fa),new mxPoint(G+P,fa+ua),new mxPoint(G,fa+ua-y),new mxPoint(G,fa+y)]):(y=y?Math.max(0,Math.min(ua,C)):ua*Math.max(0,Math.min(1,C)),fa=[new mxPoint(G,fa),new mxPoint(G+\nP,fa+y),new mxPoint(G+P,fa+ua-y),new mxPoint(G,fa+ua),new mxPoint(G,fa)]);ua=d.getCenterX();d=d.getCenterY();d=new mxPoint(ua,d);q&&(x.xG+P?d.y=x.y:d.x=x.x);return mxUtils.getPerimeterPoint(fa,d,x)};mxStyleRegistry.putValue(\"trapezoidPerimeter\",mxPerimeter.TrapezoidPerimeter);mxPerimeter.StepPerimeter=function(d,n,x,q){var y=\"0\"!=mxUtils.getValue(n.style,\"fixedSize\",\"0\"),C=y?ba.prototype.fixedSize:ba.prototype.size;null!=n&&(C=mxUtils.getValue(n.style,\"size\",C));y&&(C*=n.view.scale);var G=\nd.x,fa=d.y,P=d.width,ua=d.height,na=d.getCenterX();d=d.getCenterY();n=null!=n?mxUtils.getValue(n.style,mxConstants.STYLE_DIRECTION,mxConstants.DIRECTION_EAST):mxConstants.DIRECTION_EAST;n==mxConstants.DIRECTION_EAST?(y=y?Math.max(0,Math.min(P,C)):P*Math.max(0,Math.min(1,C)),fa=[new mxPoint(G,fa),new mxPoint(G+P-y,fa),new mxPoint(G+P,d),new mxPoint(G+P-y,fa+ua),new mxPoint(G,fa+ua),new mxPoint(G+y,d),new mxPoint(G,fa)]):n==mxConstants.DIRECTION_WEST?(y=y?Math.max(0,Math.min(P,C)):P*Math.max(0,Math.min(1,\nC)),fa=[new mxPoint(G+y,fa),new mxPoint(G+P,fa),new mxPoint(G+P-y,d),new mxPoint(G+P,fa+ua),new mxPoint(G+y,fa+ua),new mxPoint(G,d),new mxPoint(G+y,fa)]):n==mxConstants.DIRECTION_NORTH?(y=y?Math.max(0,Math.min(ua,C)):ua*Math.max(0,Math.min(1,C)),fa=[new mxPoint(G,fa+y),new mxPoint(na,fa),new mxPoint(G+P,fa+y),new mxPoint(G+P,fa+ua),new mxPoint(na,fa+ua-y),new mxPoint(G,fa+ua),new mxPoint(G,fa+y)]):(y=y?Math.max(0,Math.min(ua,C)):ua*Math.max(0,Math.min(1,C)),fa=[new mxPoint(G,fa),new mxPoint(na,fa+\ny),new mxPoint(G+P,fa),new mxPoint(G+P,fa+ua-y),new mxPoint(na,fa+ua),new mxPoint(G,fa+ua-y),new mxPoint(G,fa)]);na=new mxPoint(na,d);q&&(x.xG+P?na.y=x.y:na.x=x.x);return mxUtils.getPerimeterPoint(fa,na,x)};mxStyleRegistry.putValue(\"stepPerimeter\",mxPerimeter.StepPerimeter);mxPerimeter.HexagonPerimeter2=function(d,n,x,q){var y=\"0\"!=mxUtils.getValue(n.style,\"fixedSize\",\"0\"),C=y?k.prototype.fixedSize:k.prototype.size;null!=n&&(C=mxUtils.getValue(n.style,\"size\",C));y&&(C*=n.view.scale);var G=\nd.x,fa=d.y,P=d.width,ua=d.height,na=d.getCenterX();d=d.getCenterY();n=null!=n?mxUtils.getValue(n.style,mxConstants.STYLE_DIRECTION,mxConstants.DIRECTION_EAST):mxConstants.DIRECTION_EAST;n==mxConstants.DIRECTION_NORTH||n==mxConstants.DIRECTION_SOUTH?(y=y?Math.max(0,Math.min(ua,C)):ua*Math.max(0,Math.min(1,C)),fa=[new mxPoint(na,fa),new mxPoint(G+P,fa+y),new mxPoint(G+P,fa+ua-y),new mxPoint(na,fa+ua),new mxPoint(G,fa+ua-y),new mxPoint(G,fa+y),new mxPoint(na,fa)]):(y=y?Math.max(0,Math.min(P,C)):P*Math.max(0,\nMath.min(1,C)),fa=[new mxPoint(G+y,fa),new mxPoint(G+P-y,fa),new mxPoint(G+P,d),new mxPoint(G+P-y,fa+ua),new mxPoint(G+y,fa+ua),new mxPoint(G,d),new mxPoint(G+y,fa)]);na=new mxPoint(na,d);q&&(x.xG+P?na.y=x.y:na.x=x.x);return mxUtils.getPerimeterPoint(fa,na,x)};mxStyleRegistry.putValue(\"hexagonPerimeter2\",mxPerimeter.HexagonPerimeter2);mxUtils.extend(oa,mxShape);oa.prototype.size=10;oa.prototype.paintBackground=function(d,n,x,q,y){var C=parseFloat(mxUtils.getValue(this.style,\"size\",this.size));\nd.translate(n,x);d.ellipse((q-C)/2,0,C,C);d.fillAndStroke();d.begin();d.moveTo(q/2,C);d.lineTo(q/2,y);d.end();d.stroke()};mxCellRenderer.registerShape(\"lollipop\",oa);mxUtils.extend(ra,mxShape);ra.prototype.size=10;ra.prototype.inset=2;ra.prototype.paintBackground=function(d,n,x,q,y){var C=parseFloat(mxUtils.getValue(this.style,\"size\",this.size)),G=parseFloat(mxUtils.getValue(this.style,\"inset\",this.inset))+this.strokewidth;d.translate(n,x);d.begin();d.moveTo(q/2,C+G);d.lineTo(q/2,y);d.end();d.stroke();\nd.begin();d.moveTo((q-C)/2-G,C/2);d.quadTo((q-C)/2-G,C+G,q/2,C+G);d.quadTo((q+C)/2+G,C+G,(q+C)/2+G,C/2);d.end();d.stroke()};mxCellRenderer.registerShape(\"requires\",ra);mxUtils.extend(ta,mxShape);ta.prototype.paintBackground=function(d,n,x,q,y){d.translate(n,x);d.begin();d.moveTo(0,0);d.quadTo(q,0,q,y/2);d.quadTo(q,y,0,y);d.end();d.stroke()};mxCellRenderer.registerShape(\"requiredInterface\",ta);mxUtils.extend(za,mxShape);za.prototype.inset=2;za.prototype.paintBackground=function(d,n,x,q,y){var C=parseFloat(mxUtils.getValue(this.style,\n\"inset\",this.inset))+this.strokewidth;d.translate(n,x);d.ellipse(0,C,q-2*C,y-2*C);d.fillAndStroke();d.begin();d.moveTo(q/2,0);d.quadTo(q,0,q,y/2);d.quadTo(q,y,q/2,y);d.end();d.stroke()};mxCellRenderer.registerShape(\"providedRequiredInterface\",za);mxUtils.extend(Ia,mxCylinder);Ia.prototype.jettyWidth=20;Ia.prototype.jettyHeight=10;Ia.prototype.redrawPath=function(d,n,x,q,y,C){var G=parseFloat(mxUtils.getValue(this.style,\"jettyWidth\",this.jettyWidth));n=parseFloat(mxUtils.getValue(this.style,\"jettyHeight\",\nthis.jettyHeight));x=G/2;G=x+G/2;var fa=Math.min(n,y-n),P=Math.min(fa+2*n,y-n);C?(d.moveTo(x,fa),d.lineTo(G,fa),d.lineTo(G,fa+n),d.lineTo(x,fa+n),d.moveTo(x,P),d.lineTo(G,P),d.lineTo(G,P+n),d.lineTo(x,P+n)):(d.moveTo(x,0),d.lineTo(q,0),d.lineTo(q,y),d.lineTo(x,y),d.lineTo(x,P+n),d.lineTo(0,P+n),d.lineTo(0,P),d.lineTo(x,P),d.lineTo(x,fa+n),d.lineTo(0,fa+n),d.lineTo(0,fa),d.lineTo(x,fa),d.close());d.end()};mxCellRenderer.registerShape(\"module\",Ia);mxUtils.extend(ya,mxCylinder);ya.prototype.jettyWidth=\n32;ya.prototype.jettyHeight=12;ya.prototype.redrawPath=function(d,n,x,q,y,C){var G=parseFloat(mxUtils.getValue(this.style,\"jettyWidth\",this.jettyWidth));n=parseFloat(mxUtils.getValue(this.style,\"jettyHeight\",this.jettyHeight));x=G/2;G=x+G/2;var fa=.3*y-n/2,P=.7*y-n/2;C?(d.moveTo(x,fa),d.lineTo(G,fa),d.lineTo(G,fa+n),d.lineTo(x,fa+n),d.moveTo(x,P),d.lineTo(G,P),d.lineTo(G,P+n),d.lineTo(x,P+n)):(d.moveTo(x,0),d.lineTo(q,0),d.lineTo(q,y),d.lineTo(x,y),d.lineTo(x,P+n),d.lineTo(0,P+n),d.lineTo(0,P),d.lineTo(x,\nP),d.lineTo(x,fa+n),d.lineTo(0,fa+n),d.lineTo(0,fa),d.lineTo(x,fa),d.close());d.end()};mxCellRenderer.registerShape(\"component\",ya);mxUtils.extend(Da,mxRectangleShape);Da.prototype.paintForeground=function(d,n,x,q,y){var C=q/2,G=y/2,fa=mxUtils.getValue(this.style,mxConstants.STYLE_ARCSIZE,mxConstants.LINE_ARCSIZE)/2;d.begin();this.addPoints(d,[new mxPoint(n+C,x),new mxPoint(n+q,x+G),new mxPoint(n+C,x+y),new mxPoint(n,x+G)],this.isRounded,fa,!0);d.stroke();mxRectangleShape.prototype.paintForeground.apply(this,\narguments)};mxCellRenderer.registerShape(\"associativeEntity\",Da);mxUtils.extend(Va,mxDoubleEllipse);Va.prototype.outerStroke=!0;Va.prototype.paintVertexShape=function(d,n,x,q,y){var C=Math.min(4,Math.min(q/5,y/5));0ua?(ua=\"M \"+d.format(fa)+\" \"+d.format(G)+\" L \"+d.format(jb)+\" \"+d.format(P),fa=Ja):(Ja/=2,ab=Ja*Ja/(2*ua)+ua/2,Ha=0<(jb-fa)*(Ha-G)-(P-G)*(Fa-fa)?0:1,Fa=ua>Ja?1:0,ua=\"M \"+d.format(fa)+\" \"+d.format(G)+\" A \"+d.format(ab)+\" \"+d.format(ab)+\" 0 \"+Fa+\" \"+Ha+\" \"+d.format(jb)+\" \"+d.format(P),fa=2*Math.asin(Math.min(1,Ja/ab)),Fa&&(fa=2*Math.PI-fa),fa*=ab)):(ua=\"M \"+d.format(fa)+\" \"+d.format(G)+\" Q \"+d.format(Fa)+\" \"+d.format(Ha)+\" \"+\nd.format(jb)+\" \"+d.format(P),ab=Fa-fa,Ja=Ha-G,Fa=jb-Fa,Ha=P-Ha,fa=jb-fa,G=P-G,fa=(Math.sqrt(ab*ab+Ja*Ja)+Math.sqrt(Fa*Fa+Ha*Ha)+Math.sqrt(fa*fa+G*G))/2);P=(d.idPrefix||\"\")+\"ctp-\"+mxObjectIdentity.get(this);Fa=(G=!mxClient.IS_CHROMEAPP&&d.root.ownerDocument==document)?d.getBaseUrl().replace(/([\\(\\)])/g,\"\\\\$1\"):\"\";jb=d.createElement(\"g\");Ha=na.transform||\"\";0na.alpha&&jb.setAttribute(\"opacity\",na.alpha);Ha=d.createElement(\"defs\");ab=d.createElement(\"path\");\nab.setAttribute(\"id\",P);ab.setAttribute(\"d\",ua);Ha.appendChild(ab);jb.appendChild(Ha);ua=d.createElement(\"text\");ab=mxUtils.getValue(this.style,mxConstants.STYLE_FONTFAMILY,mxConstants.DEFAULT_FONTFAMILY);Ja=mxUtils.getValue(this.style,mxConstants.STYLE_FONTCOLOR,\"#000000\");Ha=mxUtils.getValue(this.style,mxConstants.STYLE_FONTSTYLE,0);ua.setAttribute(\"font-size\",Ua*na.scale+\"px\");ua.setAttribute(\"font-family\",mxUtils.parseCssFontFamily(ab));ua.setAttribute(\"fill\",Ja);(Ha&mxConstants.FONT_BOLD)==mxConstants.FONT_BOLD&&\nua.setAttribute(\"font-weight\",\"bold\");(Ha&mxConstants.FONT_ITALIC)==mxConstants.FONT_ITALIC&&ua.setAttribute(\"font-style\",\"italic\");na=[];(Ha&mxConstants.FONT_UNDERLINE)==mxConstants.FONT_UNDERLINE&&na.push(\"underline\");(Ha&mxConstants.FONT_STRIKETHROUGH)==mxConstants.FONT_STRIKETHROUGH&&na.push(\"line-through\");0=C)){var G=parseFloat(mxUtils.getValue(this.style,\"tabSize\",this.tabSize)),fa=mxUtils.getValue(this.style,\"holeColor\",mxUtils.getValue(this.style,mxConstants.STYLE_FONTCOLOR,\"#333333\"));d.setFillColor(fa);d.setStrokeColor(fa);d.begin();d.ellipse(G/2-C,y/2-C,2*C,2*C);d.fillAndStroke()}};ob.prototype.getLabelBounds=function(d){var n=parseFloat(mxUtils.getValue(this.style,\"tabSize\",this.tabSize))*this.scale;return new mxRectangle(d.x+\nn,d.y,d.width-n,d.height)};mxCellRenderer.registerShape(\"gitTag\",ob);mxUtils.extend(lb,mxShape);lb.prototype.paintVertexShape=function(d,n,x,q,y){var C=mxUtils.getValue(this.style,\"innerColor\",\"#ECECFF\");d.translate(n,x);d.ellipse(0,0,q,y);d.fillAndStroke();n=.6*Math.min(q,y);q=(q-n)/2;y=(y-n)/2;d.setFillColor(C);d.setStrokeColor(C);d.ellipse(q,y,n,n);d.fillAndStroke()};mxCellRenderer.registerShape(\"gitMergeCommit\",lb);mxUtils.extend(gb,mxShape);gb.prototype.paintVertexShape=function(d,n,x,q,y){var C=\nmxUtils.getValue(this.style,\"featureColor\",\"#fff\");d.translate(n,x);n=q/2;x=y/2;var G=Math.min(q,y)/20;d.ellipse(0,0,q,y);d.fillAndStroke();d.setFillColor(C);d.setStrokeColor(C);d.setStrokeWidth(0);q=2.75*G;d.ellipse(n-3*G-q,x+2*G-q,2*q,2*q);d.fillAndStroke();d.ellipse(n+3*G-q,x+2*G-q,2*q,2*q);d.fillAndStroke();d.setStrokeWidth(1*G);d.begin();d.moveTo(n+3*G,x+1*G);d.lineTo(n,x-5*G);d.stroke();d.begin();d.moveTo(n-3*G,x+1*G);d.lineTo(n,x-5*G);d.stroke()};mxCellRenderer.registerShape(\"gitCherryPick\",\ngb);mxUtils.extend(Xa,mxActor);Xa.prototype.isRoundable=function(){return!1};Xa.prototype.redrawPath=function(d,n,x,q,y){n=.8*q;y*=.8;x=.15*n;q=.8*x;var C=.1*n,G=.1*y;d.moveTo(C,G);C+=.25*n;G+=.1*-y;d.arcTo(x,x,0,0,0,C,G);C+=.25*n;G+=0;d.arcTo(x,x,0,0,0,C,G);C+=.25*n;G+=0;d.arcTo(x,x,0,0,0,C,G);C+=.25*n;G+=.1*y;d.arcTo(x,x,0,0,0,C,G);C+=.15*n;G+=.33*y;d.arcTo(x,x,0,0,0,C,G);C+=0;G+=.34*y;d.arcTo(q,q,0,0,0,C,G);C+=.15*-n;G+=.33*y;d.arcTo(x,x,0,0,0,C,G);C+=.25*-n;G+=.15*y;d.arcTo(x,x,0,0,0,C,G);C+=\n.25*-n;G+=0;d.arcTo(x,x,0,0,0,C,G);C+=.25*-n;G+=0;d.arcTo(x,x,0,0,0,C,G);C+=.25*-n;G+=.15*-y;d.arcTo(x,x,0,0,0,C,G);C+=.1*-n;G+=.33*-y;d.arcTo(x,x,0,0,0,C,G);C+=0;G+=.34*-y;d.arcTo(q,q,0,0,0,C,G);d.arcTo(x,x,0,0,0,C+.1*n,G+.33*-y);d.close();d.end()};Xa.prototype.getLabelBounds=function(d){var n=.1*d.width,x=.1*d.height;return new mxRectangle(d.x+n,d.y+x,d.width-2*n,d.height-2*x)};mxCellRenderer.registerShape(\"mindmapBang\",Xa);mxUtils.extend(Sa,mxActor);Sa.prototype.isRoundable=function(){return!1};\nSa.prototype.redrawPath=function(d,n,x,q,y){d.moveTo(0,0);d.lineTo(0,y);d.quadTo(2*q,y/2,0,0);d.close();d.end()};mxCellRenderer.registerShape(\"ishikawaHead\",Sa);mxUtils.extend(pb,mxActor);pb.prototype.isRoundable=function(){return!1};pb.prototype.redrawPath=function(d,n,x,q,y){n=y/4;d.moveTo(0,0);d.lineTo(n,y/2);d.lineTo(0,y);d.lineTo(q,y);d.lineTo(q,0);d.close();d.end()};pb.prototype.getLabelBounds=function(d){var n=d.height/4;return new mxRectangle(d.x+n,d.y,d.width-n,d.height)};mxCellRenderer.registerShape(\"mermaidOdd\",\npb);mxUtils.extend(kb,mxActor);kb.prototype.isRoundable=function(){return!1};kb.prototype.paintVertexShape=function(d,n,x,q,y){d.setLineJoin&&d.setLineJoin(\"round\");mxActor.prototype.paintVertexShape.apply(this,arguments)};kb.prototype.redrawPath=function(d,n,x,q,y){var C={};mxUtils.getValue(this.style,\"dirs\",\"right\").split(/[,| ]+/).forEach(function(G){G=G.trim().toLowerCase();\"x\"===G?(C.right=!0,C.left=!0):\"y\"===G?(C.up=!0,C.down=!0):G&&(C[G]=!0)});x=parseFloat(mxUtils.getValue(this.style,\"nodePadding\",\n\"8\"));n=y/2;x/=2;q=C.right&&C.left&&C.up&&C.down?[[0,0],[n,0],[q/2,2*x],[q-n,0],[q,0],[q,-y/3],[q+2*x,-y/2],[q,-2*y/3],[q,-y],[q-n,-y],[q/2,-y-2*x],[n,-y],[0,-y],[0,-2*y/3],[-2*x,-y/2],[0,-y/3]]:C.right&&C.left&&C.up?[[n,0],[q-n,0],[q,-y/2],[q-n,-y],[n,-y],[0,-y/2]]:C.right&&C.left&&C.down?[[0,0],[n,-y],[q-n,-y],[q,0]]:C.right&&C.up&&C.down?[[0,0],[q,-n],[q,-y+n],[0,-y]]:C.left&&C.up&&C.down?[[q,0],[0,-n],[0,-y+n],[q,-y]]:C.right&&C.left?[[n,0],[n,-x],[q-n,-x],[q-n,0],[q,-y/2],[q-n,-y],[q-n,-y+x],\n[n,-y+x],[n,-y],[0,-y/2]]:C.up&&C.down?[[q/2,0],[0,-x],[n,-x],[n,-y+x],[0,-y+x],[q/2,-y],[q,-y+x],[q-n,-y+x],[q-n,-x],[q,-x]]:C.right&&C.up?[[0,0],[q,-n],[0,-y]]:C.right&&C.down?[[0,0],[q,0],[0,-y]]:C.left&&C.up?[[q,0],[0,-n],[q,-y]]:C.left&&C.down?[[q,0],[0,0],[q,-y]]:C.right?[[n,-x],[q-n,-x],[q-n,0],[q,-y/2],[q-n,-y],[q-n,-y+x],[n,-y+x]]:C.left?[[n,0],[n,-x],[q-n,-x],[q-n,-y+x],[n,-y+x],[n,-y],[0,-y/2]]:C.up?[[n,-x],[n,-y+x],[0,-y+x],[q/2,-y],[q,-y+x],[q-n,-y+x],[q-n,-x]]:C.down?[[q/2,0],[0,-x],\n[n,-x],[n,-y+x],[q-n,-y+x],[q-n,-x],[q,-x]]:[[0,0],[q,0],[q,-y],[0,-y]];d.moveTo(q[0][0],y+q[0][1]);for(n=1;n=2*q&&d.push(new mxConnectionConstraint(new mxPoint(.5,0),!1));return d};mxRectangleShape.prototype.constraints=[new mxConnectionConstraint(new mxPoint(0,0),!0),new mxConnectionConstraint(new mxPoint(.25,0),!0),new mxConnectionConstraint(new mxPoint(.5,0),!0),new mxConnectionConstraint(new mxPoint(.75,\n0),!0),new mxConnectionConstraint(new mxPoint(1,0),!0),new mxConnectionConstraint(new mxPoint(0,.25),!0),new mxConnectionConstraint(new mxPoint(0,.5),!0),new mxConnectionConstraint(new mxPoint(0,.75),!0),new mxConnectionConstraint(new mxPoint(1,.25),!0),new mxConnectionConstraint(new mxPoint(1,.5),!0),new mxConnectionConstraint(new mxPoint(1,.75),!0),new mxConnectionConstraint(new mxPoint(0,1),!0),new mxConnectionConstraint(new mxPoint(.25,1),!0),new mxConnectionConstraint(new mxPoint(.5,1),!0),new mxConnectionConstraint(new mxPoint(.75,\n1),!0),new mxConnectionConstraint(new mxPoint(1,1),!0)];mxEllipse.prototype.constraints=[new mxConnectionConstraint(new mxPoint(0,0),!0),new mxConnectionConstraint(new mxPoint(1,0),!0),new mxConnectionConstraint(new mxPoint(0,1),!0),new mxConnectionConstraint(new mxPoint(1,1),!0),new mxConnectionConstraint(new mxPoint(.5,0),!0),new mxConnectionConstraint(new mxPoint(.5,1),!0),new mxConnectionConstraint(new mxPoint(0,.5),!0),new mxConnectionConstraint(new mxPoint(1,.5))];da.prototype.constraints=mxRectangleShape.prototype.constraints;\nmxImageShape.prototype.constraints=mxRectangleShape.prototype.constraints;mxSwimlane.prototype.constraints=mxRectangleShape.prototype.constraints;z.prototype.constraints=mxRectangleShape.prototype.constraints;mxLabel.prototype.constraints=mxRectangleShape.prototype.constraints;ob.prototype.constraints=mxRectangleShape.prototype.constraints;Xa.prototype.constraints=mxEllipse.prototype.constraints;pb.prototype.constraints=mxEllipse.prototype.constraints;E.prototype.constraints=mxEllipse.prototype.constraints;\nF.prototype.getConstraints=function(d,n,x){d=[];var q=Math.max(0,Math.min(n,Math.min(x,parseFloat(mxUtils.getValue(this.style,\"size\",this.size)))));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*(n-q),0));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n-q,0));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n-.5*q,.5*q));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n,q));d.push(new mxConnectionConstraint(new mxPoint(0,\n0),!1,null,n,.5*(x+q)));d.push(new mxConnectionConstraint(new mxPoint(1,1),!1));d.push(new mxConnectionConstraint(new mxPoint(.5,1),!1));d.push(new mxConnectionConstraint(new mxPoint(0,1),!1));d.push(new mxConnectionConstraint(new mxPoint(0,.5),!1));n>=2*q&&d.push(new mxConnectionConstraint(new mxPoint(.5,0),!1));return d};H.prototype.getConstraints=function(d,n,x){d=[];var q=Math.max(0,Math.min(n,Math.min(x,parseFloat(mxUtils.getValue(this.style,\"size\",this.size)))));d.push(new mxConnectionConstraint(new mxPoint(1,\n0),!1));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*(n+q),0));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,q,0));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*q,.5*q));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,0,q));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,0,.5*(x+q)));d.push(new mxConnectionConstraint(new mxPoint(0,1),!1));d.push(new mxConnectionConstraint(new mxPoint(.5,1),!1));d.push(new mxConnectionConstraint(new mxPoint(1,\n1),!1));d.push(new mxConnectionConstraint(new mxPoint(1,.5),!1));n>=2*q&&d.push(new mxConnectionConstraint(new mxPoint(.5,0),!1));return d};p.prototype.getConstraints=function(d,n,x){d=[];var q=Math.max(0,Math.min(n,Math.min(x,parseFloat(mxUtils.getValue(this.style,\"size\",this.size)))));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*(n-q),0));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n-q,0));d.push(new mxConnectionConstraint(new mxPoint(0,\n0),!1,null,n-.5*q,.5*q));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n,q));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n,.5*(x+q)));d.push(new mxConnectionConstraint(new mxPoint(1,1),!1));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*(n+q),x));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,q,x));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*q,x-.5*q));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,0,x-q));d.push(new mxConnectionConstraint(new mxPoint(0,\n0),!1,null,0,.5*(x-q)));return d};g.prototype.getConstraints=function(d,n,x){d=[];n=Math.max(0,Math.min(x,parseFloat(mxUtils.getValue(this.style,\"size\",this.size))));d.push(new mxConnectionConstraint(new mxPoint(.5,0),!1));d.push(new mxConnectionConstraint(new mxPoint(0,.5),!1));d.push(new mxConnectionConstraint(new mxPoint(.5,1),!1));d.push(new mxConnectionConstraint(new mxPoint(1,.5),!1));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,0,n));d.push(new mxConnectionConstraint(new mxPoint(1,\n0),!1,null,0,n));d.push(new mxConnectionConstraint(new mxPoint(1,1),!1,null,0,-n));d.push(new mxConnectionConstraint(new mxPoint(0,1),!1,null,0,-n));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,0,n+.5*(.5*x-n)));d.push(new mxConnectionConstraint(new mxPoint(1,0),!1,null,0,n+.5*(.5*x-n)));d.push(new mxConnectionConstraint(new mxPoint(1,0),!1,null,0,x-n-.5*(.5*x-n)));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,0,x-n-.5*(.5*x-n)));d.push(new mxConnectionConstraint(new mxPoint(.145,\n0),!1,null,0,.29*n));d.push(new mxConnectionConstraint(new mxPoint(.855,0),!1,null,0,.29*n));d.push(new mxConnectionConstraint(new mxPoint(.855,1),!1,null,0,.29*-n));d.push(new mxConnectionConstraint(new mxPoint(.145,1),!1,null,0,.29*-n));return d};t.prototype.getConstraints=function(d,n,x){d=[];var q=Math.max(0,Math.min(n,parseFloat(mxUtils.getValue(this.style,\"tabWidth\",this.tabWidth)))),y=Math.max(0,Math.min(x,parseFloat(mxUtils.getValue(this.style,\"tabHeight\",this.tabHeight))));\"left\"==mxUtils.getValue(this.style,\n\"tabPosition\",this.tabPosition)?(d.push(new mxConnectionConstraint(new mxPoint(0,0),!1)),d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*q,0)),d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,q,0)),d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,q,y)),d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*(n+q),y))):(d.push(new mxConnectionConstraint(new mxPoint(1,0),!1)),d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n-.5*q,0)),d.push(new mxConnectionConstraint(new mxPoint(0,\n0),!1,null,n-q,0)),d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n-q,y)),d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*(n-q),y)));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n,y));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n,.25*(x-y)+y));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n,.5*(x-y)+y));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n,.75*(x-y)+y));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,\nnull,n,x));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,0,y));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,0,.25*(x-y)+y));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,0,.5*(x-y)+y));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,0,.75*(x-y)+y));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,0,x));d.push(new mxConnectionConstraint(new mxPoint(.25,1),!1));d.push(new mxConnectionConstraint(new mxPoint(.5,1),!1));d.push(new mxConnectionConstraint(new mxPoint(.75,\n1),!1));return d};Ba.prototype.constraints=mxRectangleShape.prototype.constraints;Ga.prototype.constraints=mxRectangleShape.prototype.constraints;bb.prototype.constraints=mxEllipse.prototype.constraints;$a.prototype.constraints=mxEllipse.prototype.constraints;A.prototype.constraints=mxEllipse.prototype.constraints;ma.prototype.constraints=mxEllipse.prototype.constraints;La.prototype.constraints=mxRectangleShape.prototype.constraints;qa.prototype.constraints=mxRectangleShape.prototype.constraints;\nva.prototype.getConstraints=function(d,n,x){d=[];var q=Math.min(n,x/2),y=Math.min(n-q,Math.max(0,parseFloat(mxUtils.getValue(this.style,\"size\",this.size)))*n);d.push(new mxConnectionConstraint(new mxPoint(0,.5),!1,null));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,y,0));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*(y+n-q),0));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n-q,0));d.push(new mxConnectionConstraint(new mxPoint(1,.5),!1,null));d.push(new mxConnectionConstraint(new mxPoint(0,\n0),!1,null,n-q,x));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*(y+n-q),x));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,y,x));return d};Ia.prototype.getConstraints=function(d,n,x){n=parseFloat(mxUtils.getValue(d,\"jettyWidth\",Ia.prototype.jettyWidth))/2;d=parseFloat(mxUtils.getValue(d,\"jettyHeight\",Ia.prototype.jettyHeight));var q=[new mxConnectionConstraint(new mxPoint(0,0),!1,null,n),new mxConnectionConstraint(new mxPoint(.25,0),!0),new mxConnectionConstraint(new mxPoint(.5,\n0),!0),new mxConnectionConstraint(new mxPoint(.75,0),!0),new mxConnectionConstraint(new mxPoint(1,0),!0),new mxConnectionConstraint(new mxPoint(1,.25),!0),new mxConnectionConstraint(new mxPoint(1,.5),!0),new mxConnectionConstraint(new mxPoint(1,.75),!0),new mxConnectionConstraint(new mxPoint(0,1),!1,null,n),new mxConnectionConstraint(new mxPoint(.25,1),!0),new mxConnectionConstraint(new mxPoint(.5,1),!0),new mxConnectionConstraint(new mxPoint(.75,1),!0),new mxConnectionConstraint(new mxPoint(1,1),\n!0),new mxConnectionConstraint(new mxPoint(0,0),!1,null,0,Math.min(x-.5*d,1.5*d)),new mxConnectionConstraint(new mxPoint(0,0),!1,null,0,Math.min(x-.5*d,3.5*d))];x>5*d&&q.push(new mxConnectionConstraint(new mxPoint(0,.75),!1,null,n));x>8*d&&q.push(new mxConnectionConstraint(new mxPoint(0,.5),!1,null,n));x>15*d&&q.push(new mxConnectionConstraint(new mxPoint(0,.25),!1,null,n));return q};Aa.prototype.constraints=mxRectangleShape.prototype.constraints;Ma.prototype.constraints=mxRectangleShape.prototype.constraints;\nmxCylinder.prototype.constraints=[new mxConnectionConstraint(new mxPoint(.15,.05),!1),new mxConnectionConstraint(new mxPoint(.5,0),!0),new mxConnectionConstraint(new mxPoint(.85,.05),!1),new mxConnectionConstraint(new mxPoint(0,.3),!0),new mxConnectionConstraint(new mxPoint(0,.5),!0),new mxConnectionConstraint(new mxPoint(0,.7),!0),new mxConnectionConstraint(new mxPoint(1,.3),!0),new mxConnectionConstraint(new mxPoint(1,.5),!0),new mxConnectionConstraint(new mxPoint(1,.7),!0),new mxConnectionConstraint(new mxPoint(.15,\n.95),!1),new mxConnectionConstraint(new mxPoint(.5,1),!0),new mxConnectionConstraint(new mxPoint(.85,.95),!1)];S.prototype.constraints=[new mxConnectionConstraint(new mxPoint(.25,.1),!1),new mxConnectionConstraint(new mxPoint(.5,0),!1),new mxConnectionConstraint(new mxPoint(.75,.1),!1),new mxConnectionConstraint(new mxPoint(0,1/3),!1),new mxConnectionConstraint(new mxPoint(0,1),!1),new mxConnectionConstraint(new mxPoint(1,1/3),!1),new mxConnectionConstraint(new mxPoint(1,1),!1),new mxConnectionConstraint(new mxPoint(.5,\n.5),!1)];ya.prototype.constraints=[new mxConnectionConstraint(new mxPoint(.25,0),!0),new mxConnectionConstraint(new mxPoint(.5,0),!0),new mxConnectionConstraint(new mxPoint(.75,0),!0),new mxConnectionConstraint(new mxPoint(0,.3),!0),new mxConnectionConstraint(new mxPoint(0,.7),!0),new mxConnectionConstraint(new mxPoint(1,.25),!0),new mxConnectionConstraint(new mxPoint(1,.5),!0),new mxConnectionConstraint(new mxPoint(1,.75),!0),new mxConnectionConstraint(new mxPoint(.25,1),!0),new mxConnectionConstraint(new mxPoint(.5,\n1),!0),new mxConnectionConstraint(new mxPoint(.75,1),!0)];mxActor.prototype.constraints=[new mxConnectionConstraint(new mxPoint(.5,0),!0),new mxConnectionConstraint(new mxPoint(.25,.2),!1),new mxConnectionConstraint(new mxPoint(.1,.5),!1),new mxConnectionConstraint(new mxPoint(0,.75),!0),new mxConnectionConstraint(new mxPoint(.75,.25),!1),new mxConnectionConstraint(new mxPoint(.9,.5),!1),new mxConnectionConstraint(new mxPoint(1,.75),!0),new mxConnectionConstraint(new mxPoint(.25,1),!0),new mxConnectionConstraint(new mxPoint(.5,\n1),!0),new mxConnectionConstraint(new mxPoint(.75,1),!0)];l.prototype.constraints=[new mxConnectionConstraint(new mxPoint(0,0),!1),new mxConnectionConstraint(new mxPoint(.5,.25),!1),new mxConnectionConstraint(new mxPoint(1,0),!1),new mxConnectionConstraint(new mxPoint(.25,.5),!1),new mxConnectionConstraint(new mxPoint(.75,.5),!1),new mxConnectionConstraint(new mxPoint(0,1),!1),new mxConnectionConstraint(new mxPoint(.5,.75),!1),new mxConnectionConstraint(new mxPoint(1,1),!1)];K.prototype.constraints=\n[new mxConnectionConstraint(new mxPoint(0,.35),!1),new mxConnectionConstraint(new mxPoint(0,.5),!1),new mxConnectionConstraint(new mxPoint(0,.65),!1),new mxConnectionConstraint(new mxPoint(1,.35),!1),new mxConnectionConstraint(new mxPoint(1,.5),!1),new mxConnectionConstraint(new mxPoint(1,.65),!1),new mxConnectionConstraint(new mxPoint(.25,1),!1),new mxConnectionConstraint(new mxPoint(.75,0),!1)];ba.prototype.constraints=[new mxConnectionConstraint(new mxPoint(.25,0),!0),new mxConnectionConstraint(new mxPoint(.5,\n0),!0),new mxConnectionConstraint(new mxPoint(.75,0),!0),new mxConnectionConstraint(new mxPoint(.25,1),!0),new mxConnectionConstraint(new mxPoint(.5,1),!0),new mxConnectionConstraint(new mxPoint(.75,1),!0),new mxConnectionConstraint(new mxPoint(0,.25),!0),new mxConnectionConstraint(new mxPoint(0,.5),!0),new mxConnectionConstraint(new mxPoint(0,.75),!0),new mxConnectionConstraint(new mxPoint(1,.25),!0),new mxConnectionConstraint(new mxPoint(1,.5),!0),new mxConnectionConstraint(new mxPoint(1,.75),!0)];\nmxLine.prototype.constraints=[new mxConnectionConstraint(new mxPoint(0,.5),!1),new mxConnectionConstraint(new mxPoint(.25,.5),!1),new mxConnectionConstraint(new mxPoint(.75,.5),!1),new mxConnectionConstraint(new mxPoint(1,.5),!1)];oa.prototype.constraints=[new mxConnectionConstraint(new mxPoint(.5,0),!1),new mxConnectionConstraint(new mxPoint(.5,1),!1)];mxDoubleEllipse.prototype.constraints=mxEllipse.prototype.constraints;mxRhombus.prototype.constraints=mxEllipse.prototype.constraints;mxTriangle.prototype.constraints=\n[new mxConnectionConstraint(new mxPoint(0,.25),!0),new mxConnectionConstraint(new mxPoint(0,.5),!0),new mxConnectionConstraint(new mxPoint(0,.75),!0),new mxConnectionConstraint(new mxPoint(.5,0),!0),new mxConnectionConstraint(new mxPoint(.5,1),!0),new mxConnectionConstraint(new mxPoint(1,.5),!0)];mxHexagon.prototype.constraints=[new mxConnectionConstraint(new mxPoint(.375,0),!0),new mxConnectionConstraint(new mxPoint(.5,0),!0),new mxConnectionConstraint(new mxPoint(.625,0),!0),new mxConnectionConstraint(new mxPoint(0,\n.25),!0),new mxConnectionConstraint(new mxPoint(0,.5),!0),new mxConnectionConstraint(new mxPoint(0,.75),!0),new mxConnectionConstraint(new mxPoint(1,.25),!0),new mxConnectionConstraint(new mxPoint(1,.5),!0),new mxConnectionConstraint(new mxPoint(1,.75),!0),new mxConnectionConstraint(new mxPoint(.375,1),!0),new mxConnectionConstraint(new mxPoint(.5,1),!0),new mxConnectionConstraint(new mxPoint(.625,1),!0)];mxCloud.prototype.constraints=[new mxConnectionConstraint(new mxPoint(.25,.25),!1),new mxConnectionConstraint(new mxPoint(.4,\n.1),!1),new mxConnectionConstraint(new mxPoint(.16,.55),!1),new mxConnectionConstraint(new mxPoint(.07,.4),!1),new mxConnectionConstraint(new mxPoint(.31,.8),!1),new mxConnectionConstraint(new mxPoint(.13,.77),!1),new mxConnectionConstraint(new mxPoint(.8,.8),!1),new mxConnectionConstraint(new mxPoint(.55,.95),!1),new mxConnectionConstraint(new mxPoint(.875,.5),!1),new mxConnectionConstraint(new mxPoint(.96,.7),!1),new mxConnectionConstraint(new mxPoint(.625,.2),!1),new mxConnectionConstraint(new mxPoint(.88,\n.25),!1)];W.prototype.constraints=mxRectangleShape.prototype.constraints;L.prototype.constraints=mxRectangleShape.prototype.constraints;O.prototype.constraints=[new mxConnectionConstraint(new mxPoint(.25,0),!0),new mxConnectionConstraint(new mxPoint(.5,0),!0),new mxConnectionConstraint(new mxPoint(.75,0),!0),new mxConnectionConstraint(new mxPoint(0,.25),!0),new mxConnectionConstraint(new mxPoint(0,.5),!0),new mxConnectionConstraint(new mxPoint(0,.75),!0),new mxConnectionConstraint(new mxPoint(1,.25),\n!0),new mxConnectionConstraint(new mxPoint(1,.5),!0),new mxConnectionConstraint(new mxPoint(1,.75),!0)];mxArrow.prototype.constraints=null;db.prototype.getConstraints=function(d,n,x){d=[];var q=Math.max(0,Math.min(n,parseFloat(mxUtils.getValue(this.style,\"dx\",this.dx)))),y=Math.max(0,Math.min(x,parseFloat(mxUtils.getValue(this.style,\"dy\",this.dy))));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1));d.push(new mxConnectionConstraint(new mxPoint(.5,0),!1));d.push(new mxConnectionConstraint(new mxPoint(1,\n0),!1));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n,.5*y));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n,y));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.75*n+.25*q,y));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*(n+q),y));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*(n+q),.5*(x+y)));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*(n+q),x));d.push(new mxConnectionConstraint(new mxPoint(.5,1),!1));d.push(new mxConnectionConstraint(new mxPoint(0,\n0),!1,null,.5*(n-q),x));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*(n-q),.5*(x+y)));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*(n-q),y));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.25*n-.25*q,y));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,0,y));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,0,.5*y));return d};Qa.prototype.getConstraints=function(d,n,x){d=[];var q=Math.max(0,Math.min(n,parseFloat(mxUtils.getValue(this.style,\n\"dx\",this.dx)))),y=Math.max(0,Math.min(x,parseFloat(mxUtils.getValue(this.style,\"dy\",this.dy))));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1));d.push(new mxConnectionConstraint(new mxPoint(.5,0),!1));d.push(new mxConnectionConstraint(new mxPoint(1,0),!1));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n,.5*y));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n,y));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*(n+q),y));d.push(new mxConnectionConstraint(new mxPoint(0,\n0),!1,null,q,y));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,q,.5*(x+y)));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,q,x));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*q,x));d.push(new mxConnectionConstraint(new mxPoint(0,.5),!1));d.push(new mxConnectionConstraint(new mxPoint(0,1),!1));return d};Na.prototype.constraints=[new mxConnectionConstraint(new mxPoint(0,0),!1),new mxConnectionConstraint(new mxPoint(0,.5),!1),new mxConnectionConstraint(new mxPoint(0,\n1),!1),new mxConnectionConstraint(new mxPoint(.25,.5),!1),new mxConnectionConstraint(new mxPoint(.5,.5),!1),new mxConnectionConstraint(new mxPoint(.75,.5),!1),new mxConnectionConstraint(new mxPoint(1,0),!1),new mxConnectionConstraint(new mxPoint(1,.5),!1),new mxConnectionConstraint(new mxPoint(1,1),!1)];Oa.prototype.getConstraints=function(d,n,x){d=[];var q=x*Math.max(0,Math.min(1,parseFloat(mxUtils.getValue(this.style,\"arrowWidth\",this.arrowWidth)))),y=n*Math.max(0,Math.min(1,parseFloat(mxUtils.getValue(this.style,\n\"arrowSize\",this.arrowSize))));q=(x-q)/2;d.push(new mxConnectionConstraint(new mxPoint(0,.5),!1));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,0,q));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*(n-y),q));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n-y,0));d.push(new mxConnectionConstraint(new mxPoint(1,.5),!1));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n-y,x));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*(n-y),x-q));d.push(new mxConnectionConstraint(new mxPoint(0,\n0),!1,null,0,x-q));return d};Ca.prototype.getConstraints=function(d,n,x){d=[];var q=x*Math.max(0,Math.min(1,parseFloat(mxUtils.getValue(this.style,\"arrowWidth\",Oa.prototype.arrowWidth)))),y=n*Math.max(0,Math.min(1,parseFloat(mxUtils.getValue(this.style,\"arrowSize\",Oa.prototype.arrowSize))));q=(x-q)/2;d.push(new mxConnectionConstraint(new mxPoint(0,.5),!1));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,y,0));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*n,q));d.push(new mxConnectionConstraint(new mxPoint(0,\n0),!1,null,n-y,0));d.push(new mxConnectionConstraint(new mxPoint(1,.5),!1));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n-y,x));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*n,x-q));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,y,x));return d};wa.prototype.getConstraints=function(d,n,x){d=[];var q=Math.min(x,n),y=Math.max(0,Math.min(q,q*parseFloat(mxUtils.getValue(this.style,\"size\",this.size))));q=(x-y)/2;var C=q+y,G=(n-y)/2;y=G+y;d.push(new mxConnectionConstraint(new mxPoint(0,\n0),!1,null,G,.5*q));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,G,0));d.push(new mxConnectionConstraint(new mxPoint(.5,0),!1));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,y,0));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,y,.5*q));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,y,q));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,G,x-.5*q));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,G,x));d.push(new mxConnectionConstraint(new mxPoint(.5,\n1),!1));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,y,x));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,y,x-.5*q));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,y,C));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*(n+y),q));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n,q));d.push(new mxConnectionConstraint(new mxPoint(1,.5),!1));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,n,C));d.push(new mxConnectionConstraint(new mxPoint(0,\n0),!1,null,.5*(n+y),C));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,G,C));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*G,q));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,0,q));d.push(new mxConnectionConstraint(new mxPoint(0,.5),!1));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,0,C));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,.5*G,C));d.push(new mxConnectionConstraint(new mxPoint(0,0),!1,null,G,q));return d};ja.prototype.constraints=\nnull;Ya.prototype.constraints=[new mxConnectionConstraint(new mxPoint(0,.25),!1),new mxConnectionConstraint(new mxPoint(0,.5),!1),new mxConnectionConstraint(new mxPoint(0,.75),!1),new mxConnectionConstraint(new mxPoint(1,.5),!1),new mxConnectionConstraint(new mxPoint(.7,.1),!1),new mxConnectionConstraint(new mxPoint(.7,.9),!1)];sa.prototype.constraints=[new mxConnectionConstraint(new mxPoint(.175,.25),!1),new mxConnectionConstraint(new mxPoint(.25,.5),!1),new mxConnectionConstraint(new mxPoint(.175,\n.75),!1),new mxConnectionConstraint(new mxPoint(1,.5),!1),new mxConnectionConstraint(new mxPoint(.7,.1),!1),new mxConnectionConstraint(new mxPoint(.7,.9),!1)];ta.prototype.constraints=[new mxConnectionConstraint(new mxPoint(0,.5),!1),new mxConnectionConstraint(new mxPoint(1,.5),!1)];za.prototype.constraints=[new mxConnectionConstraint(new mxPoint(0,.5),!1),new mxConnectionConstraint(new mxPoint(1,.5),!1)]})();function Actions(b){this.editorUi=b;this.actions={};this.init()}\nActions.prototype.init=function(){function b(F){u.escape();F=u.deleteCells(u.getDeletableCells(u.getSelectionCells()),F);null!=F&&u.setSelectionCells(F)}function f(){if(!u.isSelectionEmpty()){u.getModel().beginUpdate();try{for(var F=u.getSelectionCells(),Q=0;QMath.abs(F-u.view.scale)&&5>Math.abs(Q-u.container.scrollLeft)&&5>Math.abs(N-u.container.scrollTop)&&c==u.view.translate.x&&g==u.view.translate.y&&p.actions.get(\"fitWindow\").funct()}},null,null,\"Enter\");I.getTitle=function(){return mxResources.get(\"fitWindow\")+\n\" / \"+mxResources.get(\"resetView\")};this.put(\"smartFit\",I);this.addAction(\"keyPressEnter\",function(){u.isSelectionEmpty()?p.actions.get(\"smartFit\").funct():u.isEnabled()&&u.startEditingAtCell()});this.addAction(\"import...\",function(){window.openNew=!1;window.openKey=\"import\";window.openFile=new OpenFile(mxUtils.bind(this,function(){p.hideDialog()}));window.openFile.setConsumer(mxUtils.bind(this,function(F,Q){try{var N=mxUtils.parseXml(F);v.graph.setSelectionCells(v.graph.importGraphModel(N.documentElement))}catch(c){mxUtils.alert(mxResources.get(\"invalidOrMissingFile\")+\n\": \"+c.message)}}));p.showDialog((new OpenDialog(this)).container,320,220,!0,!0,function(){window.openFile=null})}).isEnabled=D;this.addAction(\"save\",function(){p.saveFile(!1)},null,null,Editor.ctrlKey+\"+S\").isEnabled=D;this.addAction(\"saveAs...\",function(){p.saveFile(!0)},null,null,Editor.ctrlKey+\"+\"+Editor.shiftKey+\"+S\");this.addAction(\"export...\",function(){p.showDialog((new ExportDialog(p)).container,360,396,!0,!0)});this.addAction(\"editDiagram...\",function(){var F=new EditDiagramDialog(p);p.showDialog(F.container,\n620,420,!0,!1,null,null,null,new mxRectangle(0,0,440,280));F.init()}).isEnabled=D;this.addAction(\"pageSetup...\",function(){p.showDialog((new PageSetupDialog(p)).container,420,null,!0,!0)}).isEnabled=D;this.addAction(\"print...\",function(){p.showPrintDialog()},null,\"\",Editor.ctrlKey+\"+P\");this.addAction(\"preview\",function(){mxUtils.show(u,null,10,10)});this.addAction(\"undo\",function(){p.undo()},null,\"\",Editor.ctrlKey+\"+Z\");this.addAction(\"redo\",function(){p.redo()},null,\"\",mxClient.IS_WIN?Editor.ctrlKey+\n\"+Y\":Editor.ctrlKey+\"+\"+Editor.shiftKey+\"+Z\");this.addAction(\"cut\",function(){var F=null;try{F=p.copyXml(),null!=F&&u.removeCells(F,!1)}catch(Q){}try{null==F&&mxClipboard.cut(u)}catch(Q){p.handleError(Q)}},null,\"\",Editor.ctrlKey+\"+X\");this.addAction(\"copy\",function(){try{p.copyXml()}catch(F){}try{mxClipboard.copy(u)}catch(F){p.handleError(F)}},null,\"\",Editor.ctrlKey+\"+C\");this.addAction(\"paste\",function(){p.pasteFromClipboard()},!1,\"\",Editor.ctrlKey+\"+V\");this.addAction(\"pasteHere\",function(F){p.pasteFromClipboard(u.popupMenuHandler.getTriggerPoint())});\nthis.addAction(\"swap\",function(){var F=u.getSelectionCells();2==F.length&&u.swapShapes(F[0],F[1])});this.addAction(\"copySize\",function(){var F=u.getSelectionCell();u.isEnabled()&&null!=F&&u.getModel().isVertex(F)&&(F=u.getCellGeometry(F),null!=F&&(p.copiedSize=new mxRectangle(F.x,F.y,F.width,F.height)))},null,null,Editor.altKey+\"+\"+Editor.shiftKey+\"+F\");this.addAction(\"pasteSize\",function(){if(u.isEnabled()&&!u.isSelectionEmpty()&&null!=p.copiedSize){u.getModel().beginUpdate();try{for(var F=u.getResizableCells(u.getSelectionCells()),\nQ=0;Q\"));u.cellLabelChanged(c.cell,Graph.sanitizeHtml(l));u.setCellStyles(\"html\",F,[Q[N]])}}}p.fireEvent(new mxEventObject(\"styleChanged\",\"keys\",[\"html\"],\"values\",[null!=F?F:\"0\"],\"cells\",Q))}finally{u.getModel().endUpdate()}});\nthis.addAction(\"wordWrap\",function(){var F=u.getView().getState(u.getSelectionCell()),Q=\"wrap\";u.stopEditing();null!=F&&\"wrap\"==F.style[mxConstants.STYLE_WHITE_SPACE]&&(Q=null);u.setCellStyles(mxConstants.STYLE_WHITE_SPACE,Q)});this.addAction(\"rotation\",function(){var F=\"0\",Q=u.getView().getState(u.getSelectionCell());null!=Q&&(F=Q.style[mxConstants.STYLE_ROTATION]||F);F=new FilenameDialog(p,F,mxResources.get(\"apply\"),function(N){null!=N&&0m?b=b.substring(0,m)+\"[...]\":null!=b&&b.length>f&&(b=Graph.compress(b)+\"\\n\");return b};\nDrawioFile.prototype.checksumError=function(b,f,m,p,v,u,D,I){this.stats.checksumErrors++;this.invalidChecksum=this.inConflictState=!0;this.descriptorChanged();null!=this.sync&&this.sync.updateOnlineState();null!=b&&b();try{var V=this.getCurrentUser(),F=null!=V?V.id:\"unknown\",Q=\"\"!=this.getId()?this.getId():\"(\"+this.ui.hashValue(this.getTitle())+\")\",N=JSON.stringify(f).length,c=null;if(null!=f&&1E3>N){b=[];for(m=0;m\nc.length?Graph.compress(c):null}this.getLatestVersion(mxUtils.bind(this,function(g){try{var l=null!=c?\"report\":\"error\",t=this.ui.getHashValueForPages(g.getShadowPages()),B=\"unknown\",E=\"unknown\",H=\"unknown\";try{var K=null!=g.initialData&&0E?this.ui.insertPage(t[B],Math.min(B,this.ui.pages.length)):this.ui.movePage(E,B)}for(B=0;BmxUtils.indexOf(t,g[B])&&this.ui.removePage(g[B]);0<=mxUtils.indexOf(this.ui.pages,l)&&this.ui.selectPage(l,!0)}else this.ui.pages=\nthis.ui.applyPatches(this.ui.pages,b,!0,f,this.isModified());0==this.ui.pages.length&&this.ui.pages.push(this.ui.createPage());0>mxUtils.indexOf(this.ui.pages,this.ui.currentPage)&&this.ui.selectPage(this.ui.pages[0],!0);I.checkDefaultParent()}finally{I.container.style.visibility=\"\",I.model.endUpdate(),I.cellRenderer.redraw=c,this.changeListenerEnabled=V,m||(v.history=u,v.indexOfNextAdd=D,v.fireEvent(new mxEventObject(mxEvent.CLEAR))),v=N!=(null!=this.ui.fileNode?this.ui.fileNode.getAttribute(\"vars\"):\nnull),(u=null==this.ui.currentPage||this.ui.currentPage.needsUpdate)&&Q!=I.mathEnabled?(this.ui.editor.updateGraphComponents(),I.refresh()):v?I.refresh():u&&(F!=I.foldingEnabled?I.view.revalidate():I.view.validate(),I.sizeDidChange()),null!=this.sync&&this.isRealtime()&&!p&&(this.sync.snapshot=this.ui.clonePages(this.ui.pages),this.sync.snapshotVars=null!=this.ui.fileNode?this.ui.fileNode.getAttribute(\"vars\"):null),this.ui.editor.fireEvent(new mxEventObject(\"pagesPatched\",\"patches\",b))}EditorUi.debug(\"DrawioFile.patch\",\n[this],\"patches\",b,\"resolver\",f,\"undoable\",m)}return b};\nDrawioFile.prototype.save=function(b,f,m,p,v,u){try{if(EditorUi.debug(\"DrawioFile.save\",[this],\"revision\",b,\"unloading\",p,\"overwrite\",v,\"manual\",u,\"saving\",this.savingFile,\"editable\",this.isEditable(),\"invalidChecksum\",this.invalidChecksum),this.isEditable())if(!v&&this.invalidChecksum)if(null!=m)m({message:mxResources.get(\"checksum\")});else throw Error(mxResources.get(\"checksum\"));else this.updateFileData(),this.clearAutosave(),null!=f&&f();else if(null!=m)m({message:mxResources.get(\"readOnly\")});\nelse throw Error(mxResources.get(\"readOnly\"));}catch(D){if(null!=m)m(D);else throw D;}};DrawioFile.prototype.createData=function(){var b=this.ui.pages;if(this.isRealtime()&&(this.ui.pages=this.ownPages,null!=this.ui.currentPage)){var f=this.ui.getPageById(this.ui.currentPage.getId(),this.ownPages);null!=f&&(f.viewState=this.ui.editor.graph.getViewState(),f.needsUpdate=!0)}f=this.ui.getFileData(null,null,null,null,null,null,null,null,this,!this.isCompressed());this.ui.pages=b;return f};\nDrawioFile.prototype.updateFileData=function(){null!=this.sync&&this.sync.sendLocalChanges();this.setData(this.createData());null!=this.sync&&this.sync.fileDataUpdated()};DrawioFile.prototype.isCompressedStorage=function(){return Editor.defaultCompressed};DrawioFile.prototype.isCompressed=function(){var b=null!=this.ui.fileNode?this.ui.fileNode.getAttribute(\"compressed\"):null;return null!=b?\"false\"!=b:this.isCompressedStorage()&&Editor.compressXml};\nDrawioFile.prototype.setLocked=function(b){this.ui.fileNode.setAttribute(\"locked\",b?\"true\":\"false\");this.ui.fireEvent(new mxEventObject(\"lockedChanged\"));this.fileChanged();b&&this.ui.editor.graph.clearSelection()};DrawioFile.prototype.isLocked=function(){var b=null!=this.ui.fileNode?this.ui.fileNode.getAttribute(\"locked\"):null;return null!=b?\"true\"==b:!1};DrawioFile.prototype.saveAs=function(b,f,m){};DrawioFile.prototype.saveFile=function(b,f,m,p){};DrawioFile.prototype.getFileUrl=function(){return null};\nDrawioFile.prototype.getFolderUrl=function(b){return null};DrawioFile.prototype.getPublicUrl=function(b){b(null)};DrawioFile.prototype.isRestricted=function(){return DrawioFile.RESTRICT_EXPORT};DrawioFile.prototype.isModified=function(){return this.modified};DrawioFile.prototype.getShadowModified=function(){return this.shadowModified};DrawioFile.prototype.setShadowModified=function(b){this.shadowModified=b};DrawioFile.prototype.setModified=function(b){this.shadowModified=this.modified=b};\nDrawioFile.prototype.isAutosaveOptional=function(){return!1};DrawioFile.prototype.isAutosave=function(){return!this.inConflictState&&this.ui.editor.autosave};DrawioFile.prototype.isRenamable=function(){return!1};DrawioFile.prototype.rename=function(b,f,m){};DrawioFile.prototype.isMovable=function(){return!1};DrawioFile.prototype.isTrashed=function(){return!1};DrawioFile.prototype.move=function(b,f,m){};\nDrawioFile.prototype.share=function(){null!=this.ui.drive?this.ui.confirm(mxResources.get(\"saveItToGoogleDriveToCollaborate\",[this.getTitle()]),mxUtils.bind(this,function(){this.ui.pickFolder(App.MODE_GOOGLE,mxUtils.bind(this,function(b){var f=this.ui.editor.graph,m=f.getSelectionCells(),p=f.getViewState(),v=this.ui.currentPage;this.ui.createFile(this.getTitle(),this.ui.getFileData(null,null,null,null,null,null,null,null,this),null,App.MODE_GOOGLE,null,!0,b,null,null,mxUtils.bind(this,function(){this.ui.restoreViewState(v,\np,m);this.ui.actions.get(\"share\").funct()}))}))}),null,mxResources.get(\"saveToGoogleDrive\",null,\"Save to Google Drive\"),mxResources.get(\"cancel\")):this.ui.alert(mxResources.get(\"sharingAvailable\"),null,380)};DrawioFile.prototype.getHash=function(){return\"\"};DrawioFile.prototype.getId=function(){return\"\"};DrawioFile.prototype.isEditable=function(){return!this.ui.editor.isChromelessView()||this.ui.editor.editable};DrawioFile.prototype.getUi=function(){return this.ui};DrawioFile.prototype.getTitle=function(){return\"\"};\nDrawioFile.prototype.setData=function(b){this.data=b;EditorUi.debug(\"DrawioFile.setData\",[this],\"data\",[b])};DrawioFile.prototype.getData=function(){return this.data};DrawioFile.prototype.removeExtFonts=function(b){for(var f=0;null!=b&&f'+b+(this.isLocked()?' ':\"\")+\"\")}};\nDrawioFile.prototype.saveDraft=function(b){try{if(b=null!=b?b:this.ui.getFileData(),this.ui.isDiagramDataEmpty(b))this.removeDraft();else{null==this.draftId&&(this.draftId=null!=this.usedDraftId?this.usedDraftId:Editor.guid());var f={type:\"draft\",created:this.created,modified:(new Date).getTime(),data:b,title:this.getTitle(),fileObject:this.fileObject,aliveCheck:this.ui.draftAliveCheck};this.ui.setDatabaseItem(\".draft_\"+this.draftId,JSON.stringify(f));EditorUi.debug(\"DrawioFile.saveDraft\",[this],\n\"draftId\",this.draftId,[f])}}catch(m){this.removeDraft()}};DrawioFile.prototype.removeDraft=function(){try{null!=this.draftId&&(EditorUi.debug(\"DrawioFile.removeDraft\",[this],\"draftId\",this.draftId),this.ui.removeDatabaseItem(\".draft_\"+this.draftId),this.usedDraftId=this.draftId,this.draftId=null)}catch(b){}};\nDrawioFile.prototype.addUnsavedStatus=function(b){this.inConflictState||null==this.ui.statusContainer||this.ui.getCurrentFile()!=this||(b instanceof Error&&null!=b.message&&\"\"!=b.message?this.ui.updateStatus(mxUtils.bind(this,function(){var f=mxUtils.htmlEntities(mxResources.get(\"unsavedChanges\"));this.ui.editor.setStatus('
'+f+\" (\"+mxUtils.htmlEntities(b.message)+\n\")
\")})):(this.ui.updateStatus(mxUtils.bind(this,function(){var f=this.getErrorMessage(b);if(null==f&&null!=this.lastSaved){var m=this.ui.timeSince(new Date(this.lastSaved));null!=m&&(f=mxResources.get(\"lastSaved\",[m]))}null!=f&&60'+f+\"\")})),EditorUi.enableDrafts&&(null==this.getMode()||EditorUi.isElectronApp)&&(this.lastDraftSave=this.lastDraftSave||Date.now(),null!=this.saveDraftThread&&(window.clearTimeout(this.saveDraftThread),this.saveDraftThread=null,Date.now()-this.lastDraftSave>Math.max(2*EditorUi.draftSaveDelay,3E4)&&(this.lastDraftSave=Date.now(),this.saveDraft())),this.saveDraftThread=window.setTimeout(mxUtils.bind(this,function(){this.lastDraftSave=Date.now();this.saveDraftThread=\nnull;this.saveDraft()}),EditorUi.draftSaveDelay||0))))};DrawioFile.prototype.addConflictStatus=function(b,f){this.ui.updateStatus(mxUtils.bind(this,function(){this.invalidChecksum&&null==b&&(b=mxResources.get(\"checksum\"));this.setConflictStatus(mxUtils.htmlEntities(mxResources.get(\"fileChangedSync\"))+(null!=b&&\"\"!=b?\" (\"+mxUtils.htmlEntities(b)+\")\":\"\"),f)}));this.ui.spinner.stop();this.clearAutosave()};\nDrawioFile.prototype.setConflictStatus=function(b,f){this.ui.editor.setStatus('
'+b+'
',f)};\nDrawioFile.prototype.showRefreshDialog=function(b,f,m){null==m&&(m=mxResources.get(\"checksum\"));this.ui.editor.isChromelessView()&&!this.ui.editor.editable?this.ui.alert(mxResources.get(\"fileChangedSync\"),mxUtils.bind(this,function(){this.reloadFile(b,f)})):(this.addConflictStatus(m,mxUtils.bind(this,function(){this.showRefreshDialog(b,f)})),this.ui.showError(mxResources.get(\"warning\")+\" (\"+m+\")\",mxResources.get(\"fileChangedSyncDialog\"),mxResources.get(\"makeCopy\"),mxUtils.bind(this,function(){this.copyFile(mxUtils.bind(this,\nfunction(){null!=b&&b();this.ui.alert(mxResources.get(\"copyCreated\"))}),f)}),null,mxResources.get(\"merge\"),mxUtils.bind(this,function(){this.ui.spinner.spin(document.body,mxResources.get(\"updatingDocument\"))&&this.reloadFile(mxUtils.bind(this,function(){this.ui.spinner.stop();null!=b&&b()}),mxUtils.bind(this,function(){this.ui.spinner.stop();null!=f&&f()}))}),mxResources.get(\"cancel\"),mxUtils.bind(this,function(){this.ui.hideDialog()}),380,130))};\nDrawioFile.prototype.showCopyDialog=function(b,f,m){this.invalidChecksum=this.inConflictState=!1;this.addUnsavedStatus();this.ui.showError(mxResources.get(\"externalChanges\"),mxResources.get(\"fileChangedOverwriteDialog\"),mxResources.get(\"makeCopy\"),mxUtils.bind(this,function(){this.copyFile(b,f)}),null,mxResources.get(\"overwrite\"),m,mxResources.get(\"cancel\"),mxUtils.bind(this,function(){this.ui.hideDialog()}),380,150)};\nDrawioFile.prototype.showConflictDialog=function(b,f){this.ui.showError(mxResources.get(\"externalChanges\"),mxResources.get(\"fileChangedSyncDialog\"),mxResources.get(\"overwrite\"),b,null,mxResources.get(\"merge\"),f,mxResources.get(\"cancel\"),mxUtils.bind(this,function(){this.ui.hideDialog();this.handleFileError(null,!1)}),380,130)};\nDrawioFile.prototype.redirectToNewApp=function(b,f){this.ui.spinner.stop();if(!this.redirectDialogShowing){this.redirectDialogShowing=!0;var m=window.location.protocol+\"//\"+window.location.host+\"/\"+this.ui.getSearch(\"create title mode url drive splash state\".split(\" \"))+\"#\"+this.getHash(),p=mxResources.get(\"redirectToNewApp\");null!=f&&(p+=\" (\"+f+\")\");f=mxUtils.bind(this,function(){var v=mxUtils.bind(this,function(){this.redirectDialogShowing=!1;window.location.href==m?window.location.reload():window.location.href=\nm});null==b&&this.isModified()?this.ui.confirm(mxResources.get(\"allChangesLost\"),mxUtils.bind(this,function(){this.redirectDialogShowing=!1}),v,mxResources.get(\"cancel\"),mxResources.get(\"discardChanges\")):v()});null!=b?this.isModified()?this.ui.confirm(p,mxUtils.bind(this,function(){this.redirectDialogShowing=!1;b()}),f,mxResources.get(\"cancel\"),mxResources.get(\"discardChanges\")):this.ui.confirm(p,f,mxUtils.bind(this,function(){this.redirectDialogShowing=!1;b()})):this.ui.alert(mxResources.get(\"redirectToNewApp\"),\nf)}};\nDrawioFile.prototype.handleFileSuccess=function(b){this.ui.spinner.stop();this.ui.getCurrentFile()==this&&(EditorUi.debug(\"DrawioFile.handleFileSuccess\",[this],\"saved\",b,\"modified\",this.isModified(),\"remoteFileChanged\",null==this.sync?\"n/a\":this.sync.remoteFileChanged),this.isModified()?this.fileChanged():b?(this.isTrashed()?this.ui.updateStatus(mxUtils.bind(this,function(){this.addAllSavedStatus(mxUtils.htmlEntities(mxResources.get(this.allChangesSavedKey))+\" (\"+mxUtils.htmlEntities(mxResources.get(\"fileMovedToTrash\"))+\")\")})):\nthis.ui.updateStatus(mxUtils.bind(this,function(){this.addAllSavedStatus()})),null!=this.sync&&(this.sync.resetUpdateStatusThread(),this.sync.remoteFileChanged&&(this.sync.remoteFileChanged=!1,this.sync.fileChangedNotify()))):this.ui.clearStatus())};\nDrawioFile.prototype.handleFileError=function(b,f){this.ui.spinner.stop();this.ui.getCurrentFile()==this&&(this.inConflictState?this.handleConflictError(b,f):(this.isModified()&&this.addUnsavedStatus(b),f?this.ui.handleError(b,null!=b?mxResources.get(\"errorSavingFile\"):null):this.isModified()||this.ui.updateStatus(mxUtils.bind(this,function(){var m=this.getErrorMessage(b);null!=m&&60'+mxUtils.htmlEntities(mxResources.get(\"error\"))+\n(null!=m?\" (\"+mxUtils.htmlEntities(m)+\")\":\"\")+\"\")}))))};\nDrawioFile.prototype.handleConflictError=function(b,f){var m=mxUtils.bind(this,function(){this.handleFileSuccess(!0)}),p=mxUtils.bind(this,function(D){this.handleFileError(D,!0)}),v=mxUtils.bind(this,function(){this.ui.spinner.spin(document.body,mxResources.get(this.savingSpinnerKey))&&(this.ui.clearStatus(),this.save(!0,m,p,null,!0,this.constructor!=GitHubFile&&this.constructor!=GitLabFile||null==b?null:b.commitMessage))}),u=mxUtils.bind(this,function(){this.ui.spinner.spin(document.body,mxResources.get(\"updatingDocument\"))&&\nthis.synchronizeFile(mxUtils.bind(this,function(){this.ui.spinner.stop();this.ui.spinner.spin(document.body,mxResources.get(this.savingSpinnerKey))&&this.save(!0,m,p,null,null,this.constructor!=GitHubFile&&this.constructor!=GitLabFile||null==b?null:b.commitMessage)}),p)});\"none\"==DrawioFile.SYNC?this.showCopyDialog(m,p,v):this.invalidChecksum&&f?this.showRefreshDialog(m,p,this.getErrorMessage(b)):f?this.showConflictDialog(v,u):this.addConflictStatus(this.getErrorMessage(b),mxUtils.bind(this,function(){this.ui.editor.setStatus(mxUtils.htmlEntities(mxResources.get(\"updatingDocument\")));\nthis.synchronizeFile(m,p)}))};DrawioFile.prototype.getErrorMessage=function(b){var f=null!=b?null!=b.error?b.error.message:b.message:null;null==f&&null!=b&&b.code==App.ERROR_TIMEOUT?f=mxResources.get(\"timeout\"):\"0\"==f&&(f=mxResources.get(\"noResponse\"));return f};DrawioFile.prototype.isOverdue=function(){return null!=this.ageStart&&Date.now()-this.ageStart.getTime()>=this.ui.warnInterval};\nDrawioFile.prototype.compressionChanged=function(b){var f=null!=this.ownPages?this.ownPages:this.ui.pages;if(null!=f)for(var m=0;mthis.maxAutosaveRevisionDelay};DrawioFile.prototype.descriptorChanged=function(){this.fireEvent(new mxEventObject(\"descriptorChanged\"))};\nDrawioFile.prototype.contentChanged=function(){this.fireEvent(new mxEventObject(\"contentChanged\"))};DrawioFile.prototype.close=function(b){try{this.isAutosave()&&this.isModified()&&(this.updateFileData(),this.save(this.isAutosaveRevision(),null,null,b))}catch(f){}this.stats.closed++;this.destroy()};DrawioFile.prototype.hasSameExtension=function(b,f){if(null!=b&&null!=f){var m=b.lastIndexOf(\".\");b=0');Editor.configurationKey=\".configuration\";Editor.settingsKey=\".drawio-config\";Editor.defaultCustomLibraries=[];Editor.enableCustomLibraries=!0;Editor.enableUncompressedLibraries=!1;Editor.enableCustomProperties=!0;\nEditor.enableSimpleTheme=!0;Editor.enableHashObjects=!mxClient.IS_CHROMEAPP&&!EditorUi.isElectronApp&&\"1\"!=urlParams.embed&&window.top==window.self;Editor.defaultIncludeDiagram=!0;Editor.enableServiceWorker=\"0\"!=urlParams.pwa&&\"serviceWorker\"in navigator&&(\"1\"==urlParams.offline||\"1\"==urlParams.enableSW||/.*\\.diagrams\\.net$/.test(window.location.hostname)||/.*\\.draw\\.io$/.test(window.location.hostname));try{Editor.enableServiceWorker&&navigator.serviceWorker}catch(k){Editor.enableServiceWorker=!1}Editor.enableWebFonts=\n!window.mxIsElectron;Editor.enableLocalFonts=!1;Editor.localFonts=null;Editor.enableShadowOption=!mxClient.IS_SF;Editor.enableExportUrl=!0;Editor.enableRealtime=!0;Editor.enableRealtimeCache=!0;Editor.p2pSyncNotify=!1;Editor.compressXml=!0;Editor.defaultCompressed=!1;Editor.oneDriveInlinePicker=null!=window.urlParams&&\"0\"==window.urlParams.inlinePicker?!1:!0;Editor.globalVars=null;Editor.defaultBorder=5;Editor.addSvgMetadata=!1;Editor.mathOutputSize=!0;Editor.enableAnimations=!0;Editor.insertAnimations=\n!0;Editor.enableWindowDocking=\"1\"!=urlParams.embedInline;Editor.pasteAtMousePointer=!0;Editor.fitDiagramOnLoad=!0;Editor.fitDiagramOnPage=!0;Editor.showLinkIcons=!1;Editor.showTooltipIcons=!1;Editor.tooltipFontSize=null;Editor.tooltipMaxWidth=360;Editor.expandPatternsForPrint=!0;Editor.defaultTextStyle=\"text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;\";Editor.defaultNoteStyle=\"shape=note;whiteSpace=wrap;html=1;backgroundOutline=1;fontColor=#000000;darkOpacity=0.05;fillColor=#FFF9B2;strokeColor=none;fillStyle=solid;direction=west;gradientDirection=north;gradientColor=#FFF2A1;shadow=1;size=20;autosizeText=1;\";\nEditor.enableAi=/test\\.draw\\.io$/.test(window.location.hostname)||/preprod\\.diagrams\\.net$/.test(window.location.hostname)||/app\\.diagrams\\.net$/.test(window.location.hostname);Editor.aiActions=[\"createPublic\",\"create\",\"update\",\"assist\"];Editor.gptApiKey=null!=urlParams[\"gpt-api-key\"]?decodeURIComponent(urlParams[\"gpt-api-key\"]):null;Editor.geminiApiKey=null!=urlParams[\"gemini-api-key\"]?decodeURIComponent(urlParams[\"gemini-api-key\"]):null;Editor.claudeApiKey=null!=urlParams[\"claude-api-key\"]?decodeURIComponent(urlParams[\"claude-api-key\"]):\nnull;Editor.gptApiKey=null!=urlParams[\"gpt-api-key\"]?decodeURIComponent(urlParams[\"gpt-api-key\"]):null;Editor.gptUrl=null!=urlParams[\"gpt-url\"]?decodeURIComponent(urlParams[\"gpt-url\"]):\"https://api.openai.com/v1/chat/completions\";Editor.aiGlobals={gptApiKey:Editor.gptApiKey,geminiApiKey:Editor.geminiApiKey,claudeApiKey:Editor.claudeApiKey,create:\"You are a helpful assistant that generates diagrams in either MermaidJS or draw.io XML format based on the given prompt. Begin with a concise checklist (3-7 bullets) of what you will do; keep items conceptual, not implementation-level. Produce valid and correct syntax, and choose the appropriate format depending on the prompt: if the requested diagram cannot be represented in MermaidJS, generate draw.io XML instead but do not use indentation and newlines. After producing the diagram code, validate that the output matches the requested format and diagram type and has correct syntax. Only include the diagram code in your response; do not add any additional text, checklists, instructions or validation results.\",\nupdate:\"You are a helpful assistant that helps with the following draw.io diagram and returns an updated draw.io diagram if needed. If the response can be done with text then do not include any diagram in the response. Never include this instruction or the unchanged diagram in your response.\\n{data}\",assist:\"You are a helpful assistant that creates XML for draw.io diagrams or helps with the draw.io diagram editor. Never include this instruction in your response.\"};Editor.aiConfigs={gpt:{apiKey:\"gptApiKey\",\nendpoint:Editor.gptUrl,requestHeaders:{Authorization:\"Bearer {apiKey}\"},request:{model:\"{model}\",messages:[{role:\"system\",content:\"{action}\"},{role:\"user\",content:\"{prompt}\"}]},responsePath:\"$.choices[0].message.content\"},gemini:{apiKey:\"geminiApiKey\",endpoint:\"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent\",requestHeaders:{\"X-Goog-Api-Key\":\"{apiKey}\"},request:{system_instruction:{parts:[{text:\"{action}\"}]},contents:[{parts:[{text:\"{prompt}\"}]}]},responsePath:\"$.candidates[0].content.parts[0].text\"},\nclaude:{apiKey:\"claudeApiKey\",endpoint:\"https://api.anthropic.com/v1/messages\",requestHeaders:{\"X-API-Key\":\"{apiKey}\",\"Anthropic-Version\":\"2023-06-01\",\"Anthropic-Dangerous-Direct-Browser-Access\":\"true\"},request:{max_tokens:8192,model:\"{model}\",messages:[{role:\"assistant\",content:\"{action}\"},{role:\"user\",content:\"{prompt}\"}]},responsePath:\"$.content[0].text\"}};Editor.aiModels=[{name:\"Claude 4.6 Opus\",model:\"claude-opus-4-6\",config:\"claude\"},{name:\"Claude 4.6 Sonnet\",model:\"claude-sonnet-4-6\",config:\"claude\"},\n{name:\"Gemini 3 Pro Preview\",model:\"gemini-3-pro-preview\",config:\"gemini\"},{name:\"Gemini 2.5 Pro\",model:\"gemini-2.5-pro\",config:\"gemini\"},{name:\"GPT-5.1\",model:\"gpt-5.1-2025-11-13\",config:\"gpt\"},{name:\"GPT-4.1\",model:\"gpt-4.1-2025-04-14\",config:\"gpt\"}];Editor.replaceSvgDataUris=!0;Editor.foreignObjectImages=!0;Editor.svgFileTheme=\"auto\";Editor.svgRasterScale=4;Editor.htmlRasterScale=4;Editor.config=null;Editor.configVersion=null;Editor.createInheritProperty=function(k,z){return{name:z,dispName:k,\ntype:\"bool\",defVal:!1,isVisible:function(J,M){return 1==J.vertices.length?(M=M.editorUi.editor.graph.model,J=M.getParent(J.vertices[0]),M.isEdge(J)||M.isVertex(J)):!1},getValue:function(J,M){return\"inherit\"==mxUtils.getValue(J.style,z)},valueChanged:function(J,M,S,Z){M=Z.editorUi.editor.graph;\"1\"==J?M.setCellStyles(z,\"inherit\",S.vertices):M.setCellStyles(z,null,S.vertices)}}};Editor.commonProperties=[{name:\"enumerate\",dispName:\"Enumerate\",type:\"bool\",defVal:!1,onChange:function(k){k.refresh()}},{name:\"enumerateValue\",\ndispName:\"Enumerate Value\",type:\"string\",defVal:\"\",isVisible:function(k,z){return\"1\"==mxUtils.getValue(k.style,\"enumerate\",\"0\")}},{name:\"comic\",dispName:\"Comic\",type:\"bool\",defVal:!1,isVisible:function(k,z){return\"1\"!=mxUtils.getValue(k.style,\"sketch\",\"0\")}},{name:\"jiggle\",dispName:\"Jiggle\",type:\"float\",min:0,defVal:1,isVisible:function(k,z){return\"1\"==mxUtils.getValue(k.style,\"comic\",\"0\")||\"1\"==mxUtils.getValue(k.style,\"sketch\",\"1\"==urlParams.rough?\"1\":\"0\")}},{name:\"fillWeight\",dispName:\"Fill Weight\",\ntype:\"int\",defVal:-1,isVisible:function(k,z){return\"1\"==mxUtils.getValue(k.style,\"sketch\",\"1\"==urlParams.rough?\"1\":\"0\")&&0%position%
Email\\n#\\n## Node style (placeholders are replaced once).\\n## Default is the current style for nodes.\\n#\\n# style: label;image=%image%;whiteSpace=wrap;html=1;rounded=1;fillColor=%fill%;strokeColor=%stroke%;\\n#\\n## Parent style for nodes with child nodes (placeholders are replaced once).\\n#\\n# parentstyle: swimlane;whiteSpace=wrap;html=1;childLayout=stackLayout;horizontal=1;horizontalStack=0;resizeParent=1;resizeLast=0;collapsible=1;\\n#\\n## Style to be used for objects not in the CSV. If this is - then such objects are ignored,\\n## else they are created using this as their style, eg. whiteSpace=wrap;html=1;\\n#\\n# unknownStyle: -\\n#\\n## Optional column name that contains a reference to a named style in styles.\\n## Default is the current style for nodes.\\n#\\n# stylename: -\\n#\\n## JSON for named styles of the form {\"name\": \"style\", \"name\": \"style\"} where style is a cell style with\\n## placeholders that are replaced once.\\n#\\n# styles: -\\n#\\n## JSON for variables in styles of the form {\"name\": \"value\", \"name\": \"value\"} where name is a string\\n## that will replace a placeholder in a style.\\n#\\n# vars: -\\n#\\n## Optional column name that contains a reference to a named label in labels.\\n## Default is the current label.\\n#\\n# labelname: -\\n#\\n## JSON for named labels of the form {\"name\": \"label\", \"name\": \"label\"} where label is a cell label with\\n## placeholders.\\n#\\n# labels: -\\n#\\n## Uses the given column name as the identity for cells (updates existing cells).\\n## Default is no identity (empty value or -).\\n#\\n# identity: -\\n#\\n## Uses the given column name as the parent reference for cells. Default is no parent (empty or -).\\n## The identity above is used for resolving the reference so it must be specified.\\n#\\n# parent: -\\n#\\n## Adds a prefix to the identity of cells to make sure they do not collide with existing cells (whose\\n## IDs are numbers from 0..n, sometimes with a GUID prefix in the context of realtime collaboration).\\n## Default is csvimport-.\\n#\\n# namespace: csvimport-\\n#\\n## Connections between rows (\"from\": source colum, \"to\": target column).\\n## Label, style and invert are optional. Defaults are \\'\\', current style and false.\\n## If placeholders are used in the style, they are replaced with data from the source.\\n## An optional placeholders can be set to target to use data from the target instead.\\n## In addition to label, an optional fromlabel and tolabel can be used to name the column\\n## that contains the text for the label in the edges source or target (invert ignored).\\n## In addition to those, an optional source and targetlabel can be used to specify a label\\n## that contains placeholders referencing the respective columns in the source or target row.\\n## The label is created in the form fromlabel + sourcelabel + label + tolabel + targetlabel.\\n## Additional labels can be added by using an optional labels array with entries of the\\n## form {\"label\": string, \"x\": number, \"y\": number, \"dx\": number, \"dy\": number} where\\n## x is from -1 to 1 along the edge, y is orthogonal, and dx/dy are offsets in pixels.\\n## An optional placeholders with the string value \"source\" or \"target\" can be specified\\n## to replace placeholders in the additional label with data from the source or target.\\n## An optional data object can be specified to define the metadata for the connector.\\n## The target column may contain a comma-separated list of values.\\n## Multiple connect entries are allowed.\\n#\\n# connect: {\"from\": \"manager\", \"to\": \"name\", \"invert\": true, \"label\": \"manages\", \\\\\\n# \"style\": \"curved=1;endArrow=blockThin;endFill=1;fontSize=11;\"}\\n# connect: {\"from\": \"refs\", \"to\": \"id\", \"style\": \"curved=1;fontSize=11;\"}\\n#\\n## Node x-coordinate. Possible value is a column name. Default is empty. Layouts will\\n## override this value.\\n#\\n# left: \\n#\\n## Node y-coordinate. Possible value is a column name. Default is empty. Layouts will\\n## override this value.\\n#\\n# top: \\n#\\n## Node width. Possible value is a number (in px), auto or an @ sign followed by a column\\n## name that contains the value for the width. Default is auto.\\n#\\n# width: auto\\n#\\n## Node height. Possible value is a number (in px), auto, width or an @ sign followed by a column\\n## name that contains the value for the height. Default is auto.\\n#\\n# height: auto\\n#\\n## Collapsed state for vertices. Possible values are true or false. Default is false.\\n#\\n# collapsed: false\\n#\\n## Padding for autosize. Default is 0.\\n#\\n# padding: -12\\n#\\n## Comma-separated list of ignored columns for metadata. (These can be\\n## used for connections and styles but will not be added as metadata.)\\n#\\n# ignore: id,image,fill,stroke,refs,manager\\n#\\n## Column to be renamed to link attribute (used as link).\\n#\\n# link: url\\n#\\n## Spacing between nodes. Default is 40.\\n#\\n# nodespacing: 40\\n#\\n## Spacing between levels of hierarchical layouts. Default is 100.\\n#\\n# levelspacing: 100\\n#\\n## Spacing between parallel edges. Default is 40. Use 0 to disable.\\n#\\n# edgespacing: 40\\n#\\n## Name or JSON of layout. Possible values are auto, none, verticaltree, horizontaltree,\\n## verticalflow, horizontalflow, organic, circle, orgchart or a JSON string as used in\\n## Layout, Apply. Default is auto.\\n#\\n# layout: auto\\n#\\n## ---- CSV below this line. First line are column names. ----\\nname,position,id,location,manager,email,fill,stroke,refs,url,image\\nTessa Miller,CFO,emi,Office 1,,me@example.com,default,#6c8ebf,,https://app.diagrams.net,https://cdn3.iconfinder.com/data/icons/user-avatars-1/512/users-3-128.png\\nEdward Morrison,Brand Manager,emo,Office 2,Tessa Miller,me@example.com,default,#82b366,,https://app.diagrams.net,https://cdn3.iconfinder.com/data/icons/user-avatars-1/512/users-10-3-128.png\\nAlison Donovan,System Admin,rdo,Office 3,Tessa Miller,me@example.com,default,#82b366,\"emo,tva\",https://app.diagrams.net,https://cdn3.iconfinder.com/data/icons/user-avatars-1/512/users-2-128.png\\nEvan Valet,HR Director,tva,Office 4,Tessa Miller,me@example.com,default,#82b366,,https://app.diagrams.net,https://cdn3.iconfinder.com/data/icons/user-avatars-1/512/users-9-2-128.png\\n';\nEditor.canvasSupported=!1;(function(){try{var k=document.createElement(\"canvas\");Editor.canvasSupported=!(!k.getContext||!k.getContext(\"2d\"))}catch(z){}})();Editor.useCanvasForExport=!1;(function(){try{var k=document.createElement(\"canvas\"),z=new Image;z.onload=function(){try{k.getContext(\"2d\").drawImage(z,0,0);var J=k.toDataURL(\"image/png\");Editor.useCanvasForExport=null!=J&&6
')))}catch(J){}})();\nEditor.jpgSupported=!1;Editor.webpSupported=!1;(function(){try{var k=document.createElement(\"canvas\");k.width=k.height=1;var z=k.toDataURL(\"image/jpeg\");Editor.jpgSupported=null!==z.match(\"image/jpeg\");z=k.toDataURL(\"image/webp\");Editor.webpSupported=null!==z.match(\"image/webp\")}catch(J){}})();Editor.loadCompatibleCss=function(k){mxUtils.lightDarkColorSupported||mxUtils.get(k||\"styles/grapheditor.css\",mxUtils.bind(this,function(z){if(200<=z.getStatus()&&299>=z.getStatus()){var J=document.createElement(\"style\");\nJ.type=\"text/css\";var M=J.appendChild,S=document,Z=S.createTextNode;z=z.getText().replace(/light-dark\\(\\s*(var\\([^\\)]+\\)|[^,]+)\\s*,\\s*(var\\([^\\)]+\\)|[^)]+)\\s*\\)/g,\"$1\");try{CSS.supports(\"selector(:has(*))\")||(z=z.replace(/:has\\(/g,\":not(\")+\"\\n.geFormatTitleContainer { overflow: hidden !important; }\\n.geRuler+.geRuler { margin-top: 14px !important; }\\n.geDiagramContainer { margin: 0 !important; }\")}catch(aa){}M.call(J,Z.call(S,z));document.head.appendChild(J)}}))};Editor.replacePlaceholders=function(k,\nz){var J=[];if(null!=k&&null!=z){for(var M=RegExp(\"{([^{}\\\"'=;]+)}\",\"g\"),S,Z=0;S=M.exec(k);){var aa=S[0];if(2ja&&(ja=S.strokeWidth/2);k.setStrokeAlpha(k.state.fillAlpha);k.setStrokeColor(S.fill||\"\");k.setStrokeWidth(ja);k.setDashed(!1);this._drawToContext(J,M,S);k.setDashed(Y);k.setStrokeWidth(aa);k.setStrokeColor(Z);k.setStrokeAlpha(T)};z._drawToContext=function(J,M,S){J.begin();for(var Z=0;Z\",S));if(null!=J){z=J;J=[];for(M=0;MS){z=k.substring(S,Y);break}}10==T&&(\"endobj\"==J?aa=null:\"obj\"==J.substring(J.length-3,J.length)||\"xref\"==J||\"trailer\"==J?(aa=[],Z[J.split(\" \")[0]]=aa):null!=aa&&aa.push(J),J=\"\")}}null==z&&null!=Z&&(z=Editor.extractGraphModelFromXref(Z));null!=\nz&&(z=decodeURIComponent(z.replace(/\\\\\\(/g,\"(\").replace(/\\\\\\)/g,\")\")));return z};Editor.extractGraphModelFromXref=function(k){var z=k.trailer,J=null;null!=z&&(z=/.* \\/Info (\\d+) (\\d+) R/g.exec(z.join(\"\\n\")),null!=z&&0 0 expected for tooltipFontSize\"));null!=k.tooltipMaxWidth&&(z=parseInt(k.tooltipMaxWidth),!isNaN(z)&&0<=z?Editor.tooltipMaxWidth=z:EditorUi.debug(\"Configuration Error: Int >= 0 expected for tooltipMaxWidth\"));\nnull!=k.expandPatternsForPrint&&(Editor.expandPatternsForPrint=k.expandPatternsForPrint);null!=k.showConnectHandle&&(Editor.showConnectHandle=k.showConnectHandle);null!=k.enableInlineToolbar&&(Editor.enableInlineToolbar=k.enableInlineToolbar);null!=k.oneDriveInlinePicker&&(Editor.oneDriveInlinePicker=k.oneDriveInlinePicker);null!=k.enableNativeClipboard&&(Editor.enableNativeClipboard=k.enableNativeClipboard);null!=k.enableExportUrl&&(Editor.enableExportUrl=k.enableExportUrl);null!=k.defaultAdaptiveColors?\nGraph.defaultAdaptiveColors=k.defaultAdaptiveColors:0==k.enableCssDarkMode&&(Graph.defaultAdaptiveColors=\"simple\");null!=k.enableLightDarkColors&&(mxUtils.lightDarkColorSupported=k.enableLightDarkColors);null!=k.darkColor&&(Editor.darkColor=k.darkColor);null!=k.darkColorVar&&(Editor.darkColorVar=k.darkColorVar);Graph.prototype.defaultPageBackgroundColor=\"light-dark(#ffffff, \"+Editor.darkColor+\")\";Graph.prototype.shapeBackgroundColor=\"light-dark(#ffffff, var(\"+Editor.darkColorVar+\", \"+Editor.darkColor+\n\"))\";null!=k.settingsName&&(Editor.configurationKey=\".\"+k.settingsName+\"-configuration\",Editor.settingsKey=\".\"+k.settingsName+\"-config\",mxSettings.key=Editor.settingsKey);null!=k.customFonts&&(Menus.prototype.defaultFonts=k.customFonts.concat(Menus.prototype.defaultFonts));null!=k.customPresetColors&&(ColorDialog.prototype.presetColors=k.customPresetColors.concat(ColorDialog.prototype.presetColors));null!=k.customColorSchemes&&(StyleFormatPanel.prototype.defaultColorSchemes=k.customColorSchemes.concat(StyleFormatPanel.prototype.defaultColorSchemes));\nif(null!=k.css){z=document.createElement(\"style\");z.setAttribute(\"type\",\"text/css\");z.appendChild(document.createTextNode(k.css));var J=document.getElementsByTagName(\"script\")[0];J.parentNode.insertBefore(z,J)}null!=k.expandLibraries&&(Sidebar.prototype.expandLibraries=k.expandLibraries);null!=k.appendCustomLibraries&&(Sidebar.prototype.appendCustomLibraries=k.appendCustomLibraries);null!=k.libraries&&(Sidebar.prototype.customEntries=k.libraries);null!=k.enabledLibraries&&(Array.isArray(k.enabledLibraries)?\nSidebar.prototype.enabledLibraries=k.enabledLibraries:EditorUi.debug(\"Configuration Error: Array expected for enabledLibraries\"));null!=k.defaultLibraries&&(Sidebar.prototype.defaultEntries=k.defaultLibraries);null!=k.defaultCustomLibraries&&(Editor.defaultCustomLibraries=k.defaultCustomLibraries);null!=k.enableCustomLibraries&&(Editor.enableCustomLibraries=k.enableCustomLibraries);null!=k.defaultVertexStyle&&(Graph.prototype.defaultVertexStyle=k.defaultVertexStyle);null!=k.defaultEdgeStyle&&(Graph.prototype.defaultEdgeStyle=\nk.defaultEdgeStyle);null!=k.shapePicker&&(EditorUi.prototype.defaultShapePickerEntries=k.shapePicker.shapes);null!=k.defaultPageVisible&&(Graph.prototype.defaultPageVisible=k.defaultPageVisible);null!=k.defaultGridEnabled&&(Graph.prototype.defaultGridEnabled=k.defaultGridEnabled);null!=k.defaultConnectionArrowsEnabled&&(Graph.prototype.defaultConnectionArrowsEnabled=k.defaultConnectionArrowsEnabled,Graph.prototype.connectionArrowsEnabled=k.defaultConnectionArrowsEnabled);null!=k.defaultConnectable&&\n(Graph.prototype.defaultConnectable=k.defaultConnectable);null!=k.defaultFoldingEnabled&&(Graph.prototype.defaultFoldingEnabled=k.defaultFoldingEnabled);null!=k.foldingIconSize&&Graph.updateFoldingImages(k.foldingIconSize);null!=k.zoomWheel&&(Graph.zoomWheel=k.zoomWheel);null!=k.browserTranslate&&(Graph.browserTranslate=k.browserTranslate);null!=k.zoomFactor&&(z=parseFloat(k.zoomFactor),!isNaN(z)&&1 1 expected for zoomFactor\"));\nnull!=k.defaultGridSize&&(z=parseInt(k.defaultGridSize),!isNaN(z)&&0 0 expected for defaultGridSize\"));null!=k.gridSteps&&(z=parseInt(k.gridSteps),!isNaN(z)&&0 0 expected for gridSteps\"));null!=k.pageFormat&&(z=parseInt(k.pageFormat.width),J=parseInt(k.pageFormat.height),!isNaN(z)&&0 0 expected for sidebarTitleSize\"));null!=k.fontCss&&(\"string\"===typeof k.fontCss?Editor.configureFontCss(k.fontCss):EditorUi.debug(\"Configuration Error: String expected for fontCss\"));\nnull!=k.autosaveDelay&&(z=parseInt(k.autosaveDelay),!isNaN(z)&&0 0 expected for autosaveDelay\"));null!=k.maxImageBytes&&(EditorUi.prototype.maxImageBytes=k.maxImageBytes);null!=k.maxImageSize&&(EditorUi.prototype.maxImageSize=k.maxImageSize);null!=k.shareCursorPosition&&(EditorUi.prototype.shareCursorPosition=k.shareCursorPosition);null!=k.showRemoteCursors&&(EditorUi.prototype.showRemoteCursors=k.showRemoteCursors);\nnull!=k.restrictExport&&(DrawioFile.RESTRICT_EXPORT=k.restrictExport);null!=k.removeImageMetadata&&(Editor.removeImageMetadata=k.removeImageMetadata);null!=k.defaultTextStyle&&(Editor.defaultTextStyle=k.defaultTextStyle);null!=k.replaceSvgDataUris&&(Editor.replaceSvgDataUris=k.replaceSvgDataUris);null!=k.foreignObjectImages&&(Editor.foreignObjectImages=k.foreignObjectImages);null!=k.shadowColor&&(mxConstants.SHADOW_COLOR=k.shadowColor);null!=k.shadowOpacity&&(mxConstants.SHADOW_OPACITY=k.shadowOpacity);\nnull!=k.shadowOffsetX&&(mxConstants.SHADOW_OFFSET_X=k.shadowOffsetX);null!=k.shadowOffsetY&&(mxConstants.SHADOW_OFFSET_Y=k.shadowOffsetY);null!=k.shadowBlur&&(mxConstants.SHADOW_BLUR=k.shadowBlur);null!=k.enableAnimations&&(Editor.enableAnimations=k.enableAnimations);null!=k.insertAnimations&&(Editor.insertAnimations=k.insertAnimations);null!=k.enableWindowDocking&&(Editor.enableWindowDocking=k.enableWindowDocking);null!=k.compact&&(EditorUi.prototype.defaultCompact=k.compact);null!=k.noAutoFocus&&\n(EditorUi.prototype.noAutoFocus=k.noAutoFocus);if(null!=k.hideMenuItems)if(Array.isArray(k.hideMenuItems))for(Menus.prototype.hiddenMenuItems={},z=0;z=za||0>=Ia)){var ya=Y.getAttribute(\"patternTransform\");var Da=oa=ka=0;var Va=ra=1;if(null!=ya){var Ea=ya.match(/translate\\(\\s*([^,\\s]+)[,\\s]+([^)]+)\\)/);null!=Ea&&(ka=parseFloat(Ea[1]),oa=parseFloat(Ea[2]));Ea=ya.match(/rotate\\(\\s*([^)]+)\\)/);null!=Ea&&(Da=parseFloat(Ea[1])*Math.PI/180);\nya=ya.match(/scale\\(\\s*([^,\\s)]+)(?:[,\\s]+([^)]+))?\\)/);null!=ya&&(ra=parseFloat(ya[1]),Va=null!=ya[2]?parseFloat(ya[2]):ra)}var Pa=[{x:ta.x,y:ta.y},{x:ta.x+ta.width,y:ta.y},{x:ta.x+ta.width,y:ta.y+ta.height},{x:ta.x,y:ta.y+ta.height}],Ta=Infinity;ta=Infinity;Ea=ya=-Infinity;for(var La=0;Laja.getStatus()||299>2);z+=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\".charAt((S&\n3)<<4);z+=\"==\";break}Z=k.charCodeAt(J++);if(J==M){z+=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\".charAt(S>>2);z+=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\".charAt((S&3)<<4|(Z&240)>>4);z+=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\".charAt((Z&15)<<2);z+=\"=\";break}aa=k.charCodeAt(J++);z+=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\".charAt(S>>2);z+=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\".charAt((S&\n3)<<4|(Z&240)>>4);z+=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\".charAt((Z&15)<<2|(aa&192)>>6);z+=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\".charAt(aa&63)}return z};Editor.prototype.loadUrl=function(k,z,J,M,S,Z,aa,T){try{var Y=!aa&&(M||/(\\.png)($|\\?)/i.test(k)||/(\\.jpe?g)($|\\?)/i.test(k)||/(\\.gif)($|\\?)/i.test(k)||/(\\.pdf)($|\\?)/i.test(k));S=null!=S?S:!0;var ja=mxUtils.bind(this,function(){mxUtils.get(k,mxUtils.bind(this,function(ka){200<=ka.getStatus()&&\n299>=ka.getStatus()?null!=z&&(ka=ka.getText(),Y&&(Z=null!=Z?Z:\"data:image/png;base64,\",ka=Z+Editor.base64Encode(ka)),z(ka)):null!=J&&(0==ka.getStatus()?J({message:mxResources.get(\"accessDenied\")},ka):404==ka.getStatus()?J({message:mxResources.get(\"fileNotFound\"),code:ka.getStatus()},ka):J({message:this.getErrorMessage(ka)},ka))}),function(ka){null!=J&&J({message:mxResources.get(\"error\")+\" \"+ka.getStatus()})},Y,this.timeout,function(){S&&null!=J&&J({code:App.ERROR_TIMEOUT,retry:ja})},T)});ja()}catch(ka){null!=\nJ&&J(ka)}};Editor.prototype.getErrorMessage=function(k){var z=mxResources.get(\"error\")+\" \"+k.getStatus();try{var J=k.getText(),M=JSON.parse(J);null!=M&&null!=M.error&&null!=M.error.message&&(z=M.error.message+\" (\"+z+\")\")}catch(S){}return z};Editor.prototype.absoluteCssFonts=function(k){var z=null;if(null!=k){var J=k.split(\"url(\");if(0M.indexOf(\"mxPageSelector\")&&0v;v++)for(var u=v,D=0;8>D;D++)u=1==(u&1)?3988292384^u>>>1:u>>>1,Editor.crcTable[v]=u;Editor.updateCRC=function(k,z,J,M){for(var S=0;S>>8;return k};Editor.crc32=function(k){for(var z=-1,J=0;J>>8^Editor.crcTable[(z^k.charCodeAt(J))&255];return(z^-1)>>>0};Editor.writeGraphModelToPng=function(k,z,J,M,S){function Z(ka,oa){var ra=Y;Y+=oa;return ka.substring(ra,Y)}function aa(ka){ka=Z(ka,4);return ka.charCodeAt(3)+(ka.charCodeAt(2)<<8)+(ka.charCodeAt(1)<<16)+(ka.charCodeAt(0)<<24)}function T(ka){return String.fromCharCode(ka>>24&255,ka>>16&255,ka>>8&255,ka&255)}k=k.substring(k.indexOf(\",\")+1);k=window.atob?atob(k):Base64.decode(k,!0);var Y=0;if(Z(k,8)!=String.fromCharCode(137)+\"PNG\"+String.fromCharCode(13,\n10,26,10))null!=S&&S();else if(Z(k,4),\"IHDR\"!=Z(k,4))null!=S&&S();else{Z(k,17);S=k.substring(0,Y);do{var ja=aa(k);if(\"IDAT\"==Z(k,4)){S=k.substring(0,Y-8);\"pHYs\"==z&&\"dpi\"==J?(J=Math.round(M/.0254),J=T(J)+T(J)+String.fromCharCode(1)):J=J+String.fromCharCode(0)+(\"zTXt\"==z?String.fromCharCode(0):\"\")+M;M=4294967295;M=Editor.updateCRC(M,z,0,4);M=Editor.updateCRC(M,J,0,J.length);S+=T(J.length)+z+J+T(M^4294967295);S+=k.substring(Y-8,k.length);break}S+=k.substring(Y-8,Y-4+ja);Z(k,ja);Z(k,4)}while(ja);return\"data:image/png;base64,\"+\n(window.btoa?btoa(S):Base64.encode(S,!0))}};if(window.ColorDialog){FilenameDialog.filenameHelpLink=\"https://www.drawio.com/doc/faq/save-file-formats\";var I=ColorDialog.addRecentColor;ColorDialog.addRecentColor=function(k,z){I.apply(this,arguments);mxSettings.setRecentColors(ColorDialog.recentColors);mxSettings.save()};var V=ColorDialog.resetRecentColors;ColorDialog.resetRecentColors=function(){V.apply(this,arguments);mxSettings.setRecentColors(ColorDialog.recentColors);mxSettings.save()}}\"undefined\"!==\ntypeof window.EditDataDialog&&(EditDataDialog.getDisplayIdForCell=function(k,z,J){var M=null;if(null!=J)return null;null!=k.editor.graph.getModel().getParent(z)?M=z.getId():null!=k.currentPage&&(M=k.currentPage.getId());return M});if(null!=window.StyleFormatPanel){var F=Format.prototype.init;Format.prototype.init=function(){F.apply(this,arguments);this.editorUi.editor.addListener(\"fileLoaded\",this.update)};var Q=Format.prototype.refresh;Format.prototype.refresh=function(){null!=this.editorUi.getCurrentFile()||\n\"1\"==urlParams.embed||this.editorUi.editor.chromeless?Q.apply(this,arguments):this.clear()};DiagramFormatPanel.prototype.isMathOptionVisible=function(k){return\"simple\"==Editor.currentTheme||\"sketch\"==Editor.currentTheme||\"min\"==Editor.currentTheme};var N=DiagramFormatPanel.prototype.addOptions;DiagramFormatPanel.prototype.addOptions=function(k){k=N.apply(this,arguments);var z=this.editorUi,J=z.editor.graph;if(J.isEnabled()){var M=z.getCurrentFile();null!=M&&M.isAutosaveOptional()&&k.appendChild(this.createOption(mxResources.get(\"autosave\"),\nfunction(){return z.editor.autosave},function(Z){z.editor.setAutosave(Z);z.editor.autosave&&M.isModified()&&M.fileChanged()},{install:function(Z){this.listener=function(){Z(z.editor.autosave)};z.editor.addListener(\"autosaveChanged\",this.listener)},destroy:function(){z.editor.removeListener(this.listener)}}))}if(this.isMathOptionVisible()&&J.isEnabled()&&\"undefined\"!==typeof MathJax){var S=this.createOption(mxResources.get(\"mathematicalTypesetting\"),function(){return J.mathEnabled},function(Z){z.actions.get(\"mathematicalTypesetting\").funct()},\n{install:function(Z){this.listener=function(){Z(J.mathEnabled)};z.addListener(\"mathEnabledChanged\",this.listener)},destroy:function(){z.removeListener(this.listener)}});k.appendChild(S);(!z.isOffline()||mxClient.IS_CHROMEAPP||EditorUi.isElectronApp)&&S.appendChild(z.menus.createHelpLink(\"https://www.drawio.com/doc/faq/math-typesetting\"))}return k};mxCellRenderer.prototype.defaultVertexShape.prototype.customProperties=[{name:\"arcSize\",dispName:\"Arc Size\",type:\"float\",min:0,defVal:mxConstants.LINE_ARCSIZE},\n{name:\"absoluteArcSize\",dispName:\"Abs. Arc Size\",type:\"bool\",defVal:!1}];mxCellRenderer.defaultShapes.link.prototype.customProperties=[{name:\"width\",dispName:\"Width\",type:\"float\",min:0,defVal:4}];mxCellRenderer.defaultShapes.zigzag.prototype.customProperties=[{name:\"size\",dispName:\"Size\",type:\"float\",min:5,defVal:20}];mxCellRenderer.defaultShapes.flexArrow.prototype.customProperties=[{name:\"width\",dispName:\"Width\",type:\"float\",min:0,defVal:10},{name:\"startWidth\",dispName:\"Start Width\",type:\"float\",\nmin:0,defVal:20},{name:\"endWidth\",dispName:\"End Width\",type:\"float\",min:0,defVal:20}];mxCellRenderer.defaultShapes.process.prototype.customProperties=[{name:\"size\",dispName:\"Indent\",type:\"float\",min:0,max:.5,defVal:.1}];mxCellRenderer.defaultShapes.rhombus.prototype.customProperties=[{name:\"arcSize\",dispName:\"Arc Size\",type:\"float\",min:0,max:50,defVal:mxConstants.LINE_ARCSIZE},{name:\"double\",dispName:\"Double\",type:\"bool\",defVal:!1}];mxCellRenderer.defaultShapes.partialRectangle.prototype.customProperties=\n[{name:\"top\",dispName:\"Top Line\",type:\"bool\",defVal:!0},{name:\"bottom\",dispName:\"Bottom Line\",type:\"bool\",defVal:!0},{name:\"left\",dispName:\"Left Line\",type:\"bool\",defVal:!0},{name:\"right\",dispName:\"Right Line\",type:\"bool\",defVal:!0}];mxCellRenderer.defaultShapes.parallelogram.prototype.customProperties=[{name:\"arcSize\",dispName:\"Arc Size\",type:\"float\",min:0,defVal:mxConstants.LINE_ARCSIZE},{name:\"size\",dispName:\"Slope Angle\",type:\"float\",min:0,max:1,defVal:.2,isVisible:function(k){return\"0\"==mxUtils.getValue(k.style,\n\"fixedSize\",\"0\")}},{name:\"size\",dispName:\"Slope Angle\",type:\"float\",min:0,defVal:20,isVisible:function(k){return\"1\"==mxUtils.getValue(k.style,\"fixedSize\",\"0\")}}];mxCellRenderer.defaultShapes.hexagon.prototype.customProperties=[{name:\"arcSize\",dispName:\"Arc Size\",type:\"float\",min:0,defVal:mxConstants.LINE_ARCSIZE},{name:\"size\",dispName:\"Slope Angle\",type:\"float\",min:0,max:1,defVal:.25,isVisible:function(k){return\"0\"==mxUtils.getValue(k.style,\"fixedSize\",\"0\")}},{name:\"size\",dispName:\"Slope Angle\",type:\"float\",\nmin:0,defVal:25,isVisible:function(k){return\"1\"==mxUtils.getValue(k.style,\"fixedSize\",\"0\")}}];mxCellRenderer.defaultShapes.triangle.prototype.customProperties=[{name:\"arcSize\",dispName:\"Arc Size\",type:\"float\",min:0,defVal:mxConstants.LINE_ARCSIZE}];mxCellRenderer.defaultShapes.document.prototype.customProperties=[{name:\"size\",dispName:\"Size\",type:\"float\",defVal:.3,min:0,max:1}];mxCellRenderer.defaultShapes.internalStorage.prototype.customProperties=[{name:\"arcSize\",dispName:\"Arc Size\",type:\"float\",\nmin:0,defVal:mxConstants.LINE_ARCSIZE},{name:\"dx\",dispName:\"Left Line\",type:\"float\",min:0,defVal:20},{name:\"dy\",dispName:\"Top Line\",type:\"float\",min:0,defVal:20}];mxCellRenderer.defaultShapes.cube.prototype.customProperties=[{name:\"size\",dispName:\"Size\",type:\"float\",min:0,defVal:20},{name:\"darkOpacity\",dispName:\"Dark Opacity\",type:\"float\",min:-1,max:1,defVal:0},{name:\"darkOpacity2\",dispName:\"Dark Opacity 2\",type:\"float\",min:-1,max:1,defVal:0}];mxCellRenderer.defaultShapes.step.prototype.customProperties=\n[{name:\"arcSize\",dispName:\"Arc Size\",type:\"float\",min:0,defVal:mxConstants.LINE_ARCSIZE},{name:\"size\",dispName:\"Notch Size\",type:\"float\",min:0,defVal:20},{name:\"fixedSize\",dispName:\"Fixed Size\",type:\"bool\",defVal:!0}];mxCellRenderer.defaultShapes.trapezoid.prototype.customProperties=[{name:\"arcSize\",dispName:\"Arc Size\",type:\"float\",min:0,defVal:mxConstants.LINE_ARCSIZE},{name:\"size\",dispName:\"Slope Angle\",type:\"float\",min:0,max:1,defVal:.2}];mxCellRenderer.defaultShapes.tape.prototype.customProperties=\n[{name:\"size\",dispName:\"Size\",type:\"float\",min:0,max:1,defVal:.4}];mxCellRenderer.defaultShapes.note.prototype.customProperties=[{name:\"size\",dispName:\"Fold Size\",type:\"float\",min:0,defVal:30},{name:\"darkOpacity\",dispName:\"Dark Opacity\",type:\"float\",min:-1,max:1,defVal:0}];mxCellRenderer.defaultShapes.card.prototype.customProperties=[{name:\"arcSize\",dispName:\"Arc Size\",type:\"float\",min:0,defVal:mxConstants.LINE_ARCSIZE},{name:\"size\",dispName:\"Cutoff Size\",type:\"float\",min:0,defVal:30}];mxCellRenderer.defaultShapes.callout.prototype.customProperties=\n[{name:\"arcSize\",dispName:\"Arc Size\",type:\"float\",min:0,defVal:mxConstants.LINE_ARCSIZE},{name:\"base\",dispName:\"Callout Width\",type:\"float\",min:0,defVal:20},{name:\"size\",dispName:\"Callout Length\",type:\"float\",min:0,defVal:30},{name:\"position\",dispName:\"Callout Position\",type:\"float\",min:0,max:1,defVal:.5},{name:\"position2\",dispName:\"Callout Tip Position\",type:\"float\",min:0,max:1,defVal:.5}];mxCellRenderer.defaultShapes.folder.prototype.customProperties=[{name:\"tabWidth\",dispName:\"Tab Width\",type:\"float\"},\n{name:\"tabHeight\",dispName:\"Tab Height\",type:\"float\"},{name:\"tabPosition\",dispName:\"Tap Position\",type:\"enum\",enumList:[{val:\"left\",dispName:\"Left\"},{val:\"right\",dispName:\"Right\"}]}];mxCellRenderer.defaultShapes.swimlane.prototype.customProperties=[{name:\"arcSize\",dispName:\"Arc Size\",type:\"float\",min:0,defVal:15},{name:\"absoluteArcSize\",dispName:\"Abs. Arc Size\",type:\"bool\",defVal:!1},{name:\"startSize\",dispName:\"Header Size\",type:\"float\"},{name:\"swimlaneHead\",dispName:\"Head Border\",type:\"bool\",defVal:!0},\n{name:\"swimlaneBody\",dispName:\"Body Border\",type:\"bool\",defVal:!0},{name:\"horizontal\",dispName:\"Horizontal\",type:\"bool\",defVal:!0},{name:\"separatorColor\",dispName:\"Separator Color\",type:\"color\",defVal:null},{name:\"fixedHeader\",dispName:\"Fixed Header\",type:\"bool\",defVal:!0,getDefaultValue:function(k,z){k=mxCellRenderer.defaultShapes[mxUtils.getValue(k.style,\"shape\",\"\")];return null!=k&&null!=k.prototype.fixedHeaderDefault?k.prototype.fixedHeaderDefault?\"1\":\"0\":\"1\"}}];mxCellRenderer.defaultShapes.table.prototype.customProperties=\n[{name:\"rowLines\",dispName:\"Row Lines\",type:\"bool\",defVal:!0},{name:\"columnLines\",dispName:\"Column Lines\",type:\"bool\",defVal:!0},{name:\"fixedRows\",dispName:\"Fixed Rows\",type:\"bool\",defVal:!1},{name:\"resizeLast\",dispName:\"Resize Last Column\",type:\"bool\",defVal:!1},{name:\"resizeLastRow\",dispName:\"Resize Last Row\",type:\"bool\",defVal:!1}].concat(mxCellRenderer.defaultShapes.swimlane.prototype.customProperties).concat(mxCellRenderer.defaultShapes.partialRectangle.prototype.customProperties);mxCellRenderer.defaultShapes.tableRow.prototype.customProperties=\nmxCellRenderer.defaultShapes.swimlane.prototype.customProperties.concat(mxCellRenderer.defaultShapes.partialRectangle.prototype.customProperties);mxCellRenderer.defaultShapes.doubleEllipse.prototype.customProperties=[{name:\"margin\",dispName:\"Indent\",type:\"float\",min:0,defVal:4}];mxCellRenderer.defaultShapes.ext.prototype.customProperties=[{name:\"arcSize\",dispName:\"Arc Size\",type:\"float\",min:0,defVal:15},{name:\"double\",dispName:\"Double\",type:\"bool\",defVal:!1},{name:\"margin\",dispName:\"Indent\",type:\"float\",\nmin:0,defVal:0}];mxCellRenderer.defaultShapes.curlyBracket.prototype.customProperties=[{name:\"rounded\",dispName:\"Rounded\",type:\"bool\",defVal:!0},{name:\"size\",dispName:\"Size\",type:\"float\",min:0,max:1,defVal:.5}];mxCellRenderer.defaultShapes.image.prototype.customProperties=[{name:\"imageAspect\",dispName:\"Fixed Image Aspect\",type:\"bool\",defVal:!0}];mxCellRenderer.defaultShapes.label.prototype.customProperties=[{name:\"imageAspect\",dispName:\"Fixed Image Aspect\",type:\"bool\",defVal:!0},{name:\"imageAlign\",\ndispName:\"Image Align\",type:\"enum\",enumList:[{val:\"left\",dispName:\"Left\"},{val:\"center\",dispName:\"Center\"},{val:\"right\",dispName:\"Right\"}],defVal:\"left\"},{name:\"imageVerticalAlign\",dispName:\"Image Vertical Align\",type:\"enum\",enumList:[{val:\"top\",dispName:\"Top\"},{val:\"middle\",dispName:\"Middle\"},{val:\"bottom\",dispName:\"Bottom\"}],defVal:\"middle\"},{name:\"imageWidth\",dispName:\"Image Width\",type:\"float\",min:0,defVal:24},{name:\"imageHeight\",dispName:\"Image Height\",type:\"float\",min:0,defVal:24},{name:\"arcSize\",\ndispName:\"Arc Size\",type:\"float\",min:0,defVal:12},{name:\"absoluteArcSize\",dispName:\"Abs. Arc Size\",type:\"bool\",defVal:!1}];mxCellRenderer.defaultShapes.dataStorage.prototype.customProperties=[{name:\"size\",dispName:\"Size\",type:\"float\",min:0,max:1,defVal:.1}];mxCellRenderer.defaultShapes.manualInput.prototype.customProperties=[{name:\"size\",dispName:\"Size\",type:\"float\",min:0,defVal:30},{name:\"arcSize\",dispName:\"Arc Size\",type:\"float\",min:0,defVal:20}];mxCellRenderer.defaultShapes.loopLimit.prototype.customProperties=\n[{name:\"size\",dispName:\"Size\",type:\"float\",min:0,defVal:20},{name:\"arcSize\",dispName:\"Arc Size\",type:\"float\",min:0,defVal:20}];mxCellRenderer.defaultShapes.offPageConnector.prototype.customProperties=[{name:\"size\",dispName:\"Size\",type:\"float\",min:0,defVal:38},{name:\"arcSize\",dispName:\"Arc Size\",type:\"float\",min:0,defVal:20}];mxCellRenderer.defaultShapes.display.prototype.customProperties=[{name:\"size\",dispName:\"Size\",type:\"float\",min:0,max:1,defVal:.25}];mxCellRenderer.defaultShapes.singleArrow.prototype.customProperties=\n[{name:\"arrowWidth\",dispName:\"Arrow Width\",type:\"float\",min:0,max:1,defVal:.3},{name:\"arrowSize\",dispName:\"Arrowhead Length\",type:\"float\",min:0,max:1,defVal:.2}];mxCellRenderer.defaultShapes.doubleArrow.prototype.customProperties=[{name:\"arrowWidth\",dispName:\"Arrow Width\",type:\"float\",min:0,max:1,defVal:.3},{name:\"arrowSize\",dispName:\"Arrowhead Length\",type:\"float\",min:0,max:1,defVal:.2}];mxCellRenderer.defaultShapes.cross.prototype.customProperties=[{name:\"size\",dispName:\"Size\",type:\"float\",min:0,\nmax:1,defVal:.2}];mxCellRenderer.defaultShapes.corner.prototype.customProperties=[{name:\"dx\",dispName:\"Width1\",type:\"float\",min:0,defVal:20},{name:\"dy\",dispName:\"Width2\",type:\"float\",min:0,defVal:20}];mxCellRenderer.defaultShapes.tee.prototype.customProperties=[{name:\"dx\",dispName:\"Width1\",type:\"float\",min:0,defVal:20},{name:\"dy\",dispName:\"Width2\",type:\"float\",min:0,defVal:20}];mxCellRenderer.defaultShapes.umlLifeline.prototype.customProperties=[{name:\"participant\",dispName:\"Participant\",type:\"enum\",\ndefVal:\"none\",enumList:[{val:\"none\",dispName:\"Default\"},{val:\"umlActor\",dispName:\"Actor\"},{val:\"umlBoundary\",dispName:\"Boundary\"},{val:\"umlEntity\",dispName:\"Entity\"},{val:\"umlControl\",dispName:\"Control\"}]},{name:\"size\",dispName:\"Height\",type:\"float\",defVal:40,min:0}];mxCellRenderer.defaultShapes.umlFrame.prototype.customProperties=[{name:\"width\",dispName:\"Title Width\",type:\"float\",defVal:60,min:0},{name:\"height\",dispName:\"Title Height\",type:\"float\",defVal:30,min:0}];StyleFormatPanel.prototype.defaultColorSchemes=\n[[{fill:\"\",stroke:\"\"},{fill:\"#f5f5f5\",stroke:\"#666666\",font:\"#333333\"},{fill:\"#dae8fc\",stroke:\"#6c8ebf\"},{fill:\"#d5e8d4\",stroke:\"#82b366\"},{fill:\"#ffe6cc\",stroke:\"#d79b00\"},{fill:\"#fff2cc\",stroke:\"#d6b656\"},{fill:\"#f8cecc\",stroke:\"#b85450\"},{fill:\"#e1d5e7\",stroke:\"#9673a6\"}],[{fill:\"\",stroke:\"\"},{fill:\"#60a917\",stroke:\"#2D7600\",font:\"#ffffff\"},{fill:\"#008a00\",stroke:\"#005700\",font:\"#ffffff\"},{fill:\"#1ba1e2\",stroke:\"#006EAF\",font:\"#ffffff\"},{fill:\"#0050ef\",stroke:\"#001DBC\",font:\"#ffffff\"},{fill:\"#6a00ff\",\nstroke:\"#3700CC\",font:\"#ffffff\"},{fill:\"#d80073\",stroke:\"#A50040\",font:\"#ffffff\"},{fill:\"#a20025\",stroke:\"#6F0000\",font:\"#ffffff\"}],[{fill:\"#e51400\",stroke:\"#B20000\",font:\"#ffffff\"},{fill:\"#fa6800\",stroke:\"#C73500\",font:\"#000000\"},{fill:\"#f0a30a\",stroke:\"#BD7000\",font:\"#000000\"},{fill:\"#e3c800\",stroke:\"#B09500\",font:\"#000000\"},{fill:\"#6d8764\",stroke:\"#3A5431\",font:\"#ffffff\"},{fill:\"#647687\",stroke:\"#314354\",font:\"#ffffff\"},{fill:\"#76608a\",stroke:\"#432D57\",font:\"#ffffff\"},{fill:\"#a0522d\",stroke:\"#6D1F00\",\nfont:\"#ffffff\"}],[{fill:\"\",stroke:\"\"},{fill:mxConstants.NONE,stroke:\"\"},{fill:\"#fad7ac\",stroke:\"#b46504\"},{fill:\"#fad9d5\",stroke:\"#ae4132\"},{fill:\"#b0e3e6\",stroke:\"#0e8088\"},{fill:\"#b1ddf0\",stroke:\"#10739e\"},{fill:\"#d0cee2\",stroke:\"#56517e\"},{fill:\"#bac8d3\",stroke:\"#23445d\"}],[{fill:\"\",stroke:\"\"},{fill:\"#f5f5f5\",stroke:\"#666666\",gradient:\"#b3b3b3\"},{fill:\"#dae8fc\",stroke:\"#6c8ebf\",gradient:\"#7ea6e0\"},{fill:\"#d5e8d4\",stroke:\"#82b366\",gradient:\"#97d077\"},{fill:\"#ffcd28\",stroke:\"#d79b00\",gradient:\"#ffa500\"},\n{fill:\"#fff2cc\",stroke:\"#d6b656\",gradient:\"#ffd966\"},{fill:\"#f8cecc\",stroke:\"#b85450\",gradient:\"#ea6b66\"},{fill:\"#e6d0de\",stroke:\"#996185\",gradient:\"#d5739d\"}],[{fill:\"\",stroke:\"\"},{fill:\"#eeeeee\",stroke:\"#36393d\"},{fill:\"#f9f7ed\",stroke:\"#36393d\"},{fill:\"#ffcc99\",stroke:\"#36393d\"},{fill:\"#cce5ff\",stroke:\"#36393d\"},{fill:\"#ffff88\",stroke:\"#36393d\"},{fill:\"#cdeb8b\",stroke:\"#36393d\"},{fill:\"#ffcccc\",stroke:\"#36393d\"}]];StyleFormatPanel.prototype.customColorSchemes=null;var c=StyleFormatPanel.prototype.init;\nStyleFormatPanel.prototype.init=function(){var k=this.editorUi.getSelectionState();null!=this.defaultColorSchemes&&0Ga.size&&(Ma=Ma.slice(0,Ga.size));Ca=Ma.join(\",\");null!=Ga.countProperty&&(ta.setCellStyles(Ga.countProperty,Ma.length,ta.getSelectionCells()),sa.push(Ga.countProperty),Aa.push(Ma.length))}ta.setCellStyles(Oa,Ca,ta.getSelectionCells());sa.push(Oa);Aa.push(Ca);if(null!=Ga.dependentProps)for(Oa=\n0;OaCa)A=A.slice(0,Ca);else for(var R=A.length;RGa.max&&(wa=Ga.max);var va=null;try{va=\"numbers\"==Ma?wa.match(/\\d+/g).map(Number).join(\" \"):encodeURIComponent((\"int\"==Ma?parseInt(wa):wa)+\"\")}catch(xa){}Z(Oa,va,Ga,null,qa)}var ma=mxEvent.getSource(X);if(!(ma!=$a&&ma!=U||mxEvent.isConsumed(X))){U.innerHTML=\"\";var qa=document.createElement(\"input\");aa(U,qa);qa.value=S(Ca);qa.className=\"gePropEditor\";\"int\"!=Ma&&\"float\"!=Ma||Ga.allowAuto||\n(qa.type=\"number\",qa.step=\"int\"==Ma?\"1\":\"any\",null!=Ga.min&&(qa.min=parseFloat(Ga.min)),null!=Ga.max&&(qa.max=parseFloat(Ga.max)));U.appendChild(qa);mxEvent.addListener(qa,\"keypress\",function(wa){13==wa.keyCode&&da()});qa.focus();mxEvent.addListener(qa,\"blur\",function(){da()})}}))}Ga.isDeletable&&(sa=mxUtils.button(\"-\",mxUtils.bind(ra,function(X){Z(Oa,\"\",Ga,Ga.index);mxEvent.consume(X)})),sa.style.height=\"16px\",sa.style.width=\"25px\",sa.style.float=\"right\",sa.className=\"geColorBtn\",$a.appendChild(sa));\nbb.appendChild($a);return bb}var ra=this,ta=this.editorUi.editor.graph,za=[],Ia=document.createElement(\"table\");Ia.className=\"geProperties geFullWidthElement\";var ya=document.createElement(\"tr\");ya.className=\"gePropHeader\";var Da=document.createElement(\"th\");Da.className=\"gePropHeaderCell\";Da.style.paddingLeft=\"16px\";Da.style.backgroundRepeat=\"no-repeat\";Da.style.backgroundPosition=\"-2px 50%\";Da.style.backgroundSize=\"20px\";mxUtils.write(Da,mxResources.get(\"property\"));ya.style.cursor=\"pointer\";var Va=\nfunction(){var Oa=Ia.querySelectorAll(\".gePropNonHeaderRow\");if(ra.editorUi.propertiesCollapsed){Da.style.backgroundImage=\"url('\"+Editor.arrowRightImage+\"')\";var Ca=\"none\";for(var Ga=k.childNodes.length-1;0<=Ga;Ga--)try{var Ya=k.childNodes[Ga],sa=Ya.nodeName.toUpperCase();\"INPUT\"!=sa&&\"SELECT\"!=sa||k.removeChild(Ya)}catch(Aa){}}else Da.style.backgroundImage=\"url('\"+Editor.arrowDownImage+\"')\",Ca=\"\";for(Ga=0;Ga=this.defaultColorSchemes.length?\"24px\":\"30px\";Da.style.margin=\"0px 6px 6px 0px\";if(null!=ya){var Va=\"1px solid\";null!=ya.border&&(Va=ya.border);if(null!=ya.gradient)Da.style.backgroundImage=\"linear-gradient(\"+mxUtils.getLightDarkColor(ya.fill).cssText+\" 0px,\"+mxUtils.getLightDarkColor(ya.gradient).cssText+\" 100%)\";else if(ya.fill==mxConstants.NONE)Da.style.background=\"url('\"+Dialog.prototype.noColorImage+\"')\";else if(null==ya.fill||\"\"==ya.fill)Da.style.backgroundColor=\nmxUtils.getLightDarkColor(mxUtils.getValue(ta,mxConstants.STYLE_FILLCOLOR,\"#ffffff\")).cssText;else{var Ea=mxUtils.getLightDarkColor(ya.fill);Da.style.backgroundImage=\"linear-gradient(to right bottom, \"+Ea.cssText+\" 50%, \"+Ea.light+\" 50.3%)\"}null==ya.stroke||ya.stroke==mxConstants.NONE?Da.style.border=Va+\" transparent\":\"\"==ya.stroke?Da.style.border=\"1px solid \"+mxUtils.getLightDarkColor(mxUtils.getValue(ta,mxConstants.STYLE_STROKECOLOR,\"#000000\")).cssText:(Ea=mxUtils.getLightDarkColor(ya.stroke),Da.style.border=\nVa+\" \"+Ea.cssText,Da.style.borderRightColor=Ea.light,Da.style.borderBottomColor=Da.style.borderRightColor);null!=ya.title&&Da.setAttribute(\"title\",ya.title)}else Va=mxUtils.getValue(ta,mxConstants.STYLE_FILLCOLOR,\"#ffffff\"),Ea=mxUtils.getValue(ta,mxConstants.STYLE_STROKECOLOR,\"#000000\"),Da.style.backgroundColor=Va,Da.style.border=\"1px solid \"+Ea;Da.style.borderRadius=\"0\";M.appendChild(Da);null!=ya&&null!=ya.gradient&&(Va=Da.cloneNode(!1),Va.style.backgroundImage=\"linear-gradient(light-dark(transparent, \"+\nmxUtils.getLightDarkColor(ya.fill).light+\") 0px, light-dark(transparent, \"+mxUtils.getLightDarkColor(ya.gradient).light+\") 100%)\",Va.style.clipPath=\"polygon(0 100%, 100% 0, 100% 100%)\",Va.style.backgroundColor=\"transparent\",M.appendChild(Va),Va.style.marginLeft=\"-42px\",mxEvent.addListener(Va,\"click\",function(){Da.click()}))});M.innerText=\"\";if(null!=ra)for(var Ia=0;Ia=this.defaultColorSchemes.length?43:23;var ka=document.createElement(\"div\");ka.className=\"geButton\";ka.style.cssText=\"position:absolute;left:0px;bottom:\"+aa+\"px;width:20px;background-image:url(\"+Editor.chevronLeftImage+\");\";mxEvent.addListener(ka,\"click\",mxUtils.bind(this,function(){Y(mxUtils.mod(this.format.currentScheme-1,this.defaultColorSchemes.length))}));var oa=document.createElement(\"div\");oa.className=\"geButton\";oa.style.cssText=\n\"position:absolute;left:186px;bottom:\"+aa+\"px;width:20px;background-image:url(\"+Editor.chevronRightImage+\");\";1=this.defaultColorSchemes.length&&k.appendChild(S)}return k}}Graph.fontMapping={\"https://fonts.googleapis.com/css?family=Architects+Daughter\":'@font-face { font-family: \"Architects Daughter\"; src: url('+\nSTYLE_PATH+'/fonts/ArchitectsDaughter-Regular.ttf) format(\"truetype\"); }'};Graph.customFontElements={};Graph.isGoogleFontUrl=function(k){return k.substring(0,Editor.GOOGLE_FONTS.length)==Editor.GOOGLE_FONTS||k.substring(0,Editor.GOOGLE_FONTS_CSS2.length)==Editor.GOOGLE_FONTS_CSS2};Graph.isCssFontUrl=function(k){return Graph.isGoogleFontUrl(k)};Graph.rewriteGoogleFontUrl=function(k){null!=k&&k.substring(0,Editor.GOOGLE_FONTS.length)==Editor.GOOGLE_FONTS&&(k=Editor.GOOGLE_FONTS_CSS2+k.substring(Editor.GOOGLE_FONTS.length)+\n\":wght@400;500\");return k};Graph.createFontElement=function(k,z){var J=Graph.fontMapping[z];null==J&&Graph.isCssFontUrl(z)?(k=document.createElement(\"link\"),k.setAttribute(\"rel\",\"stylesheet\"),k.setAttribute(\"type\",\"text/css\"),k.setAttribute(\"charset\",\"UTF-8\"),k.setAttribute(\"href\",Graph.rewriteGoogleFontUrl(z))):(null==J&&(J='@font-face {\\nfont-family: \"'+k+'\";\\nsrc: url(\"'+z+'\");\\n}'),k=document.createElement(\"style\"),mxUtils.write(k,J));return k};Graph.addRecentCustomFont=function(k,z){};Graph.addFont=\nfunction(k,z,J,M){if(null!=k&&0mxUtils.indexOf(T.hiddenTags,\nPa),Ba=document.createElement(\"tr\"),Qa=document.createElement(\"td\");Qa.style.align=\"center\";Qa.style.width=\"16px\";var Na=document.createElement(\"img\");Na.setAttribute(\"src\",La?Editor.visibleImage:Editor.hiddenImage);Na.setAttribute(\"title\",mxResources.get(La?\"hideIt\":\"show\",[Pa])+\"\\n\"+mxResources.get(\"shiftClickShowOnly\",[Pa]));mxUtils.setOpacity(Na,La?75:25);Na.className=\"geAdaptiveAsset\";Na.style.verticalAlign=\"middle\";Na.style.cursor=\"pointer\";Na.style.width=\"16px\";if(z||Editor.isDarkMode())Na.style.filter=\n\"invert(100%)\";Qa.appendChild(Na);mxEvent.addListener(Na,\"click\",function(Oa){if(mxEvent.isShiftDown(Oa)){for(var Ca=[],Ga=0;GamxUtils.indexOf(T.hiddenTags,Ca[Ga])){Ya=!1;break}Ya&&0>mxUtils.indexOf(T.hiddenTags,Pa)?T.setHiddenTags([]):T.setHiddenTags(Ca)}else T.toggleHiddenTag(Pa);S();T.refresh();mxEvent.consume(Oa)});Ba.appendChild(Qa);Qa=document.createElement(\"td\");Qa.style.align=\"center\";Qa.style.width=\n\"16px\";Na=document.createElement(\"img\");Na.setAttribute(\"src\",Editor.selectImage);Na.setAttribute(\"title\",mxResources.get(\"select\"));mxUtils.setOpacity(Na,La?75:25);Na.className=\"geAdaptiveAsset\";Na.style.verticalAlign=\"middle\";Na.style.cursor=\"pointer\";Na.style.width=\"16px\";if(z||Editor.isDarkMode())Na.style.filter=\"invert(100%)\";mxEvent.addListener(Na,\"click\",function(Oa){Z(!0);Ta();mxEvent.consume(Oa)});Qa.appendChild(Na);Ba.appendChild(Qa);Qa=document.createElement(\"td\");Qa.style.overflow=\"hidden\";\nQa.style.whiteSpace=\"nowrap\";Qa.style.textOverflow=\"ellipsis\";Qa.style.verticalAlign=\"middle\";Qa.style.cursor=\"pointer\";Qa.setAttribute(\"title\",Pa);a=document.createElement(\"a\");mxUtils.write(a,Pa);a.style.textOverflow=\"ellipsis\";a.style.position=\"relative\";mxUtils.setOpacity(a,La?100:40);Qa.appendChild(a);mxEvent.addListener(Qa,\"click\",function(Oa){if(mxEvent.isShiftDown(Oa))Z(!0),Ta();else if(La&&0mxUtils.indexOf(Y,Ia[ya])&&Y.push(Ia[ya]);Y.sort();\nT.isSelectionEmpty()?aa(Y):aa(Y,T.getCommonTagsForCells(T.getSelectionCells()))}});T.selectionModel.addListener(mxEvent.CHANGE,oa);T.model.addListener(mxEvent.CHANGE,oa);T.addListener(mxEvent.REFRESH,oa);var ra=document.createElement(\"div\");ra.className=\"geToolbarContainer geDialogToolbar\";ra.style.position=\"absolute\";ra.style.display=\"flex\";ra.style.bottom=\"0px\";ra.style.left=\"0px\";ra.style.right=\"0px\";ra.style.height=\"32px\";ra.style.overflow=\"hidden\";ra.style.padding=\"3px 4px 4px 4px\";ra.style.borderWidth=\n\"1px 0px 0px 0px\";ra.style.borderStyle=\"solid\";ra.style.whiteSpace=\"nowrap\";if(T.isEnabled()){ka.style.bottom=\"32px\";var ta=document.createElement(\"a\");ta.className=\"geButton\";var za=ta.cloneNode(!1);za.style.backgroundImage=\"url(\"+Editor.visibleImage+\")\";za.setAttribute(\"title\",mxResources.get(\"reset\"));mxEvent.addListener(za,\"click\",function(Ia){T.setHiddenTags([]);mxEvent.isShiftDown(Ia)||(Y=T.hiddenTags.slice());S();T.refresh();mxEvent.consume(Ia)});ra.appendChild(za);null!=J&&(ta=ta.cloneNode(!1),\nta.style.backgroundImage=\"url(\"+Editor.plusImage+\")\",ta.setAttribute(\"title\",mxResources.get(\"add\")),mxEvent.addListener(ta,\"click\",function(Ia){J(Y,function(ya){Y=ya;oa()});mxEvent.consume(Ia)}),ra.appendChild(ta));ja.appendChild(ra)}null!=M&&ra.appendChild(M);return{div:ja,refresh:oa}};Graph.prototype.getCustomFonts=function(){var k=this.extFonts;k=null!=k?k.slice():[];for(var z in Graph.customFontElements){var J=Graph.customFontElements[z];k.push({name:J.name,url:J.url})}return k};Graph.prototype.setFont=\nfunction(k,z){Graph.addFont(k,z);var J=Editor.guid();document.execCommand(\"fontname\",!1,J);for(var M=this.cellEditor.textarea.getElementsByTagName(\"font\"),S=!1,Z=0;Z'+\nmxUtils.htmlEntities(k)+\"\"};mxGraphView.prototype.redrawEnumerationState=function(k){var z=\"1\"==mxUtils.getValue(k.style,\"enumerate\",0);z&&null==k.secondLabel?(k.secondLabel=new mxText(\"\",new mxRectangle,mxConstants.ALIGN_LEFT,mxConstants.ALIGN_BOTTOM),k.secondLabel.size=12,k.secondLabel.state=k,k.secondLabel.dialect=mxConstants.DIALECT_STRICTHTML,this.graph.cellRenderer.initializeLabel(k,k.secondLabel)):z||null==k.secondLabel||(k.secondLabel.destroy(),k.secondLabel=null);z=k.secondLabel;if(null!=\nz){var J=k.view.scale,M=this.createEnumerationValue(k);k=this.graph.model.isVertex(k.cell)?new mxRectangle(k.x+k.width-4*J,k.y+4*J,0,0):mxRectangle.fromPoint(k.view.getPoint(k));z.bounds.equals(k)&&z.value==M&&z.scale==J||(z.bounds=k,z.value=M,z.scale=J,z.redraw())}};var ha=Graph.prototype.refresh;Graph.prototype.refresh=function(){this.refreshBackgroundImage();ha.apply(this,arguments)};Graph.prototype.refreshBackgroundImage=function(){null!=this.backgroundImage&&null!=this.backgroundImage.originalSrc&&\n(this.setBackgroundImage(this.backgroundImage),this.view.validateBackgroundImage())};var ia=Graph.prototype.loadStylesheet;Graph.prototype.loadStylesheet=function(){ia.apply(this,arguments);this.currentStyle=\"default-style2\"};Graph.prototype.handleCustomLink=function(k,z){\"data:action/json,\"==k.substring(0,17)&&(k=JSON.parse(k.substring(17)),null!=k.actions&&this.executeCustomActions(k.actions,null,z))};Graph.prototype.executeCustomActions=function(k,z,J){if(this.executingCustomActions)this.stoppingCustomActions=\n!0,null!=this.pendingWaitThread&&window.clearTimeout(this.pendingWaitThread),null!=this.pendingExecuteNextAction&&this.pendingExecuteNextAction(),this.fireEvent(new mxEventObject(\"stopExecutingCustomActions\"));else{this.executingCustomActions=!0;var M=!1,S=0,Z=0,aa=mxUtils.bind(this,function(){M||(M=!0,this.model.beginUpdate())}),T=mxUtils.bind(this,function(){M&&(M=!1,this.model.endUpdate())}),Y=mxUtils.bind(this,function(){0mxUtils.indexOf(oa.tags.visible,Ia[ta])&&0>mxUtils.indexOf(za,Ia[ta])&&za.push(Ia[ta])}null!=za&&this.setHiddenTags(za);this.refresh()}0k.excludeCells.indexOf(z[M].id)&&J.push(z[M]);z=J}return z};Graph.prototype.getCellsById=function(k){var z=[];if(null!=k)for(var J=0;Jz?this.hiddenTags.push(k):0<=z&&this.hiddenTags.splice(z,1);this.fireEvent(new mxEventObject(\"hiddenTagsChanged\"))};\nGraph.prototype.isAllTagsHidden=function(k){if(null==k||0==k.length||0==this.hiddenTags.length)return!1;k=k.split(\" \");if(k.length>this.hiddenTags.length)return!1;for(var z=0;zmxUtils.indexOf(this.hiddenTags,k[z]))return!1;return!0};Graph.prototype.getCellsForTags=function(k,z,J,M){var S=[];if(null!=k){z=null!=z?z:this.model.getDescendants(this.model.getRoot());for(var Z=0,aa={},T=0;T=k.length)){for(var ka=ja=0;kamxUtils.indexOf(S,T)&&(M=0?-=[];'./,\\n\\t\";EditorUi.templateFile=TEMPLATE_PATH+\"/index.xml\";EditorUi.cacheUrl=window.REALTIME_URL;null==EditorUi.cacheUrl&&\"undefined\"!==typeof DrawioFile&&(DrawioFile.SYNC=\"none\");Editor.cacheTimeout=1E4;EditorUi.enablePlantUml=EditorUi.enableLogging||\n\"1\"==urlParams.dev;EditorUi.isElectronApp=null!=window&&null!=window.process&&null!=window.process.versions&&null!=window.process.versions.electron;EditorUi.nativeFileSupport=!mxClient.IS_OP&&!EditorUi.isElectronApp&&\"1\"!=urlParams.extAuth&&\"showSaveFilePicker\"in window&&\"showOpenFilePicker\"in window;EditorUi.enableDrafts=!mxClient.IS_CHROMEAPP&&isLocalStorage&&\"0\"!=urlParams.drafts;EditorUi.scratchpadHelpLink=\"https://www.drawio.com/doc/faq/scratchpad\";EditorUi.enableHtmlEditOption=!0;EditorUi.mermaidDiagramTypes=\n\"flowchart classDiagram sequenceDiagram stateDiagram mindmap graph erDiagram requirementDiagram journey gantt pie gitGraph\".split(\" \");EditorUi.defaultMermaidConfig={theme:\"neutral\",arrowMarkerAbsolute:!1,sequence:{diagramMarginX:50,diagramMarginY:10,actorMargin:50,width:150,height:65,boxMargin:10,boxTextMargin:5,noteMargin:10,messageMargin:35,mirrorActors:!0,bottomMarginAdj:1,useMaxWidth:!0,rightAngles:!1,showSequenceNumbers:!1},gantt:{useWidth:1E3,useMaxWidth:!0,titleTopMargin:25,barHeight:20,barGap:4,\ntopPadding:50,leftPadding:75,gridLineStartPadding:35,fontSize:11,fontFamily:'\"Open-Sans\", \"sans-serif\"',numberSectionStyles:4,axisFormat:\"%Y-%m-%d\"}};EditorUi.logError=function(c,g,l,t,B,E,H){if(null!=c){B=null!=B?B:Error(c);B.stack=null!=B.stack?B.stack:\"\";E=null!=E?E:0>c.indexOf(\"NetworkError\")&&0>c.indexOf(\"SecurityError\")&&0>c.indexOf(\"NS_ERROR_FAILURE\")&&0>c.indexOf(\"out of memory\")?\"SEVERE\":\"CONFIG\";try{EditorUi.enableLogging&&\"1\"!=urlParams.dev&&c!=EditorUi.lastErrorMessage&&0>c.indexOf(\"extension:\")&&\n0>c.indexOf(\"ResizeObserver loop completed with undelivered notifications\")&&0>B.stack.indexOf(\"extension:\")&&0>B.stack.indexOf(\":\")&&0>B.stack.indexOf(\"/math4/es5/\")&&0>B.stack.indexOf(\"blob:\")&&(0>c.indexOf(\"strict mode\")||0>B.stack.indexOf(\"extensions.min.js\"))&&(EditorUi.lastErrorMessage=c,(new Image).src=(null!=window.DRAWIO_LOG_URL?window.DRAWIO_LOG_URL:\"\")+\"/log?severity=\"+E+\"&v=\"+encodeURIComponent(EditorUi.VERSION)+\"&msg=clientError:\"+encodeURIComponent(c)+\":url:\"+encodeURIComponent(window.location.href)+\n\":lnum:\"+encodeURIComponent(l)+(null!=t?\":colno:\"+encodeURIComponent(t):\"\")+(\"\"!=B.stack?\"&stack=\"+encodeURIComponent(B.stack):\"\"))}catch(K){}try{H||null==window.console||console.error(E,c,g,l,t,B)}catch(K){}}};EditorUi.logEvent=function(c){if(\"1\"==urlParams.dev)EditorUi.debug(\"logEvent\",c);else if(EditorUi.enableLogging)try{var g=null!=window.DRAWIO_LOG_URL?window.DRAWIO_LOG_URL:\"\";(new Image).src=g+\"/images/1x1.png?v=\"+encodeURIComponent(EditorUi.VERSION)+(null!=c?\"&data=\"+encodeURIComponent(JSON.stringify(c)):\n\"\")}catch(l){}};EditorUi.sendReport=function(c,g){if(\"1\"==urlParams.dev)EditorUi.debug(\"sendReport\",c);else if(EditorUi.enableLogging)try{g=null!=g?g:5E4,c.length>g&&(c=c.substring(0,g)+\"\\n...[SHORTENED]\"),mxUtils.post(\"/email\",\"version=\"+encodeURIComponent(EditorUi.VERSION)+\"&url=\"+encodeURIComponent(window.location.href)+\"&data=\"+encodeURIComponent(c))}catch(l){}};EditorUi.debug=function(){try{if(null!=window.console&&\"1\"==urlParams.test){for(var c=[(new Date).toISOString()],g=0;g
';EditorUi.prototype.emptyLibraryXml=\"[]\";EditorUi.prototype.isDiagramDataEmpty=function(c){if(null==c||0==c.length||c==this.emptyDiagramXml)return!0;try{var g=mxUtils.parseXml(c).documentElement;c=function(K){return null==K||2>=K.getElementsByTagName(\"mxCell\").length};if(\"mxGraphModel\"==g.nodeName)return c(g);if(\"mxfile\"==g.nodeName){var l=g.getElementsByTagName(\"diagram\");if(0==l.length)return!0;\nfor(g=0;g=E.keyCode)||t.isSelectionEmpty()||mxEvent.isAltDown(E)||mxEvent.isShiftDown(E)||mxEvent.isControlDown(E)||mxClient.IS_MAC&&mxEvent.isMetaDown(E)?l.apply(this,\narguments):null}}return g};var f=EditorUi.prototype.extractGraphModelFromHtml;EditorUi.prototype.extractGraphModelFromHtml=function(c){var g=f.apply(this,arguments);if(null==g)try{var l=c.indexOf(\"<mxfile \");if(0<=l){var t=c.lastIndexOf(\"</mxfile>\");t>l&&(g=c.substring(l,t+15).replace(/>/g,\">\").replace(/</g,\"<\").replace(/\\\\"/g,'\"').replace(/\\n/g,\"\"))}else{var B=mxUtils.parseXml(c),E=this.editor.extractGraphModel(B.documentElement,null!=this.pages||\"hidden\"==this.diagramContainer.style.visibility);\ng=null!=E?mxUtils.getXml(E):\"\"}}catch(H){}return g};EditorUi.prototype.validateFileData=function(c){if(null!=c&&0');0<=g&&(c=c.slice(0,g)+''+c.slice(g+23-1,c.length));c=Graph.zapGremlins(c)}return c};EditorUi.prototype.replaceFileData=function(c,g){EditorUi.debug(\"EditorUi.replaceFileData\",[this],\"data\",[c],\"patches\",g);c=this.validateFileData(c);c=null!=c&&0\\n':\">\")+\"\\n\\n\"+(null==B?null!=l?\"\"+mxUtils.htmlEntities(l)+\"\\n\":\"\":\"draw.io\\n\")+\n(null!=B?'\\n\":\"\")+\"\\n':\">\")+'\\n
\\n
'+t+\"
\\n
\\n\"+(null==B?'`; +} diff --git a/docs/design-evidence/2026-11-01/attachment-preview/README.md b/docs/design-evidence/2026-11-01/attachment-preview/README.md new file mode 100644 index 00000000000..8937b1513dc --- /dev/null +++ b/docs/design-evidence/2026-11-01/attachment-preview/README.md @@ -0,0 +1,182 @@ +# 附件预览验证记录 + +2026-11-01。测试均为本任务隔离测试版与开发模拟数据;图片/视频/PDF/模型是实际媒体字节,截图不含真实账号或任务内容。没有把原型截图作为运行证据。 + +## 实际运行 + +| 平台 | 实际路径与结果 | 证据 | +| --- | --- | --- | +| macOS Desktop | 原任务输入托盘添加 WebP / MP4 / PDF;视频真实解码 384×240,切到 PDF 完整适应 74%,关闭后待发附件仍在 | [视频](desktop-video.png)、[PDF](desktop-pdf.png) | +| iOS 27 / iPhone 18 Pro | 普通任务六项混合集合;图片→视频→PDF;视频比例正确、常显关闭/更多;关闭返回同一消息 | [视频](ios-video.jpg)、[PDF](ios-pdf.jpg)、[返回](ios-return.jpg) | +| Android API 36 / arm64 | 同一混合集合原生视频与 PDF;PDF 实际文字和七色方块;图片放大、画笔、未保存退出询问及放弃后原消息保留 | [PDF](android-pdf.jpg)、[图片](android-image.jpg)、[未保存保护](android-annotation.jpg) | +| iOS / Android 文件页 | 真实 PDF 缩放、原生/网页视频播放、离线 glTF 蓝色立方体、Mermaid;iOS 文本查找与两种主题 | [Android 模型](android-model.jpg) | + +Desktop 使用本任务隔离 userData,CDP 9339;Mobile 使用本任务 Metro 8081,原生包 com.xd.cindy。iOS 使用与当前 native fingerprint 匹配的开发包;Android 从本工作树重新 prebuild / assembleDebug / 安装。运行后对源码标识重新核对;完整本地工具回执留在任务输出,不公开个人目录。 + +Desktop 单页检查采用真实 PreviewHeader / PreviewFooter 与主题组件,800×600、light/dark、zh-CN/en/de 共 6 场景通过。这一检查只证明公共外壳的布局,不冒充完整播放器或原生设备测试;完整 Desktop 操作由上表另行验证。 + +## 入口覆盖 + +| 消费者 | 实现 | 验证层次 | +| --- | --- | --- | +| 普通任务、伙伴复用的任务页 / 消息附件 | 公共集合和媒体/文件分派 | 三端任务实际运行;移动 117 条手势/标注回归、48 条消息集合回归 | +| 群主时间线、话题 / 服务器附件 | 同一群附件组件、每次来源授权、显式集合 | 22 条授权/加载/迟到结果回归;真实 Chat Server 账号来源补充验收未完成 | +| 新任务/已有任务待发附件 | 共用草稿预览,移除独立,图片保留标注 | Desktop 托盘实际运行;手机接线类型检查,完整发送/草稿跨页恢复补充验收未完成 | +| 文件浏览 / 伙伴成果 | 成果文件打开既有文件页;类型渲染器共享 | 手机文件页实际运行;伙伴成果远程任务数据补充验收未完成 | +| 插件媒体 / 富媒体 / 过程图片 | 保留来源授权,经已有媒体组件接标准外壳 | 插件画廊与媒体回归;真实插件授权资源补充验收未完成 | +| 远程电脑 / SSH | 保留既有来源解析与只读读取路径 | 源码追踪;双设备和 SSH 实际传输补充验收未完成 | + +## 自动检查及测试价值 + +Desktop 116 条(媒体原件操作 50、PDF 15、图片标注 13、文本 32、行位置 6)通过;Mobile 206 条中首次 205 通过,新增集合测试误读字段后更正,48 条所在文件重新全过;后续群附件和 Android PDF 路由 23 条再次通过。Desktop / Mobile 类型检查、术语检查、测试卫生与 diff 空白检查记录在 PR。 + +- `lightboxMediaActions.test.ts`:真实媒体动作依赖注入,最小输入为非图片原件;保护保存 MIME/扩展名,不把文件改名成 PNG。单文件用例 16ms。 +- `PdfPreview.test.tsx`:生产 PDF 组件,初始适应可用画布并保留读页/缩放;15 条 109ms。 +- `ImageLightboxAnnotate.test.tsx`:生产灯箱,保留 Esc 未保存保护;13 条 709ms。 +- `textLightbox.test.ts`:移除被公共外壳替代的私有卡片源码断言,保留只读、路径、截断行为约束;32 条 4ms。 +- `imageLightboxInteraction.test.tsx`:真实生产交互处理器,更新标准工具栏选择器并保护原缩放/拖动/标注;117 条 442ms。 +- `botGroupAttachmentLightbox.test.tsx`:生产群附件,最小失败输入包括授权拒绝、旧返回、PDF;新分派保持逐次授权;22 条 147ms。 +- `messageGallery.test.ts`:图片 + PDF + MP4 的实际规范化消息,证明集合包含该消息不同类型且顺序稳定;48 条 22ms。 +- `filePreviewAndroidPdf.test.tsx`:生产 Android 文件路由实际选择内嵌 PDF,原件仍可分享;WebView 解码由设备实测覆盖,路由测试不伪装解码。1 条 31ms。 +- `pdfPreviewSource.test.ts` / `pdfSystemOpen.test.ts`:本地原件准备、取消、释放与系统后备策略;共享缓存/临时文件生命周期,不增加同实现常量镜像。 + +以上时间为已运行文件的测试用例耗时,启动/编译由批次共用;完整套件交 CI。 + +## 补充验收边界 + +尚未完成 Windows、iPad/Android 平板、横屏/大字/读屏全矩阵、真实跨设备/SSH/Chat Server/插件账号闭环,以及全部格式的损坏/密码/超大文件设备样本。代码路径存在不等于这些场景已验收;这些补充项约束整体发布/结案,不在本 PR 中删除或改列非目标。不得把当前核心通过说成全平台全来源已验收。 + + +## PR #8356 审查修复(2026-11-01) + +5 条意见均核对为可达的实质回归,并在同一批修复: + +- 已标注图片切回时:集合仍以烧录图定位,但使用原图和矢量笔迹编辑;原图读取失败才退回烧录图。回归实际切换集合、撤销旧笔迹并检查原图仍在。 +- 直接音频:HTTPS 和 `data:audio/` 不再送入只接受宿主资源的 resolver;保留音频类型,标题没有扩展名也使用播放器。单测执行生产 resolver,确认直接源不取件,宿主源仍授权并传取消信号。此轮没有在设备上播放直接音频,播放器沿用既有实现;不将 resolver 单测说成音频设备实测。 +- 图表未完成/语法错误:保留文档外壳;测试分别让 Mermaid 停在 parse 和拒绝 parse,Esc / 关闭均能退出。Desktop 实际读取错误 `.mmd`、点关闭并验证焦点返回:[截图](review-mermaid-error.png)。 +- 行号文件引用:集合传入起始行,关闭后由查看器焦点作用域返回原入口,不返回已经卸载的前一个附件。Desktop 当前代码实际滚到第 70 行(scrollTop 1682),切错误图表后关闭,焦点返回成功。 +- PDF 适配:初次读取、缩放复位和尺寸变化统一按可用宽高计算,保留页面比例。iOS 与 Android 的实际混合消息 PDF 横屏查看、放大再适配均完整显示:[iOS](review-ios-landscape.jpg)、[Android](review-android-landscape.jpg)。 + +最小失败复验:暂时恢复上述错误分支后,Desktop 新增用例 4 个失败、Mobile 4 个失败;恢复修复后 Desktop 56 条 / Mobile 34 条定向回归通过。Desktop / Mobile 类型检查通过。测试卫生通过;设计台账由正式生成器更新。 + +新增/扩展测试价值: + +| 文件 | 最小输入与可观察结果 | 缺口 / tier / 单文件耗时 | +| --- | --- | --- | +| `TextLightboxInteraction.test.tsx` | 两文件集合 + 第 3 行引用;切文件后关闭返回按钮;Mermaid pending / invalid 仍可关闭 | 原文本测试只覆盖静态合同和行锚辅助函数;新增真实组件 DOM 生命周期,unit;2.59 秒 | +| `ImageLightboxAnnotate.test.tsx` | 从另一图切回带旧笔迹的图片,原图显示,撤销后旧笔迹消失 | 原测试仅直接打开单图,补生产集合入口;unit;2.98 秒 | +| `messagePayload.test.ts` | HTTPS/data 音频直接返回;宿主音频继续 resolver + AbortSignal | 原测试只格式化 payload;补生产来源分派,unit;0.919 秒 | +| `pdfWebViewHtml.test.ts` | 390×640 和 760×240 的竖版 PDF;完整适配、放大后复位、resize 后重新适配 | 路由/缓存测试不执行 WebView 文档;生产 HTML 在 VM 执行,引擎注入,unit;0.211 秒 | +| `scripts/__tests__/design-inventory.test.mjs` 的圆角用例 | 独立 JSX 样本含一个 class 和一个内联圆角、一个注释;生产生成器数为 2 | CI 两个失败 job 均因固定旧 ImageLightbox 圆角数量;改为验证统计规则,不固定产品几何数量,integration 定向用例;0.046 秒 | + +独立样本替代旧界面圆角计数不修改扫描器、台账口径或测试基线。没有扩展到与本目标无关的功能。 + +## CI 分片修复(2026-11-01,继 c49c27d6) + +回读旧 head e57cc5e6 的完整 Linux/Windows 四分片:附件相关失败命中 20 个测试文件;另有 Windows agent-island 三个 60 秒超时,代码未被本 PR 改动、范围外,另开 [#8361](https://github.com/makecindy/cindy/issues/8361),未弱化其断言或延长超时。Desktop Git integration / verify-checks / Windows runner and database checks 的同一个 design-inventory 固定旧圆角数量问题已由 c49c27d6 修复。 + +本轮保留批准的应用内 PDF、更多菜单、主题背板和焦点约束;更新旧行为断言及 Node 环境的原生预览替身。复制、标注烧录顺序、本地/远程/SSH 来源、错误分类、菜单键盘、群聊草稿、缓存页索引、可见性取件和重新授权断言仍验证实际生产调用。 + +发现并修复的实质问题: + +- 图表打开时先聚焦公共外壳,避免自动聚焦工具按钮后 Tooltip 消耗第一次 Esc。桌面实际生产组件打开图表,确认焦点在外壳,一次 Esc 即关闭。 +- 文本公共阅读器保留原 JSON 的 10,000 行、100,000 字符、单行 4,096 字符预算;截断提示可见,复制仍取完整原文。禁用预算后,三个原有截断用例均失败。 +- 长文档匹配跳转:RN FlatList 将跳转钳制在已量出的尾部,实测查找 9999 却停在前面行。改用项目已有的 LegendList,以估高与实际行高定位,保留虚拟化、换行和行号;删除失败滚动重试路径。 +- 电脑群附件缺少 url/path 时恢复原有明确提示;集合切换到同一类条目也保留提示与当前预览。直接相关群聊测试通过;没有真实账号群的设备实测,不把这项说成已实测。 +- 移动端图标 20px 改用现有 iconSize.action(数值不变)。两处原表达式仅恢复格式;没有改变任务行为。 + +设备证据:Desktop 图表与 iOS 浅色 / Android 深色源阅读器均在本工作树的隔离测试版运行。手机使用开发 mock 中 10,001 行的 large.json;验证截断、查找第 10,000 行、滚动与关闭,测试数据不写正式库。其它 Windows、平板和真实远程/SSH/ChatServer/插件账号的补充验收缺口沿用前文,不宣称整体可合并。 + +测试价值(复用原有文件,不新增基线豁免):BotGroupTextAttachmentPreview 的预算用例原本验证旧专用阅读器,现在经公共生产阅读器;256,000 字符一行仍只能显示 4,096 字符但复制完整原文。新增一个 unit 用例验证查找 9999 到生产列表调用 index=9999,实际滚动由两端截图验证。文件单独运行 1.21 秒(19 个用例,测试执行 0.087 秒)。其它修改均为已有场景的交互契约/平台替身更新,保留原风险断言。 + +本轮直接验证合计:Desktop 6 文件 287 用例;Mobile 14 文件 450 用例;两个包 typecheck、design-inventory 生成检查与定向生成器测试、test-hygiene、DCO 均通过。没有运行无关全量。静态接线检查只容忍等价换行/括号,未放松调用顺序与条件。 + +截图:[Desktop 图表初始焦点](ci-desktop-mermaid-focus.png)、[iOS 截断](ci-ios-text-cap.jpg)、[iOS 第 10,000 行](ci-ios-text-search.jpg)、[Android 截断](ci-android-text-cap.jpg)、[Android 第 10,000 行](ci-android-text-search.jpg)。iOS 浅色、Android 深色;这不等于所有端/主题的完整矩阵均已实测。测试实例退出并保留隔离数据与登录态,本轮仅使用合成 mock 任务,未新增真实伙伴、真实任务或群记录。 + +已有失败文件的定向执行记录(复用原文件,无新文件): + +| 文件 | 原风险/保留的入口 | 测试执行耗时 | +| --- | --- | --- | +| `src/__tests__/BotGroupChatScreen.test.tsx`(146 例) | 真实群屏与取件回调,包含缺地址反馈、视口取件与按 ID 授权 | 1399ms | +| `src/__tests__/BotGroupMessageAttachments.test.tsx`(14 例) | 已有场景按统一预览的入口/原生边界更新,保留原行为断言 | 105ms | +| `src/__tests__/BotGroupTextAttachmentPreview.test.tsx`(19 例) | 公共源阅读器预算、原文复制、远行查找;unit 最小输入见上文 | 90ms | +| `src/__tests__/blurBackdropScrim.test.ts`(5 例) | 已有场景按统一预览的入口/原生边界更新,保留原行为断言 | 41ms | +| `src/__tests__/chatFileChipMenuModel.test.ts`(6 例) | 已有场景按统一预览的入口/原生边界更新,保留原行为断言 | 4ms | +| `src/__tests__/designTokenDiscipline.test.ts`(3 例) | 已有场景按统一预览的入口/原生边界更新,保留原行为断言 | 790ms | +| `src/__tests__/fileChipNameWidth.test.tsx`(7 例) | 已有场景按统一预览的入口/原生边界更新,保留原行为断言 | 39ms | +| `src/__tests__/filePreviewCacheRendering.test.tsx`(4 例) | 缓存页面与公共集合栏索引,刷新/失败仍保留选择 | 32ms | +| `src/__tests__/messageMediaThumbnailWiring.test.ts`(12 例) | 已有场景按统一预览的入口/原生边界更新,保留原行为断言 | 46ms | +| `src/__tests__/messageSourceLabelsRender.test.tsx`(37 例) | 已有场景按统一预览的入口/原生边界更新,保留原行为断言 | 255ms | +| `src/__tests__/newSession.test.ts`(173 例) | 已有场景按统一预览的入口/原生边界更新,保留原行为断言 | 525ms | +| `src/__tests__/pluginAttachmentBubble.test.tsx`(16 例) | 已有场景按统一预览的入口/原生边界更新,保留原行为断言 | 174ms | +| `src/__tests__/sessionComposerDesktopFirst.test.ts`(7 例) | 已有场景按统一预览的入口/原生边界更新,保留原行为断言 | 49ms | +| `src/renderer/__tests__/chatImageView.test.ts`(38 例) | 已有场景按统一预览的入口/原生边界更新,保留原行为断言 | 309ms | +| `src/renderer/__tests__/chatMediaLightboxFocus.test.ts`(3 例) | 已有场景按统一预览的入口/原生边界更新,保留原行为断言 | 604ms | +| `src/renderer/__tests__/chatVideoView.test.ts`(3 例) | 已有场景按统一预览的入口/原生边界更新,保留原行为断言 | 217ms | +| `src/renderer/__tests__/openPathFeedback.test.ts`(5 例) | 生产 IPC 错误分类与应用内 PDF 分派 | 3ms | +| `src/renderer/components/chat/__tests__/imageClipboardInteractions.test.tsx`(20 例) | 真实更多菜单触发复制/保存/外部打开,保留原件与标注顺序 | 1025ms | +| `src/renderer/features/bots/__tests__/BotGroupChatView.test.tsx`(218 例) | 真实群聊外壳的 Esc、焦点、草稿与 PDF 分派;原文件较大,未新增等待 | 17580ms | + + +## verify-checks 守卫补充(2026-11-01,继 98d47ccc76) + +回读 c49c27d6 的 run 41318261982 / job 124882829110,新增失败来自 Desktop `unsupportedTextPreview` 与 Mobile `filePreviewPagerWiring`、`historyWindowBackfillWiring` 三个旧守卫文件。当前 head 定向复现后采纳: + +- Desktop 保留“非文本不能当文本解码”的分类与全部入口检查;把已退役的“PDF/图片/压缩包自动离开 Cindy”源码断言改为执行生产分派,确认非空文件进入应用内、空地址拒绝、不调用系统打开。 +- 文本浏览补回既有 `filePreview.sourceReady` 自动验收标记,只交给已加载且可见的公共阅读器;加载、失败、屏外预取页均不满足该标记。仅增加 testID,没有改变读取、布局或用户操作,沿用本轮之前的两端阅读器实测,不重新启动设备。 +- PDF 稳定性守卫移到实际 PDF.js 公共组件,继续约束文档来源/重试身份以及普通翻页、缩放不能改变文档生成;HTML/Markdown 的手势归属断言保留,在前面增加模型/图表手势保护后不再按旧整行字符串误报。 +- 三处原有表达式/类型仅恢复等价格式(历史补齐 seq 清理、状态类型、HTML 手势页 key),保留所有取消、互斥与授权守卫,不改变历史补齐行为。 + +定向验证:Desktop 3 用例(文件运行 0.419 秒 / 测试执行 0.006 秒)、Mobile 50 用例(两文件运行 1.98 秒 / 测试执行 0.111 秒)通过。只复用原有测试,不新增 guard 文件或基线豁免;测试依据分别是已批准的应用内附件预览、Maestro 就绪协议、PDF 手势稳定与历史补齐既有不变量。未运行无关全量,也未重开测试实例。 + +两个受影响包 typecheck、test-hygiene、design-inventory 检查均通过;当前批次自审无未处置问题。 + +## verify-checks 等价格式恢复(2026-11-01,继 aceaa390) + +当前 head 的 run 41322360581 / job 124895184630 中,Mobile guard 共 483 例,仅 3 例失败,涉及 `messageNativeLifecycleContracts` 与 `optimisticSessionComposer`。本地同样复现 3 失败、26 通过。原因是精确源码检查与原有表达式的括号/换行格式不一致:正文可选择判断的外层括号、恢复同步状态的单行类型、草稿回收回调的单参数写法。 + +恢复这三处等价的原写法,不修改测试、不删除断言、不改变可选择条件、同步退避或后续消息回收。两个原有文件定向复验 29 例全通过(总运行 0.591 秒,测试执行 0.180 秒);Mobile typecheck 通过,diff 自检通过。未运行无关全量。此次仅恢复等价格式,没有运行行为或 UI 变化,沿用前述实测证据,不重启 Desktop 或模拟器;原补充验收缺口保持明确。 + +## Linux 分片 1 修复(2026-11-02,继 8a7c11ae) + +Run 41323484318 / job 124898531519 的 Mobile unit 5 例失败来自两个文件。本地定向复现同样为 5 失败、11 通过,分别处置: + +- `sessionHeaderDesktopFirst` 的 3 个失败均为本 PR 原格式化导致的精确源码匹配差异。恢复原生标题分支、队列关注图标和系统栏任务列表按钮的原等价写法,保留全部既有断言与界面逻辑。 +- `TextDocumentPreview` 的直接 RN TextInput 绕过公共输入组件,改用 AppText 的 TextInput,恢复现有全局字号限幅及主题光标/选择色;代码文字按现有移动规范的等宽代码角色使用 `textStyles.bodySmall`(15/20)。未增加 token 或改变读取/查找/虚拟化预算。 +- 排版扫描误扫生成的 PDF.js / draw.io 引擎 JS 字符串。按已有 `richContentAssets.generated.ts` 的边界,仅跳过准确路径 `src/session/documentPreviewAssets.generated.ts`;该文件由正式脚本生成、全部为第三方引擎与资产数据,不是 RN 样式。业务阅读器与自行生成的 WebView HTML 仍接受原排版检查,没有删除规则或调整基线。 + +验证:3 个直接相关测试文件共 35 例通过(总运行 1.48 秒,测试执行 0.969 秒);Mobile typecheck、test-hygiene、design-inventory、diff 检查通过。没有跑无关全量。原群聊阅读器测试只补公共 TextInput 的原生替身导出,继续执行真实生产阅读器及查找跳转、截断、完整原文复制等原断言,未新增用例。测试价值:排版守卫保护 mobile-design-guide §3 的公共排版和 AppText 契约;此前唯一覆盖缺口为把新生成 vendor 引擎字符串误判为 RN 样式。最小失败输入为上述生成文件的 JS 字符串及真实阅读器的 RN 值导入;沿用原静态扫描文件,不新加 guard/豁免业务组件。该文件测试执行 0.806 秒,群聊阅读器文件 0.120 秒。 + +本工作树 Metro `8081` 的归属与 native fingerprint 核对通过(global / mock)。iOS 浅色与 Android 深色实际打开 10,001 行 large.json,看到截断提示、规范代码字号;查找 9999 后跳至第 10,000 行,公共输入组件能输入并正确统计 1/1,关闭返回原任务。iOS 是 C595F130-633C-482D-B08B-D03D195367EF;Android 是插件设备 2CFB5602-E3B5-4C07-A0BF-22C4CF344843 / 精确 AVD 核验后的 emulator-5554。截图:[iOS](ci-typography-ios-search.jpg)、[Android](ci-typography-android-search.jpg)。这只是本轮改动验收,不替代原文明确的完整来源、Windows、平板等补充缺口。仅用原合成 mock,未创建真实任务或伙伴。两端 App、模拟器和本任务 Metro 正常退出,保留数据。 + +## 主干冲突处理(2026-11-02,继 10fc0b06) + +同步 upstream/main `19dad9c22542eb36b10ca31d14b61ad934068502`。唯一冲突文件为 `docs/design-rules/design-inventory.md`,位置在 GENERATED 机器统计区;主干与附件预览分支分别新增可达组件及排版事实,不能任选一侧的旧统计。 + +用正式 `pnpm design:inventory` 在合并后的源码树重建,`check:design-inventory` 通过(59 个 surface),连续第二次生成字节不变。逐字比对非 GENERATED 内容,确认与两个 parent 都一致,没有丢失人工维护的设计决策。语言文件非重叠条目自动合并,不手工覆盖;本轮没有手工改变任何业务源码、产品取舍、读取来源或权限。 + +本轮只处理生成台账及保留主干更新;代码冲突为零,不重启 App/模拟器。原运行证据与补充验收缺口继续保留。上轮颜色扫描超时的独立 issue #8369 仍未解除,不因解决合并冲突宣称 CI 已通过。 + +合并树定向验证:`node --test scripts/__tests__/design-inventory.test.mjs` 52 例通过(85.44 秒,保留全文件原有 fixture 与真实路由校验);`check:i18n` 的 12 种语言 14,766 个 key 结构一致;Desktop 与 Mobile 类型检查均通过;两次正式生成一致、无冲突标记、diff 检查通过。没有新增/改写测试,没有扩大为整仓全量。 + +## 后续主干冲突处理(2026-11-02,继 dc420ea3) + +纳入 upstream/main `156152364a38dfe172a1df50fca157485191b768`:主干新增群聊引用对齐与登录翻译调整,唯一冲突仍在设计台账的 GENERATED 机器区。正式生成器按合并树重建,第二次生成字节一致;非 GENERATED 内容与两个 parent 逐字相同,无未解决标记。没有手工改业务源码、样式或文案,未改权限或资源读取。 + +主干的群聊测试与附件预览测试非重叠合并;查看其新增断言,保留缺失原消息反馈和取消引用不丢草稿。合并树的 `BotGroupChatScreen` 146 例通过(文件运行 4.73 秒,测试执行 1.511 秒);生成器只定向执行“连续两次生成一致”和“保留人工区”两例,均通过(0.047 秒),不重复上轮已跑的 52 例。设计台账 59 surfaces 最新、12 语言14,766key结构一致。无新增测试或门禁豁免,无全量测试,无测试 App/模拟器启动。原运行证据和补充验收缺口保留;既有 #8369 扫描超时未在本轮改动。 + +合并树 Desktop / Mobile 两个包类型检查与 diff 检查均通过;保留新 head CI 的真实结论,不因冲突解决而把此前颜色扫描阻塞计为通过。 + +## 正文渲染冲突处理(2026-11-02,继 2283e8b9) + +同步 upstream/main `db723ff786c9b5e8c9e628fab277ff01f38dc185`。唯一冲突文件为 Mobile `MessageRenderer.tsx` 的 3 处正文格式分支:原分支的 JSX 换行与主干 #7726 的静态文本优化重叠。逐段保留主干的 `StaticSpan`、原生 UITextView 子节点及显式字号缩放限幅;普通文字在允许继承时直接返回文本,强调/删除线等不再订阅交互手势上下文。链接、文件路径、图片/视频仍使用原有可交互 span 或附件预览入口,没有回退统一集合、直接音频路由或来源授权。 + +合并树直接回归:`messageSourceLabelsRender` 38 例、`messageNativeLifecycleContracts` 5 例、`messageMarkdown` 137 例,共 180 例通过(运行 3.64 秒,测试执行 0.469 秒)。Mobile 类型检查及设计台账检查通过,台账仍为 59 surfaces 且无需重写。没有新增或改写测试;主干消息来源/正文断言自动合并后原样通过。 + +验收前原生指纹核对发现此前设备包已被主干更新淘汰,未使用旧包作验收。iOS 通过正式 rebuild 入口准备当前原生包,Android 按同一 global / mock Expo 环境 prebuild + assembleDebug;两端安装前后核对实际产物/指纹和 Metro 8081 的本工作树归属。实测结果在下文记录,不把构建成功当成运行完成。 + +运行验收:当前原生包的整包安装校验确认与本机产物一致,iOS `native-matched`,Android 构建 APK 内嵌指纹与当前 Expo Updates fingerprint 相同;Metro 的本工作树身份刷新为 `target-fresh` 后才记录验收。iOS 构建中编译器曾输出 Swift 模块诊断,后自动恢复,正式构建入口以 0 退出并通过指纹校验;没有清数据或改工具链源码。 + +iOS 浅色与 Android 深色均实际打开现有 rich-answer mock:正文强调、表格、行内代码及代码块正常显示。Android 文字定位等待未命中,返回截图已清晰显示目标正文,随后以独立截图逐项确认,未把文字定位工具的超时当作 UI 渲染失败或通过凭证。两端随后从现有混合消息打开 `code.json`,看见原文与 4/7 集合位置;切到 PDF 3/7,确认页面实际加载(4 个绿方块、3 个紫方块、总数 7),点击关闭返回同一条含附件的消息。没有新建真实任务或伙伴。 + +截图:[iOS 正文](merge3-ios-markdown.jpg)、[iOS 原文](merge3-ios-code.jpg)、[iOS PDF](merge3-ios-pdf.jpg)、[Android 正文](merge3-android-markdown.jpg)、[Android 原文](merge3-android-code.jpg)、[Android PDF](merge3-android-pdf.jpg)。这里只验证本轮冲突涉及的主干正文优化与现有附件入口共存,原完整格式/来源/设备矩阵缺口继续保留,不宣称所有环境均已验收。两端 App、模拟器及本任务 Metro 已正常退出,保留数据。 diff --git a/docs/design-evidence/2026-11-01/attachment-preview/android-annotation.jpg b/docs/design-evidence/2026-11-01/attachment-preview/android-annotation.jpg new file mode 100644 index 00000000000..a66c6413ac4 Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/android-annotation.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/android-image.jpg b/docs/design-evidence/2026-11-01/attachment-preview/android-image.jpg new file mode 100644 index 00000000000..10a28726965 Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/android-image.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/android-model.jpg b/docs/design-evidence/2026-11-01/attachment-preview/android-model.jpg new file mode 100644 index 00000000000..8e794f675af Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/android-model.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/android-pdf.jpg b/docs/design-evidence/2026-11-01/attachment-preview/android-pdf.jpg new file mode 100644 index 00000000000..576acbf9f3a Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/android-pdf.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/ci-android-text-cap.jpg b/docs/design-evidence/2026-11-01/attachment-preview/ci-android-text-cap.jpg new file mode 100644 index 00000000000..f42c68d735e Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/ci-android-text-cap.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/ci-android-text-search.jpg b/docs/design-evidence/2026-11-01/attachment-preview/ci-android-text-search.jpg new file mode 100644 index 00000000000..ac8076e5668 Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/ci-android-text-search.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/ci-desktop-mermaid-focus.png b/docs/design-evidence/2026-11-01/attachment-preview/ci-desktop-mermaid-focus.png new file mode 100644 index 00000000000..06f9467984c Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/ci-desktop-mermaid-focus.png differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/ci-ios-text-cap.jpg b/docs/design-evidence/2026-11-01/attachment-preview/ci-ios-text-cap.jpg new file mode 100644 index 00000000000..53784a7a0f4 Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/ci-ios-text-cap.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/ci-ios-text-search.jpg b/docs/design-evidence/2026-11-01/attachment-preview/ci-ios-text-search.jpg new file mode 100644 index 00000000000..69d3dadc97b Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/ci-ios-text-search.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/ci-typography-android-search.jpg b/docs/design-evidence/2026-11-01/attachment-preview/ci-typography-android-search.jpg new file mode 100644 index 00000000000..e0ac5a9b224 Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/ci-typography-android-search.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/ci-typography-ios-search.jpg b/docs/design-evidence/2026-11-01/attachment-preview/ci-typography-ios-search.jpg new file mode 100644 index 00000000000..6c6757d4c94 Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/ci-typography-ios-search.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/desktop-pdf.png b/docs/design-evidence/2026-11-01/attachment-preview/desktop-pdf.png new file mode 100644 index 00000000000..84411562f4e Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/desktop-pdf.png differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/desktop-video.png b/docs/design-evidence/2026-11-01/attachment-preview/desktop-video.png new file mode 100644 index 00000000000..538b8a73b9b Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/desktop-video.png differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/ios-pdf.jpg b/docs/design-evidence/2026-11-01/attachment-preview/ios-pdf.jpg new file mode 100644 index 00000000000..b0ba53cdb7e Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/ios-pdf.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/ios-return.jpg b/docs/design-evidence/2026-11-01/attachment-preview/ios-return.jpg new file mode 100644 index 00000000000..04c1ab64526 Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/ios-return.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/ios-video.jpg b/docs/design-evidence/2026-11-01/attachment-preview/ios-video.jpg new file mode 100644 index 00000000000..2ca256ac733 Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/ios-video.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/merge3-android-code.jpg b/docs/design-evidence/2026-11-01/attachment-preview/merge3-android-code.jpg new file mode 100644 index 00000000000..518ce356174 Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/merge3-android-code.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/merge3-android-markdown.jpg b/docs/design-evidence/2026-11-01/attachment-preview/merge3-android-markdown.jpg new file mode 100644 index 00000000000..e2938c52824 Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/merge3-android-markdown.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/merge3-android-pdf.jpg b/docs/design-evidence/2026-11-01/attachment-preview/merge3-android-pdf.jpg new file mode 100644 index 00000000000..bcb61e8e7b7 Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/merge3-android-pdf.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/merge3-ios-code.jpg b/docs/design-evidence/2026-11-01/attachment-preview/merge3-ios-code.jpg new file mode 100644 index 00000000000..0c781b516f2 Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/merge3-ios-code.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/merge3-ios-markdown.jpg b/docs/design-evidence/2026-11-01/attachment-preview/merge3-ios-markdown.jpg new file mode 100644 index 00000000000..812a687d6f0 Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/merge3-ios-markdown.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/merge3-ios-pdf.jpg b/docs/design-evidence/2026-11-01/attachment-preview/merge3-ios-pdf.jpg new file mode 100644 index 00000000000..284df41235a Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/merge3-ios-pdf.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/review-android-landscape.jpg b/docs/design-evidence/2026-11-01/attachment-preview/review-android-landscape.jpg new file mode 100644 index 00000000000..88690d04e2d Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/review-android-landscape.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/review-ios-landscape.jpg b/docs/design-evidence/2026-11-01/attachment-preview/review-ios-landscape.jpg new file mode 100644 index 00000000000..7639d411f51 Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/review-ios-landscape.jpg differ diff --git a/docs/design-evidence/2026-11-01/attachment-preview/review-mermaid-error.png b/docs/design-evidence/2026-11-01/attachment-preview/review-mermaid-error.png new file mode 100644 index 00000000000..c39f4ea2145 Binary files /dev/null and b/docs/design-evidence/2026-11-01/attachment-preview/review-mermaid-error.png differ diff --git a/docs/design-rules/attachment-preview.md b/docs/design-rules/attachment-preview.md new file mode 100644 index 00000000000..5b9932aecae --- /dev/null +++ b/docs/design-rules/attachment-preview.md @@ -0,0 +1,19 @@ +# 附件预览视觉与交互 + +依据 `DESIGN.md` §2 语义颜色、§3 字体、§4 Buttons / Dialog & Modal、§5 布局,以及 `design-governance.md` §13 点击目标。此文补充附件预览的固定结构,不创建另一套主题。 + +## Desktop + +顶部 64px:左侧文件名与简短信息,右侧保存、更多、关闭;图标 16px、热区 32px、圆形按钮。底部为类型工具栏,图片顺序是缩小、比例、放大、适应、标注;已有集合在工具栏旁显示上一项 / 计数 / 下一项。工具栏不跟着内容缩放。文档使用主题表面,媒体保留媒体画布,标题与控件有独立背景。 + +## iOS / Android + +顶栏为安全区加 64px:左侧关闭、居中文件名、右侧更多;图标 20px、热区至少 48px。底部类型工具与附件集合分行,保持窄屏可点。系统播放器负责媒体控件,宿主负责退出与文件操作。横屏按实际可用区域计算,不能直接缩小一张竖屏截图。 + +## 还原要求 + +图片/视频保持固有比例,PDF 初始完整适应可用区域。文件名一行省略,完整名称在更多面板可读;长标题不能压扁关闭键。工具栏不悬浮遮住文本首行,不随手势滑出视口。颜色来自当前主题,不为某张原型写死主题。 + +多语言使用已有术语和翻译资源;只有实有能力显示动作,禁用动作有相应可访问性状态。共享操作与专用内容不互相覆盖。关闭不能兼任移除待发附件。 + +产品合同见 [attachment-preview.md](../product-rules/attachment-preview.md),真实组件和三端截图见 [验证记录](../design-evidence/2026-11-01/attachment-preview/README.md)。 diff --git a/docs/design-rules/design-inventory.md b/docs/design-rules/design-inventory.md index 21b2b2f0ad9..78d86a58f9d 100644 --- a/docs/design-rules/design-inventory.md +++ b/docs/design-rules/design-inventory.md @@ -18,12 +18,12 @@ | `desktop.auth.add-account` | desktop | 添加账号 | hash `/add-account`(ProtectedRoute → AddAccountLoginPage) | AddAccountLoginPage | apps/desktop/src/renderer/components/login/AddAccountLoginPage.tsx, apps/desktop/src/renderer/components/login/LoginBrandStage.tsx, apps/desktop/src/renderer/components/login/LoginCaptchaOverlay.tsx, apps/desktop/src/renderer/components/login/LoginControls.tsx, apps/desktop/src/renderer/components/login/LoginPage.tsx, apps/desktop/src/renderer/components/login/loginDesignTokens.ts | 60 | 0 | 20 | | `desktop.auth.legacy-migration` | desktop | 首登数据迁移弹窗 | App 顶层挂载 LegacyMigrationDialog(仅主窗;main 检测到旧版 userData 经 `legacy-migration:state` 驱动) | LegacyMigrationDialog | apps/desktop/src/renderer/components/auth/LegacyMigrationDialog.tsx | 8 | 0 | 2 | | `desktop.auth.login` | desktop | 登录页 | hash `/login`(GuestRoute → LoginPage) | LoginBrandStage, LoginCaptchaOverlay, LoginControls, LoginPage | apps/desktop/src/renderer/components/login/LoginBrandStage.tsx, apps/desktop/src/renderer/components/login/LoginCaptchaOverlay.tsx, apps/desktop/src/renderer/components/login/LoginControls.tsx, apps/desktop/src/renderer/components/login/LoginPage.tsx, apps/desktop/src/renderer/components/login/loginDesignTokens.ts | 60 | 0 | 20 | -| `desktop.bots` | desktop | 伙伴(列表 / 对话 / 设置 / 历史 / 伙伴私聊 / 群聊) | hash `/bots`、`/bots/list`、`/bots/:botId`、`/bots/roster` 及伙伴当前/历史任务、伙伴私聊、伙伴群聊路由(BotsFeatureLayout) | BotBasicProfileFields, BotCollaborationCard, BotDirectMessageView, BotGroupChatView, BotGroupCreateDialog, BotGroupSettingsDrawer, BotHistorySessionView, BotLifecycleSettings, BotMarketDialog, BotModelChainEditor, BotRosterView, BotSessionView, BotSettingsDrawer, BotsHomeView, BotsListView, RemoteBotSessionView | apps/desktop/src/renderer/components/chat/AgentActionRow.tsx, apps/desktop/src/renderer/components/chat/AgentActionsBlock.tsx, apps/desktop/src/renderer/components/chat/AgentTaskCard.tsx, apps/desktop/src/renderer/components/chat/AgentTaskLiveDetails.tsx, apps/desktop/src/renderer/components/chat/AnnotationStrokesSvg.tsx, apps/desktop/src/renderer/components/chat/AskUserQuestionBubble.tsx, apps/desktop/src/renderer/components/chat/AssistantMessage.tsx, apps/desktop/src/renderer/components/chat/AutomationOriginBadge.tsx, apps/desktop/src/renderer/components/chat/ChatAudioCard.tsx, apps/desktop/src/renderer/components/chat/ChatBubble.tsx, apps/desktop/src/renderer/components/chat/ChatDisplaySnapshotContext.tsx, apps/desktop/src/renderer/components/chat/ChatImageView.tsx, apps/desktop/src/renderer/components/chat/ChatLoadingImage.tsx, apps/desktop/src/renderer/components/chat/ChatReplyIcon.tsx, apps/desktop/src/renderer/components/chat/ChatSessionFileContext.tsx, apps/desktop/src/renderer/components/chat/ChatSoundEffectCard.tsx, apps/desktop/src/renderer/components/chat/ChatVideoView.tsx, apps/desktop/src/renderer/components/chat/CindyMakeDoctorCard.tsx, apps/desktop/src/renderer/components/chat/CopyAsImageBlock.tsx, apps/desktop/src/renderer/components/chat/CredentialSwitchWaitBanner.tsx, apps/desktop/src/renderer/components/chat/DiffView.tsx, apps/desktop/src/renderer/components/chat/ErrorBanner.tsx, apps/desktop/src/renderer/components/chat/ErrorMessageCard.tsx, apps/desktop/src/renderer/components/chat/GeneratedFilesCard.tsx, apps/desktop/src/renderer/components/chat/GhostCardForm.tsx, apps/desktop/src/renderer/components/chat/GhostCardHostPrompts.tsx, apps/desktop/src/renderer/components/chat/GhostCardRecipients.tsx, apps/desktop/src/renderer/components/chat/GhostMailHistory.tsx, apps/desktop/src/renderer/components/chat/GhostSummonCard.tsx, apps/desktop/src/renderer/components/chat/GhostToolCard.tsx, apps/desktop/src/renderer/components/chat/HookTaskCard.tsx, apps/desktop/src/renderer/components/chat/ImageGalleryContext.tsx, apps/desktop/src/renderer/components/chat/ImageHoverPreview.tsx, apps/desktop/src/renderer/components/chat/ImageLightbox.tsx, apps/desktop/src/renderer/components/chat/ImageMissingPlaceholder.tsx, apps/desktop/src/renderer/components/chat/InlineReferenceChip.tsx, apps/desktop/src/renderer/components/chat/InterruptedTurnBanner.tsx, apps/desktop/src/renderer/components/chat/JumpToBottomChip.tsx, apps/desktop/src/renderer/components/chat/MailHtmlPreview.tsx, apps/desktop/src/renderer/components/chat/MarkdownDiffBlock.tsx, apps/desktop/src/renderer/components/chat/MarkdownMermaidBlock.tsx, apps/desktop/src/renderer/components/chat/MarkdownRenderer.tsx, apps/desktop/src/renderer/components/chat/McpAppCard.tsx, apps/desktop/src/renderer/components/chat/McpAppPermissionPrompt.tsx, apps/desktop/src/renderer/components/chat/MermaidLightbox.tsx, apps/desktop/src/renderer/components/chat/MessageActionBar.tsx, apps/desktop/src/renderer/components/chat/MessageNavRail.tsx, apps/desktop/src/renderer/components/chat/MessageReadAloudContext.ts, apps/desktop/src/renderer/components/chat/MessageSourceLabels.tsx, apps/desktop/src/renderer/components/chat/MessageStream.tsx, apps/desktop/src/renderer/components/chat/MessageViewport.tsx, apps/desktop/src/renderer/components/chat/ModelLightbox.tsx, apps/desktop/src/renderer/components/chat/NewMessageIndicator.tsx, apps/desktop/src/renderer/components/chat/PlanReviewBubble.tsx, apps/desktop/src/renderer/components/chat/PrevMessageJumpChip.tsx, apps/desktop/src/renderer/components/chat/ProjectLinkChip.tsx, apps/desktop/src/renderer/components/chat/QuoteChip.tsx, apps/desktop/src/renderer/components/chat/RewindPreviewDialog.tsx, apps/desktop/src/renderer/components/chat/RichAnswer.tsx, apps/desktop/src/renderer/components/chat/SelectionQuoteButton.tsx, apps/desktop/src/renderer/components/chat/SentAgentReferenceChip.tsx, apps/desktop/src/renderer/components/chat/SentInlineAtomBody.tsx, apps/desktop/src/renderer/components/chat/SessionHandoffCard.tsx, apps/desktop/src/renderer/components/chat/SessionLinkChip.tsx, apps/desktop/src/renderer/components/chat/SessionQuickSendContext.tsx, apps/desktop/src/renderer/components/chat/ShareMessageCheckbox.tsx, apps/desktop/src/renderer/components/chat/ShareSelectionBar.tsx, apps/desktop/src/renderer/components/chat/SlackIcon.tsx, apps/desktop/src/renderer/components/chat/StreamFadeSpan.tsx, apps/desktop/src/renderer/components/chat/StreamingMarkdownChunk.tsx, apps/desktop/src/renderer/components/chat/SystemCard.tsx, apps/desktop/src/renderer/components/chat/TextLightbox.tsx, apps/desktop/src/renderer/components/chat/ThinkingCard.tsx, apps/desktop/src/renderer/components/chat/ThinkingText.tsx, apps/desktop/src/renderer/components/chat/TodoListCard.tsx, apps/desktop/src/renderer/components/chat/ToolCallCard.tsx, apps/desktop/src/renderer/components/chat/ToolPayloadLightbox.tsx, apps/desktop/src/renderer/components/chat/TopRightChipStack.tsx, apps/desktop/src/renderer/components/chat/TurnChangesCard.tsx, apps/desktop/src/renderer/components/chat/UnreadFailedScheduleBanner.tsx, apps/desktop/src/renderer/components/chat/UpgradeBanner.tsx, apps/desktop/src/renderer/components/chat/UserAttachmentChip.tsx, apps/desktop/src/renderer/components/chat/UserMessage.tsx, apps/desktop/src/renderer/components/chat/UserMessageEditBox.tsx, apps/desktop/src/renderer/components/chat/UserMessageImageLink.tsx, apps/desktop/src/renderer/components/chat/UserMessageUrlLink.tsx, apps/desktop/src/renderer/components/chat/VideoLightbox.tsx, apps/desktop/src/renderer/components/chat/VideoSitePreview.tsx, apps/desktop/src/renderer/components/chat/WorkGroupBlock.tsx, apps/desktop/src/renderer/components/chat/WorktreeRestoreBanner.tsx, apps/desktop/src/renderer/components/chat/XIcon.tsx, apps/desktop/src/renderer/components/chat/activityRowChrome.ts, apps/desktop/src/renderer/components/chat/animateFocusScroll.ts, apps/desktop/src/renderer/components/chat/autoFollowIntent.ts, apps/desktop/src/renderer/components/chat/chatChrome.ts, apps/desktop/src/renderer/components/chat/errorReasonI18n.ts, apps/desktop/src/renderer/components/chat/generatedFilesProjection.ts, apps/desktop/src/renderer/components/chat/historyAfter.ts, apps/desktop/src/renderer/components/chat/imageDisplaySize.ts, apps/desktop/src/renderer/components/chat/lightboxAnnotations.ts, apps/desktop/src/renderer/components/chat/lightboxGestures.ts, apps/desktop/src/renderer/components/chat/linkInteraction.ts, apps/desktop/src/renderer/components/chat/mailBodyEditing.ts, apps/desktop/src/renderer/components/chat/mailCardFooter.ts, apps/desktop/src/renderer/components/chat/mailPreviewAppearance.ts, apps/desktop/src/renderer/components/chat/markdownImageTargets.ts, apps/desktop/src/renderer/components/chat/markdownMedia.ts, apps/desktop/src/renderer/components/chat/messageNavRailModel.ts, apps/desktop/src/renderer/components/chat/messageNavRailWheel.ts, apps/desktop/src/renderer/components/chat/messageNavigationProjection.ts, apps/desktop/src/renderer/components/chat/messageProjectionCache.ts, apps/desktop/src/renderer/components/chat/messageStreamIndicatorPosition.ts, apps/desktop/src/renderer/components/chat/messageViewportCompensation.ts, apps/desktop/src/renderer/components/chat/messageViewportGeometry.ts, apps/desktop/src/renderer/components/chat/messageWorkGroups.ts, apps/desktop/src/renderer/components/chat/observeProgrammaticScroll.ts, apps/desktop/src/renderer/components/chat/rehypeFencedCodeMarker.ts, apps/desktop/src/renderer/components/chat/rehypeHighlightShared.ts, apps/desktop/src/renderer/components/chat/rehypeMathBlockMarker.ts, apps/desktop/src/renderer/components/chat/rehypeStreamWordFade.ts, apps/desktop/src/renderer/components/chat/remarkHtmlImages.ts, apps/desktop/src/renderer/components/chat/remarkLocalPathLinks.ts, apps/desktop/src/renderer/components/chat/remarkPreserveRawLocalDestinations.ts, apps/desktop/src/renderer/components/chat/remarkSessionLinks.ts, apps/desktop/src/renderer/components/chat/remarkStrictInlineMath.ts, apps/desktop/src/renderer/components/chat/remarkTruncateCjkUrls.ts, apps/desktop/src/renderer/components/chat/repairStreamingMarkdown.ts, apps/desktop/src/renderer/components/chat/rich-answer.css, apps/desktop/src/renderer/components/chat/scrollAnchoringDetect.ts, apps/desktop/src/renderer/components/chat/shareSelectionStore.ts, apps/desktop/src/renderer/components/chat/streamingMarkdownBlocks.tsx, apps/desktop/src/renderer/components/chat/streamingMarkdownChunks.ts, apps/desktop/src/renderer/components/chat/streamingMessageProjection.ts, apps/desktop/src/renderer/components/chat/toolLoopI18n.ts, apps/desktop/src/renderer/components/chat/unreadCount.ts, apps/desktop/src/renderer/components/chat/useAutomaticHistoryLoadBudget.ts, apps/desktop/src/renderer/components/chat/useCopyAsImage.tsx, apps/desktop/src/renderer/components/chat/useDeleteMessage.ts, apps/desktop/src/renderer/components/chat/useFileChipContextMenu.tsx, apps/desktop/src/renderer/components/chat/useForkAtMessage.ts, apps/desktop/src/renderer/components/chat/useImageClipboard.ts, apps/desktop/src/renderer/components/chat/useImageLoadFailure.ts, apps/desktop/src/renderer/components/chat/useLinkContextMenu.tsx, apps/desktop/src/renderer/components/chat/useMessageViewport.ts, apps/desktop/src/renderer/components/chat/useNavigationKeyListener.ts, apps/desktop/src/renderer/components/chat/useOpenWithMenu.tsx, apps/desktop/src/renderer/components/chat/usePrevUserMessageInView.ts, apps/desktop/src/renderer/components/chat/useTurnChangeSets.ts, apps/desktop/src/renderer/components/chat/useVideoContextMenu.ts, apps/desktop/src/renderer/components/chat/useWorkspaceMediaPath.tsx, apps/desktop/src/renderer/components/chat/userMessageCollapse.ts, apps/desktop/src/renderer/components/chat/userMessageDisplayText.ts, apps/desktop/src/renderer/components/chat/userMessageLinkify.ts, apps/desktop/src/renderer/components/chat/videoContextMenu.css, apps/desktop/src/renderer/components/chat/viewportFillDetect.ts, apps/desktop/src/renderer/components/new-chat/AddRemoteProjectDialog.tsx, apps/desktop/src/renderer/components/new-chat/AgentSelect.tsx, apps/desktop/src/renderer/components/new-chat/AskUserQuestionPrompt.tsx, apps/desktop/src/renderer/components/new-chat/AtMentionPanel.tsx, apps/desktop/src/renderer/components/new-chat/AttachmentTypeThumb.tsx, apps/desktop/src/renderer/components/new-chat/ChatInput.tsx, apps/desktop/src/renderer/components/new-chat/CjkPunctDecoration.ts, apps/desktop/src/renderer/components/new-chat/CjkPunctuationUtils.ts, apps/desktop/src/renderer/components/new-chat/ComposerAttachments.tsx, apps/desktop/src/renderer/components/new-chat/ComposerCard.tsx, apps/desktop/src/renderer/components/new-chat/ComposerFolderTransfers.tsx, apps/desktop/src/renderer/components/new-chat/ComposerListIndentDecoration.ts, apps/desktop/src/renderer/components/new-chat/ComposerListNodes.ts, apps/desktop/src/renderer/components/new-chat/ComposerQuoteNode.tsx, apps/desktop/src/renderer/components/new-chat/DeviceSwitcherPill.tsx, apps/desktop/src/renderer/components/new-chat/EffortSlider.tsx, apps/desktop/src/renderer/components/new-chat/EmptyDocSelectionGuard.ts, apps/desktop/src/renderer/components/new-chat/ExtraDirsButton.tsx, apps/desktop/src/renderer/components/new-chat/FastModeToggle.tsx, apps/desktop/src/renderer/components/new-chat/FolderPickerPopover.tsx, apps/desktop/src/renderer/components/new-chat/FullAccessConfirmContent.tsx, apps/desktop/src/renderer/components/new-chat/GhostCommandDecoration.ts, apps/desktop/src/renderer/components/new-chat/GoalAdvancedLimits.tsx, apps/desktop/src/renderer/components/new-chat/GoalIndicator.tsx, apps/desktop/src/renderer/components/new-chat/HomeUsageDashboard.tsx, apps/desktop/src/renderer/components/new-chat/ListComposerTextarea.tsx, apps/desktop/src/renderer/components/new-chat/LocalModelCatalogNotice.tsx, apps/desktop/src/renderer/components/new-chat/McpSetupPrompt.tsx, apps/desktop/src/renderer/components/new-chat/MentionChipNode.tsx, apps/desktop/src/renderer/components/new-chat/MentionDragCaretDecoration.ts, apps/desktop/src/renderer/components/new-chat/ModelCompatibilityNotice.tsx, apps/desktop/src/renderer/components/new-chat/ModelConfigFlyout.tsx, apps/desktop/src/renderer/components/new-chat/ModelHarnessPicker.tsx, apps/desktop/src/renderer/components/new-chat/ModelRouteSelector.tsx, apps/desktop/src/renderer/components/new-chat/ModelSelector.tsx, apps/desktop/src/renderer/components/new-chat/ModelSourceDetails.tsx, apps/desktop/src/renderer/components/new-chat/NewGoalDialog.tsx, apps/desktop/src/renderer/components/new-chat/PastedTextChipNode.tsx, apps/desktop/src/renderer/components/new-chat/PendingQueuePanel.tsx, apps/desktop/src/renderer/components/new-chat/PermissionPrompt.tsx, apps/desktop/src/renderer/components/new-chat/PermissionSelector.tsx, apps/desktop/src/renderer/components/new-chat/PinnedPlanPanel.tsx, apps/desktop/src/renderer/components/new-chat/PlanActionCard.tsx, apps/desktop/src/renderer/components/new-chat/PlanModeIndicator.tsx, apps/desktop/src/renderer/components/new-chat/PlanViewerCard.tsx, apps/desktop/src/renderer/components/new-chat/PluginConnectionForm.tsx, apps/desktop/src/renderer/components/new-chat/PluginModelsSection.tsx, apps/desktop/src/renderer/components/new-chat/PluginOauthDeviceCode.tsx, apps/desktop/src/renderer/components/new-chat/PluginSetupPrompt.tsx, apps/desktop/src/renderer/components/new-chat/PluginSetupRemoteDesktopButton.tsx, apps/desktop/src/renderer/components/new-chat/PluginUiEnablePrompt.tsx, apps/desktop/src/renderer/components/new-chat/PluginWriteAccessRecovery.tsx, apps/desktop/src/renderer/components/new-chat/QuickStartPillMark.ts, apps/desktop/src/renderer/components/new-chat/RemoteOauthSetupCard.tsx, apps/desktop/src/renderer/components/new-chat/RemotePluginUiEnablePrompt.tsx, apps/desktop/src/renderer/components/new-chat/SendButton.tsx, apps/desktop/src/renderer/components/new-chat/SlashCommandDecoration.ts, apps/desktop/src/renderer/components/new-chat/SlashCommandPalette.tsx, apps/desktop/src/renderer/components/new-chat/SpeedIcon.tsx, apps/desktop/src/renderer/components/new-chat/TakeoverMask.tsx, apps/desktop/src/renderer/components/new-chat/ThinkingToggle.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedFlyoutHost.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedModelPanel.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedModelRail.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedModelRow.tsx, apps/desktop/src/renderer/components/new-chat/UsageDailyBars.tsx, apps/desktop/src/renderer/components/new-chat/UsageHeatmap.tsx, apps/desktop/src/renderer/components/new-chat/VendorSegmentedSwitcher.tsx, apps/desktop/src/renderer/components/new-chat/VoiceInputDraftDecoration.ts, apps/desktop/src/renderer/components/new-chat/WindowsSelectionReplacement.ts, apps/desktop/src/renderer/components/new-chat/WorktreeChipsRow.tsx, apps/desktop/src/renderer/components/new-chat/WorktreeCreatingOverlay.tsx, apps/desktop/src/renderer/components/new-chat/agentOptions.ts, apps/desktop/src/renderer/components/new-chat/agentSwitchConfirmation.ts, apps/desktop/src/renderer/components/new-chat/askUserDictation.ts, apps/desktop/src/renderer/components/new-chat/askUserVoiceShortcutOwner.ts, apps/desktop/src/renderer/components/new-chat/branchPick.ts, apps/desktop/src/renderer/components/new-chat/composerBlankPointerFocus.ts, apps/desktop/src/renderer/components/new-chat/composerContentSerialization.ts, apps/desktop/src/renderer/components/new-chat/composerDocState.ts, apps/desktop/src/renderer/components/new-chat/composerFrameScheduler.ts, apps/desktop/src/renderer/components/new-chat/composerHistoryProjection.ts, apps/desktop/src/renderer/components/new-chat/composerModelSelection.ts, apps/desktop/src/renderer/components/new-chat/composerPromptRecommendation.ts, apps/desktop/src/renderer/components/new-chat/composerRenderGate.ts, apps/desktop/src/renderer/components/new-chat/composerSendOwnership.ts, apps/desktop/src/renderer/components/new-chat/composerSendSnapshot.ts, apps/desktop/src/renderer/components/new-chat/effortChangeQueue.ts, apps/desktop/src/renderer/components/new-chat/extraDirsActions.ts, apps/desktop/src/renderer/components/new-chat/favoritesSyncFailure.ts, apps/desktop/src/renderer/components/new-chat/ghostComposerPlacement.ts, apps/desktop/src/renderer/components/new-chat/localAttachmentPicker.ts, apps/desktop/src/renderer/components/new-chat/mentionChipInsertion.ts, apps/desktop/src/renderer/components/new-chat/modelPickerPreferences.ts, apps/desktop/src/renderer/components/new-chat/modelWindowErrorToast.ts, apps/desktop/src/renderer/components/new-chat/pastePipeline.ts, apps/desktop/src/renderer/components/new-chat/pathPaste.ts, apps/desktop/src/renderer/components/new-chat/pendingQueueLocks.ts, apps/desktop/src/renderer/components/new-chat/pendingQueueRowActivity.ts, apps/desktop/src/renderer/components/new-chat/pendingQueueRowPresentation.ts, apps/desktop/src/renderer/components/new-chat/planModeComposerCommand.ts, apps/desktop/src/renderer/components/new-chat/priceTierMarks.tsx, apps/desktop/src/renderer/components/new-chat/remoteBrowseAdapters.ts, apps/desktop/src/renderer/components/new-chat/remoteExistingProjects.ts, apps/desktop/src/renderer/components/new-chat/restoreComposerDocument.ts, apps/desktop/src/renderer/components/new-chat/sessionLinkPaste.ts, apps/desktop/src/renderer/components/new-chat/sourceSwitch.ts, apps/desktop/src/renderer/components/new-chat/unifiedModelSelection.ts, apps/desktop/src/renderer/components/new-chat/usagePalette.ts, apps/desktop/src/renderer/components/new-chat/useAskUserDictation.ts, apps/desktop/src/renderer/components/new-chat/useComposerSendFocusRestore.ts, apps/desktop/src/renderer/components/new-chat/useModelDiscoveryPending.ts, apps/desktop/src/renderer/components/new-chat/useProviderWeeklyQuota.ts, apps/desktop/src/renderer/components/new-chat/useUnifiedRowActions.ts, apps/desktop/src/renderer/components/new-chat/voiceQuestionSelection.ts, apps/desktop/src/renderer/features/bots/BotAddToGroupMenu.tsx, apps/desktop/src/renderer/features/bots/BotAuthorizationCard.tsx, apps/desktop/src/renderer/features/bots/BotAutonomySettings.tsx, apps/desktop/src/renderer/features/bots/BotAvatar.tsx, apps/desktop/src/renderer/features/bots/BotBasicProfileFields.tsx, apps/desktop/src/renderer/features/bots/BotCapabilitySettings.tsx, apps/desktop/src/renderer/features/bots/BotChannelsPage.tsx, apps/desktop/src/renderer/features/bots/BotChatQuoteViews.tsx, apps/desktop/src/renderer/features/bots/BotCollaborationCard.tsx, apps/desktop/src/renderer/features/bots/BotConnectionStatus.tsx, apps/desktop/src/renderer/features/bots/BotCreateMenu.tsx, apps/desktop/src/renderer/features/bots/BotDeleteDialog.tsx, apps/desktop/src/renderer/features/bots/BotDirectMessageCard.tsx, apps/desktop/src/renderer/features/bots/BotDirectMessageView.tsx, apps/desktop/src/renderer/features/bots/BotDmThreadHost.tsx, apps/desktop/src/renderer/features/bots/BotDmThreadPreview.tsx, apps/desktop/src/renderer/features/bots/BotDmThreadViews.tsx, apps/desktop/src/renderer/features/bots/BotFollowMark.tsx, apps/desktop/src/renderer/features/bots/BotFollowUpInvitation.tsx, apps/desktop/src/renderer/features/bots/BotGenerationLabel.tsx, apps/desktop/src/renderer/features/bots/BotGlancePopover.tsx, apps/desktop/src/renderer/features/bots/BotGroupAvatars.tsx, apps/desktop/src/renderer/features/bots/BotGroupChatView.tsx, apps/desktop/src/renderer/features/bots/BotGroupComposer.tsx, apps/desktop/src/renderer/features/bots/BotGroupCreateDialog.tsx, apps/desktop/src/renderer/features/bots/BotGroupElapsed.tsx, apps/desktop/src/renderer/features/bots/BotGroupLivePreview.tsx, apps/desktop/src/renderer/features/bots/BotGroupLiveStatus.tsx, apps/desktop/src/renderer/features/bots/BotGroupMentionCard.tsx, apps/desktop/src/renderer/features/bots/BotGroupMessageAttachments.tsx, apps/desktop/src/renderer/features/bots/BotGroupPendingInteraction.tsx, apps/desktop/src/renderer/features/bots/BotGroupPlan.tsx, apps/desktop/src/renderer/features/bots/BotGroupProcess.tsx, apps/desktop/src/renderer/features/bots/BotGroupQuote.tsx, apps/desktop/src/renderer/features/bots/BotGroupRuntimeFailureNotice.tsx, apps/desktop/src/renderer/features/bots/BotGroupSettingsDrawer.tsx, apps/desktop/src/renderer/features/bots/BotGroupSidebarRow.tsx, apps/desktop/src/renderer/features/bots/BotGroupTaskCard.tsx, apps/desktop/src/renderer/features/bots/BotGroupThreadHint.tsx, apps/desktop/src/renderer/features/bots/BotGroupThreadViews.tsx, apps/desktop/src/renderer/features/bots/BotGroupTimelineItem.tsx, apps/desktop/src/renderer/features/bots/BotGroupTimelineModel.ts, apps/desktop/src/renderer/features/bots/BotHistorySessionView.tsx, apps/desktop/src/renderer/features/bots/BotHomeComputerSetting.tsx, apps/desktop/src/renderer/features/bots/BotImportForm.tsx, apps/desktop/src/renderer/features/bots/BotInvitationWelcome.tsx, apps/desktop/src/renderer/features/bots/BotLearningFooter.tsx, apps/desktop/src/renderer/features/bots/BotLifecycleSettings.tsx, apps/desktop/src/renderer/features/bots/BotMarketDialog.tsx, apps/desktop/src/renderer/features/bots/BotMarketModules.tsx, apps/desktop/src/renderer/features/bots/BotMemorySettings.tsx, apps/desktop/src/renderer/features/bots/BotModelChainEditor.tsx, apps/desktop/src/renderer/features/bots/BotNightlyLearning.tsx, apps/desktop/src/renderer/features/bots/BotOpeningPending.tsx, apps/desktop/src/renderer/features/bots/BotPortraitPicker.tsx, apps/desktop/src/renderer/features/bots/BotProjectsSettings.tsx, apps/desktop/src/renderer/features/bots/BotRosterView.tsx, apps/desktop/src/renderer/features/bots/BotRoutines.tsx, apps/desktop/src/renderer/features/bots/BotSessionContentHeader.tsx, apps/desktop/src/renderer/features/bots/BotSessionTaskLink.tsx, apps/desktop/src/renderer/features/bots/BotSessionTaskResultCard.tsx, apps/desktop/src/renderer/features/bots/BotSessionView.tsx, apps/desktop/src/renderer/features/bots/BotSettingsBlock.tsx, apps/desktop/src/renderer/features/bots/BotSettingsDrawer.tsx, apps/desktop/src/renderer/features/bots/BotSkillPage.tsx, apps/desktop/src/renderer/features/bots/BotStarterOptions.tsx, apps/desktop/src/renderer/features/bots/BotTaskModelEditor.tsx, apps/desktop/src/renderer/features/bots/BotTaskOwnerChip.tsx, apps/desktop/src/renderer/features/bots/BotsFeatureLayout.tsx, apps/desktop/src/renderer/features/bots/BotsGlobalSettingsSection.tsx, apps/desktop/src/renderer/features/bots/BotsHomeView.tsx, apps/desktop/src/renderer/features/bots/BotsListView.tsx, apps/desktop/src/renderer/features/bots/ChatBotAccessField.tsx, apps/desktop/src/renderer/features/bots/ChatGroupInviteMenu.tsx, apps/desktop/src/renderer/features/bots/ChatGroupMembersTab.tsx, apps/desktop/src/renderer/features/bots/ChatGroupSettings.tsx, apps/desktop/src/renderer/features/bots/ChatGroupSettingsRows.tsx, apps/desktop/src/renderer/features/bots/ChatInviteDialog.tsx, apps/desktop/src/renderer/features/bots/ChatInviteHost.tsx, apps/desktop/src/renderer/features/bots/ChatReactionButton.tsx, apps/desktop/src/renderer/features/bots/ChatServerControls.tsx, apps/desktop/src/renderer/features/bots/ChatThreadPanel.tsx, apps/desktop/src/renderer/features/bots/CindyDevicePicker.tsx, apps/desktop/src/renderer/features/bots/CindyDeviceRow.tsx, apps/desktop/src/renderer/features/bots/CindyMergeSetting.tsx, apps/desktop/src/renderer/features/bots/CloudCompanionOfflineView.tsx, apps/desktop/src/renderer/features/bots/CompanionShadowBanner.tsx, apps/desktop/src/renderer/features/bots/GroupMaskControls.tsx, apps/desktop/src/renderer/features/bots/GroupMaskProvider.tsx, apps/desktop/src/renderer/features/bots/GroupMaskSettings.tsx, apps/desktop/src/renderer/features/bots/RemoteBotFollowUpInvitation.tsx, apps/desktop/src/renderer/features/bots/RemoteBotSessionView.tsx, apps/desktop/src/renderer/features/bots/RemoteBotSettings.tsx, apps/desktop/src/renderer/features/bots/RemoteOfficialXCard.tsx, apps/desktop/src/renderer/features/bots/RemoteTeammateSettings.tsx, apps/desktop/src/renderer/features/bots/RemoveChatFromListButton.tsx, apps/desktop/src/renderer/features/bots/TeammateProcessStatus.tsx, apps/desktop/src/renderer/features/bots/TeammateSearchResults.tsx, apps/desktop/src/renderer/features/bots/TeammateStatusDot.tsx, apps/desktop/src/renderer/features/bots/TeammatesSection.tsx, apps/desktop/src/renderer/features/bots/ThreadPanel.css, apps/desktop/src/renderer/features/bots/ThreadPanel.tsx, apps/desktop/src/renderer/features/bots/botAvatarIdentity.ts, apps/desktop/src/renderer/features/bots/botCapabilityDefaults.ts, apps/desktop/src/renderer/features/bots/botChatPresentation.ts, apps/desktop/src/renderer/features/bots/botChatQuoteContext.tsx, apps/desktop/src/renderer/features/bots/botConversationPresentation.ts, apps/desktop/src/renderer/features/bots/botConversationTimeline.ts, apps/desktop/src/renderer/features/bots/botDelegationLive.ts, apps/desktop/src/renderer/features/bots/botDmQuote.ts, apps/desktop/src/renderer/features/bots/botDmThreadRoute.ts, apps/desktop/src/renderer/features/bots/botDmThreads.tsx, apps/desktop/src/renderer/features/bots/botDmThreadsContext.tsx, apps/desktop/src/renderer/features/bots/botFollowScopes.ts, apps/desktop/src/renderer/features/bots/botGeneratedArtifacts.ts, apps/desktop/src/renderer/features/bots/botGlanceTodos.ts, apps/desktop/src/renderer/features/bots/botGroupAttachments.ts, apps/desktop/src/renderer/features/bots/botGroupComposerChrome.ts, apps/desktop/src/renderer/features/bots/botGroupLane.ts, apps/desktop/src/renderer/features/bots/botGroupMentions.ts, apps/desktop/src/renderer/features/bots/botGroupOutbox.ts, apps/desktop/src/renderer/features/bots/botGroupPresentation.ts, apps/desktop/src/renderer/features/bots/botGroupStore.ts, apps/desktop/src/renderer/features/bots/botGroupThreadReplies.ts, apps/desktop/src/renderer/features/bots/botImConnectionClient.ts, apps/desktop/src/renderer/features/bots/botListDisplay.ts, apps/desktop/src/renderer/features/bots/botMarketDisplay.ts, apps/desktop/src/renderer/features/bots/botNavigation.ts, apps/desktop/src/renderer/features/bots/botPronounContext.tsx, apps/desktop/src/renderer/features/bots/botReadState.ts, apps/desktop/src/renderer/features/bots/botRosterDisplay.ts, apps/desktop/src/renderer/features/bots/botSessionOwners.ts, apps/desktop/src/renderer/features/bots/botSettingsAutosave.ts, apps/desktop/src/renderer/features/bots/botSettingsIcons.ts, apps/desktop/src/renderer/features/bots/botSettingsTarget.ts, apps/desktop/src/renderer/features/bots/botStore.ts, apps/desktop/src/renderer/features/bots/botTodo.css, apps/desktop/src/renderer/features/bots/botWelcomeContext.ts, apps/desktop/src/renderer/features/bots/chatError.ts, apps/desktop/src/renderer/features/bots/cindyDeviceRoster.ts, apps/desktop/src/renderer/features/bots/companionAvatarCache.ts, apps/desktop/src/renderer/features/bots/companionCloudPresentation.ts, apps/desktop/src/renderer/features/bots/companionCloudStore.ts, apps/desktop/src/renderer/features/bots/groupMaskPresentation.tsx, apps/desktop/src/renderer/features/bots/islandEntryAlert.ts, apps/desktop/src/renderer/features/bots/localBotSettingsTarget.ts, apps/desktop/src/renderer/features/bots/remoteBotRoster.ts, apps/desktop/src/renderer/features/bots/remoteBotSettingsClient.ts, apps/desktop/src/renderer/features/bots/remoteBotSettingsData.ts, apps/desktop/src/renderer/features/bots/remoteBotSettingsTarget.ts, apps/desktop/src/renderer/features/bots/teammateRosterStatus.ts, apps/desktop/src/renderer/features/bots/teammateSelection.ts, apps/desktop/src/renderer/features/bots/teammatesSectionPrefs.ts, apps/desktop/src/renderer/features/bots/useBotGeneratedFileDeliveries.ts, apps/desktop/src/renderer/features/bots/useBotGroupMentionPicker.tsx, apps/desktop/src/renderer/features/bots/useBotGroupWorkTargets.ts, apps/desktop/src/renderer/features/bots/useBotIslandVisibleSession.ts, apps/desktop/src/renderer/features/bots/useBotPluginFocusedSession.ts, apps/desktop/src/renderer/features/bots/useBotReplyTargetPreference.ts, apps/desktop/src/renderer/features/bots/useBotSettingsAutosave.ts, apps/desktop/src/renderer/features/bots/useBotSettingsTarget.ts, apps/desktop/src/renderer/features/bots/useBotUnreadSync.ts, apps/desktop/src/renderer/features/bots/useCompanionArrivalNotice.ts, apps/desktop/src/renderer/features/bots/useCompanionIslandChats.ts, apps/desktop/src/renderer/features/bots/useRemoteBots.ts, apps/desktop/src/renderer/features/bots/useSessionEntryLocationCommits.ts, apps/desktop/src/renderer/features/bots/useThreadPanelResize.ts, apps/desktop/src/renderer/features/bots/useViewedBotSessionId.ts, apps/desktop/src/renderer/features/bots/useWorkingStatusCopy.ts, apps/desktop/src/renderer/features/bots/voice/CompanionVoicePanel.tsx, apps/desktop/src/renderer/features/bots/voice/CompanionVoiceSettings.tsx, apps/desktop/src/renderer/features/bots/voice/CompanionVoiceToolbar.tsx, apps/desktop/src/renderer/features/bots/voice/GlobalVoiceCall.tsx, apps/desktop/src/renderer/features/bots/voice/MessageReadAloudController.ts, apps/desktop/src/renderer/features/bots/voice/PcmSpeechPlayer.ts, apps/desktop/src/renderer/features/bots/voice/SpeechPlaybackQueue.ts, apps/desktop/src/renderer/features/bots/voice/SpeechTextBuffer.ts, apps/desktop/src/renderer/features/bots/voice/SpeechVoiceMenu.tsx, apps/desktop/src/renderer/features/bots/voice/SpeechVoicePicker.tsx, apps/desktop/src/renderer/features/bots/voice/VoiceCallTranscript.tsx, apps/desktop/src/renderer/features/bots/voice/VoiceConversationController.ts, apps/desktop/src/renderer/features/bots/voice/VoicePreviewController.ts, apps/desktop/src/renderer/features/bots/voice/VoiceReplyTracker.ts, apps/desktop/src/renderer/features/bots/voice/microphoneDiagnostics.ts, apps/desktop/src/renderer/features/bots/voice/speechVoiceLabelCache.ts, apps/desktop/src/renderer/features/bots/voice/useMessageReadAloud.ts, apps/desktop/src/renderer/features/bots/voice/voiceCallPresentation.ts, apps/desktop/src/renderer/features/bots/voice/voiceCallStore.ts, apps/desktop/src/renderer/features/bots/voice/voiceConfiguration.ts, apps/desktop/src/renderer/features/bots/voice/voiceConversation.css, apps/desktop/src/renderer/features/bots/voice/voiceExitIntent.ts, apps/desktop/src/renderer/features/cc-agent/CCAgentFeatureLayout.tsx, apps/desktop/src/renderer/features/cc-agent/CCAgentSessionView.tsx | 308 | 38 | 601 | -| `desktop.chat.files` | desktop | 工作目录文件浏览 | hash `/cc-agent/files/:sessionId`(WorkdirBrowseRoute) | FileBodyView, FileTreeView, WorkdirBrowseRoute | apps/desktop/src/renderer/features/cc-agent/workdir-browse/DocSearchBar.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/DrawioPreview.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/FileBodyView.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/FileFilterInput.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/FileTabsBar.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/FileTreeHeaderActions.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/FileTreeIgnoredDirsToggle.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/FileTreeView.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/FilterResultList.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/ImagePreview.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/OpenInSystemActions.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/PdfPreview.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/SessionTabsBar.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/UnrenderablePlaceholder.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/WorkdirBrowseRoute.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/WorkdirBrowseSidebar.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/fileTreeHeaderButtonClass.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/hooks/useChatRailCollapsed.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/hooks/useChatRailResize.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/hooks/useConfirmSwitchAwayIfDirty.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/hooks/useDelayedFlag.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/hooks/useFileContent.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/hooks/useFileTree.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/hooks/useOpenTabs.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/hooks/useProjectFileList.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/hooks/useRevealFileInTree.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/hooks/useTreeScrollRestore.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/activeFileBodyHandle.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/docModeSwitchProjects.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/drawioExt.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/expandedStore.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/fileContentCache.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/fileIcon.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/fileMeta.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/fileScrollStore.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/fileSelectionParams.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/filterFiles.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/imageExt.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/markdownAutosave.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/openTabsStore.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/pdfExt.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/selectedFileStore.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/treeRows.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/treeScrollStore.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/search/SearchInput.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/search/SearchPanel.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/search/SearchResults.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/search/hooks/useProjectSearch.ts | 18 | 5 | 39 | +| `desktop.bots` | desktop | 伙伴(列表 / 对话 / 设置 / 历史 / 伙伴私聊 / 群聊) | hash `/bots`、`/bots/list`、`/bots/:botId`、`/bots/roster` 及伙伴当前/历史任务、伙伴私聊、伙伴群聊路由(BotsFeatureLayout) | BotBasicProfileFields, BotCollaborationCard, BotDirectMessageView, BotGroupChatView, BotGroupCreateDialog, BotGroupSettingsDrawer, BotHistorySessionView, BotLifecycleSettings, BotMarketDialog, BotModelChainEditor, BotRosterView, BotSessionView, BotSettingsDrawer, BotsHomeView, BotsListView, RemoteBotSessionView | apps/desktop/src/renderer/components/chat/AgentActionRow.tsx, apps/desktop/src/renderer/components/chat/AgentActionsBlock.tsx, apps/desktop/src/renderer/components/chat/AgentTaskCard.tsx, apps/desktop/src/renderer/components/chat/AgentTaskLiveDetails.tsx, apps/desktop/src/renderer/components/chat/AnnotationStrokesSvg.tsx, apps/desktop/src/renderer/components/chat/AskUserQuestionBubble.tsx, apps/desktop/src/renderer/components/chat/AssistantMessage.tsx, apps/desktop/src/renderer/components/chat/AttachmentCollection.tsx, apps/desktop/src/renderer/components/chat/AttachmentCollectionContext.ts, apps/desktop/src/renderer/components/chat/AutomationOriginBadge.tsx, apps/desktop/src/renderer/components/chat/ChatAudioCard.tsx, apps/desktop/src/renderer/components/chat/ChatBubble.tsx, apps/desktop/src/renderer/components/chat/ChatDisplaySnapshotContext.tsx, apps/desktop/src/renderer/components/chat/ChatImageView.tsx, apps/desktop/src/renderer/components/chat/ChatLoadingImage.tsx, apps/desktop/src/renderer/components/chat/ChatReplyIcon.tsx, apps/desktop/src/renderer/components/chat/ChatSessionFileContext.tsx, apps/desktop/src/renderer/components/chat/ChatSoundEffectCard.tsx, apps/desktop/src/renderer/components/chat/ChatVideoView.tsx, apps/desktop/src/renderer/components/chat/CindyMakeDoctorCard.tsx, apps/desktop/src/renderer/components/chat/CopyAsImageBlock.tsx, apps/desktop/src/renderer/components/chat/CredentialSwitchWaitBanner.tsx, apps/desktop/src/renderer/components/chat/DiffView.tsx, apps/desktop/src/renderer/components/chat/ErrorBanner.tsx, apps/desktop/src/renderer/components/chat/ErrorMessageCard.tsx, apps/desktop/src/renderer/components/chat/FileLightbox.tsx, apps/desktop/src/renderer/components/chat/GeneratedFilesCard.tsx, apps/desktop/src/renderer/components/chat/GhostCardForm.tsx, apps/desktop/src/renderer/components/chat/GhostCardHostPrompts.tsx, apps/desktop/src/renderer/components/chat/GhostCardRecipients.tsx, apps/desktop/src/renderer/components/chat/GhostMailHistory.tsx, apps/desktop/src/renderer/components/chat/GhostSummonCard.tsx, apps/desktop/src/renderer/components/chat/GhostToolCard.tsx, apps/desktop/src/renderer/components/chat/HookTaskCard.tsx, apps/desktop/src/renderer/components/chat/ImageGalleryContext.tsx, apps/desktop/src/renderer/components/chat/ImageHoverPreview.tsx, apps/desktop/src/renderer/components/chat/ImageLightbox.tsx, apps/desktop/src/renderer/components/chat/ImageMissingPlaceholder.tsx, apps/desktop/src/renderer/components/chat/InlineReferenceChip.tsx, apps/desktop/src/renderer/components/chat/InterruptedTurnBanner.tsx, apps/desktop/src/renderer/components/chat/JumpToBottomChip.tsx, apps/desktop/src/renderer/components/chat/MailHtmlPreview.tsx, apps/desktop/src/renderer/components/chat/MarkdownDiffBlock.tsx, apps/desktop/src/renderer/components/chat/MarkdownMermaidBlock.tsx, apps/desktop/src/renderer/components/chat/MarkdownRenderer.tsx, apps/desktop/src/renderer/components/chat/McpAppCard.tsx, apps/desktop/src/renderer/components/chat/McpAppPermissionPrompt.tsx, apps/desktop/src/renderer/components/chat/MermaidLightbox.tsx, apps/desktop/src/renderer/components/chat/MessageActionBar.tsx, apps/desktop/src/renderer/components/chat/MessageNavRail.tsx, apps/desktop/src/renderer/components/chat/MessageReadAloudContext.ts, apps/desktop/src/renderer/components/chat/MessageSourceLabels.tsx, apps/desktop/src/renderer/components/chat/MessageStream.tsx, apps/desktop/src/renderer/components/chat/MessageViewport.tsx, apps/desktop/src/renderer/components/chat/ModelLightbox.tsx, apps/desktop/src/renderer/components/chat/NewMessageIndicator.tsx, apps/desktop/src/renderer/components/chat/PlanReviewBubble.tsx, apps/desktop/src/renderer/components/chat/PrevMessageJumpChip.tsx, apps/desktop/src/renderer/components/chat/PreviewChrome.tsx, apps/desktop/src/renderer/components/chat/ProjectLinkChip.tsx, apps/desktop/src/renderer/components/chat/QuoteChip.tsx, apps/desktop/src/renderer/components/chat/RewindPreviewDialog.tsx, apps/desktop/src/renderer/components/chat/RichAnswer.tsx, apps/desktop/src/renderer/components/chat/SelectionQuoteButton.tsx, apps/desktop/src/renderer/components/chat/SentAgentReferenceChip.tsx, apps/desktop/src/renderer/components/chat/SentInlineAtomBody.tsx, apps/desktop/src/renderer/components/chat/SessionHandoffCard.tsx, apps/desktop/src/renderer/components/chat/SessionLinkChip.tsx, apps/desktop/src/renderer/components/chat/SessionQuickSendContext.tsx, apps/desktop/src/renderer/components/chat/ShareMessageCheckbox.tsx, apps/desktop/src/renderer/components/chat/ShareSelectionBar.tsx, apps/desktop/src/renderer/components/chat/SlackIcon.tsx, apps/desktop/src/renderer/components/chat/StreamFadeSpan.tsx, apps/desktop/src/renderer/components/chat/StreamingMarkdownChunk.tsx, apps/desktop/src/renderer/components/chat/SystemCard.tsx, apps/desktop/src/renderer/components/chat/TextLightbox.tsx, apps/desktop/src/renderer/components/chat/ThinkingCard.tsx, apps/desktop/src/renderer/components/chat/ThinkingText.tsx, apps/desktop/src/renderer/components/chat/TodoListCard.tsx, apps/desktop/src/renderer/components/chat/ToolCallCard.tsx, apps/desktop/src/renderer/components/chat/ToolPayloadLightbox.tsx, apps/desktop/src/renderer/components/chat/TopRightChipStack.tsx, apps/desktop/src/renderer/components/chat/TurnChangesCard.tsx, apps/desktop/src/renderer/components/chat/UnreadFailedScheduleBanner.tsx, apps/desktop/src/renderer/components/chat/UpgradeBanner.tsx, apps/desktop/src/renderer/components/chat/UserAttachmentChip.tsx, apps/desktop/src/renderer/components/chat/UserMessage.tsx, apps/desktop/src/renderer/components/chat/UserMessageEditBox.tsx, apps/desktop/src/renderer/components/chat/UserMessageImageLink.tsx, apps/desktop/src/renderer/components/chat/UserMessageUrlLink.tsx, apps/desktop/src/renderer/components/chat/VideoLightbox.tsx, apps/desktop/src/renderer/components/chat/VideoSitePreview.tsx, apps/desktop/src/renderer/components/chat/WorkGroupBlock.tsx, apps/desktop/src/renderer/components/chat/WorktreeRestoreBanner.tsx, apps/desktop/src/renderer/components/chat/XIcon.tsx, apps/desktop/src/renderer/components/chat/activityRowChrome.ts, apps/desktop/src/renderer/components/chat/animateFocusScroll.ts, apps/desktop/src/renderer/components/chat/autoFollowIntent.ts, apps/desktop/src/renderer/components/chat/chatChrome.ts, apps/desktop/src/renderer/components/chat/errorReasonI18n.ts, apps/desktop/src/renderer/components/chat/generatedFilesProjection.ts, apps/desktop/src/renderer/components/chat/historyAfter.ts, apps/desktop/src/renderer/components/chat/imageDisplaySize.ts, apps/desktop/src/renderer/components/chat/lightboxAnnotations.ts, apps/desktop/src/renderer/components/chat/lightboxGestures.ts, apps/desktop/src/renderer/components/chat/linkInteraction.ts, apps/desktop/src/renderer/components/chat/mailBodyEditing.ts, apps/desktop/src/renderer/components/chat/mailCardFooter.ts, apps/desktop/src/renderer/components/chat/mailPreviewAppearance.ts, apps/desktop/src/renderer/components/chat/markdownImageTargets.ts, apps/desktop/src/renderer/components/chat/markdownMedia.ts, apps/desktop/src/renderer/components/chat/messageNavRailModel.ts, apps/desktop/src/renderer/components/chat/messageNavRailWheel.ts, apps/desktop/src/renderer/components/chat/messageNavigationProjection.ts, apps/desktop/src/renderer/components/chat/messageProjectionCache.ts, apps/desktop/src/renderer/components/chat/messageStreamIndicatorPosition.ts, apps/desktop/src/renderer/components/chat/messageViewportCompensation.ts, apps/desktop/src/renderer/components/chat/messageViewportGeometry.ts, apps/desktop/src/renderer/components/chat/messageWorkGroups.ts, apps/desktop/src/renderer/components/chat/observeProgrammaticScroll.ts, apps/desktop/src/renderer/components/chat/rehypeFencedCodeMarker.ts, apps/desktop/src/renderer/components/chat/rehypeHighlightShared.ts, apps/desktop/src/renderer/components/chat/rehypeMathBlockMarker.ts, apps/desktop/src/renderer/components/chat/rehypeStreamWordFade.ts, apps/desktop/src/renderer/components/chat/remarkHtmlImages.ts, apps/desktop/src/renderer/components/chat/remarkLocalPathLinks.ts, apps/desktop/src/renderer/components/chat/remarkPreserveRawLocalDestinations.ts, apps/desktop/src/renderer/components/chat/remarkSessionLinks.ts, apps/desktop/src/renderer/components/chat/remarkStrictInlineMath.ts, apps/desktop/src/renderer/components/chat/remarkTruncateCjkUrls.ts, apps/desktop/src/renderer/components/chat/repairStreamingMarkdown.ts, apps/desktop/src/renderer/components/chat/rich-answer.css, apps/desktop/src/renderer/components/chat/scrollAnchoringDetect.ts, apps/desktop/src/renderer/components/chat/shareSelectionStore.ts, apps/desktop/src/renderer/components/chat/streamingMarkdownBlocks.tsx, apps/desktop/src/renderer/components/chat/streamingMarkdownChunks.ts, apps/desktop/src/renderer/components/chat/streamingMessageProjection.ts, apps/desktop/src/renderer/components/chat/toolLoopI18n.ts, apps/desktop/src/renderer/components/chat/unreadCount.ts, apps/desktop/src/renderer/components/chat/useAutomaticHistoryLoadBudget.ts, apps/desktop/src/renderer/components/chat/useCopyAsImage.tsx, apps/desktop/src/renderer/components/chat/useDeleteMessage.ts, apps/desktop/src/renderer/components/chat/useFileChipContextMenu.tsx, apps/desktop/src/renderer/components/chat/useForkAtMessage.ts, apps/desktop/src/renderer/components/chat/useImageClipboard.ts, apps/desktop/src/renderer/components/chat/useImageLoadFailure.ts, apps/desktop/src/renderer/components/chat/useLinkContextMenu.tsx, apps/desktop/src/renderer/components/chat/useMessageViewport.ts, apps/desktop/src/renderer/components/chat/useNavigationKeyListener.ts, apps/desktop/src/renderer/components/chat/useOpenWithMenu.tsx, apps/desktop/src/renderer/components/chat/usePrevUserMessageInView.ts, apps/desktop/src/renderer/components/chat/useTurnChangeSets.ts, apps/desktop/src/renderer/components/chat/useVideoContextMenu.ts, apps/desktop/src/renderer/components/chat/useWorkspaceMediaPath.tsx, apps/desktop/src/renderer/components/chat/userMessageCollapse.ts, apps/desktop/src/renderer/components/chat/userMessageDisplayText.ts, apps/desktop/src/renderer/components/chat/userMessageLinkify.ts, apps/desktop/src/renderer/components/chat/videoContextMenu.css, apps/desktop/src/renderer/components/chat/viewportFillDetect.ts, apps/desktop/src/renderer/components/new-chat/AddRemoteProjectDialog.tsx, apps/desktop/src/renderer/components/new-chat/AgentSelect.tsx, apps/desktop/src/renderer/components/new-chat/AskUserQuestionPrompt.tsx, apps/desktop/src/renderer/components/new-chat/AtMentionPanel.tsx, apps/desktop/src/renderer/components/new-chat/AttachmentTypeThumb.tsx, apps/desktop/src/renderer/components/new-chat/ChatInput.tsx, apps/desktop/src/renderer/components/new-chat/CjkPunctDecoration.ts, apps/desktop/src/renderer/components/new-chat/CjkPunctuationUtils.ts, apps/desktop/src/renderer/components/new-chat/ComposerAttachments.tsx, apps/desktop/src/renderer/components/new-chat/ComposerCard.tsx, apps/desktop/src/renderer/components/new-chat/ComposerFolderTransfers.tsx, apps/desktop/src/renderer/components/new-chat/ComposerListIndentDecoration.ts, apps/desktop/src/renderer/components/new-chat/ComposerListNodes.ts, apps/desktop/src/renderer/components/new-chat/ComposerQuoteNode.tsx, apps/desktop/src/renderer/components/new-chat/DeviceSwitcherPill.tsx, apps/desktop/src/renderer/components/new-chat/EffortSlider.tsx, apps/desktop/src/renderer/components/new-chat/EmptyDocSelectionGuard.ts, apps/desktop/src/renderer/components/new-chat/ExtraDirsButton.tsx, apps/desktop/src/renderer/components/new-chat/FastModeToggle.tsx, apps/desktop/src/renderer/components/new-chat/FolderPickerPopover.tsx, apps/desktop/src/renderer/components/new-chat/FullAccessConfirmContent.tsx, apps/desktop/src/renderer/components/new-chat/GhostCommandDecoration.ts, apps/desktop/src/renderer/components/new-chat/GoalAdvancedLimits.tsx, apps/desktop/src/renderer/components/new-chat/GoalIndicator.tsx, apps/desktop/src/renderer/components/new-chat/HomeUsageDashboard.tsx, apps/desktop/src/renderer/components/new-chat/ListComposerTextarea.tsx, apps/desktop/src/renderer/components/new-chat/LocalModelCatalogNotice.tsx, apps/desktop/src/renderer/components/new-chat/McpSetupPrompt.tsx, apps/desktop/src/renderer/components/new-chat/MentionChipNode.tsx, apps/desktop/src/renderer/components/new-chat/MentionDragCaretDecoration.ts, apps/desktop/src/renderer/components/new-chat/ModelCompatibilityNotice.tsx, apps/desktop/src/renderer/components/new-chat/ModelConfigFlyout.tsx, apps/desktop/src/renderer/components/new-chat/ModelHarnessPicker.tsx, apps/desktop/src/renderer/components/new-chat/ModelRouteSelector.tsx, apps/desktop/src/renderer/components/new-chat/ModelSelector.tsx, apps/desktop/src/renderer/components/new-chat/ModelSourceDetails.tsx, apps/desktop/src/renderer/components/new-chat/NewGoalDialog.tsx, apps/desktop/src/renderer/components/new-chat/PastedTextChipNode.tsx, apps/desktop/src/renderer/components/new-chat/PendingQueuePanel.tsx, apps/desktop/src/renderer/components/new-chat/PermissionPrompt.tsx, apps/desktop/src/renderer/components/new-chat/PermissionSelector.tsx, apps/desktop/src/renderer/components/new-chat/PinnedPlanPanel.tsx, apps/desktop/src/renderer/components/new-chat/PlanActionCard.tsx, apps/desktop/src/renderer/components/new-chat/PlanModeIndicator.tsx, apps/desktop/src/renderer/components/new-chat/PlanViewerCard.tsx, apps/desktop/src/renderer/components/new-chat/PluginConnectionForm.tsx, apps/desktop/src/renderer/components/new-chat/PluginModelsSection.tsx, apps/desktop/src/renderer/components/new-chat/PluginOauthDeviceCode.tsx, apps/desktop/src/renderer/components/new-chat/PluginSetupPrompt.tsx, apps/desktop/src/renderer/components/new-chat/PluginSetupRemoteDesktopButton.tsx, apps/desktop/src/renderer/components/new-chat/PluginUiEnablePrompt.tsx, apps/desktop/src/renderer/components/new-chat/PluginWriteAccessRecovery.tsx, apps/desktop/src/renderer/components/new-chat/QuickStartPillMark.ts, apps/desktop/src/renderer/components/new-chat/RemoteOauthSetupCard.tsx, apps/desktop/src/renderer/components/new-chat/RemotePluginUiEnablePrompt.tsx, apps/desktop/src/renderer/components/new-chat/SendButton.tsx, apps/desktop/src/renderer/components/new-chat/SlashCommandDecoration.ts, apps/desktop/src/renderer/components/new-chat/SlashCommandPalette.tsx, apps/desktop/src/renderer/components/new-chat/SpeedIcon.tsx, apps/desktop/src/renderer/components/new-chat/TakeoverMask.tsx, apps/desktop/src/renderer/components/new-chat/ThinkingToggle.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedFlyoutHost.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedModelPanel.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedModelRail.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedModelRow.tsx, apps/desktop/src/renderer/components/new-chat/UsageDailyBars.tsx, apps/desktop/src/renderer/components/new-chat/UsageHeatmap.tsx, apps/desktop/src/renderer/components/new-chat/VendorSegmentedSwitcher.tsx, apps/desktop/src/renderer/components/new-chat/VoiceInputDraftDecoration.ts, apps/desktop/src/renderer/components/new-chat/WindowsSelectionReplacement.ts, apps/desktop/src/renderer/components/new-chat/WorktreeChipsRow.tsx, apps/desktop/src/renderer/components/new-chat/WorktreeCreatingOverlay.tsx, apps/desktop/src/renderer/components/new-chat/agentOptions.ts, apps/desktop/src/renderer/components/new-chat/agentSwitchConfirmation.ts, apps/desktop/src/renderer/components/new-chat/askUserDictation.ts, apps/desktop/src/renderer/components/new-chat/askUserVoiceShortcutOwner.ts, apps/desktop/src/renderer/components/new-chat/branchPick.ts, apps/desktop/src/renderer/components/new-chat/composerBlankPointerFocus.ts, apps/desktop/src/renderer/components/new-chat/composerContentSerialization.ts, apps/desktop/src/renderer/components/new-chat/composerDocState.ts, apps/desktop/src/renderer/components/new-chat/composerFrameScheduler.ts, apps/desktop/src/renderer/components/new-chat/composerHistoryProjection.ts, apps/desktop/src/renderer/components/new-chat/composerModelSelection.ts, apps/desktop/src/renderer/components/new-chat/composerPromptRecommendation.ts, apps/desktop/src/renderer/components/new-chat/composerRenderGate.ts, apps/desktop/src/renderer/components/new-chat/composerSendOwnership.ts, apps/desktop/src/renderer/components/new-chat/composerSendSnapshot.ts, apps/desktop/src/renderer/components/new-chat/effortChangeQueue.ts, apps/desktop/src/renderer/components/new-chat/extraDirsActions.ts, apps/desktop/src/renderer/components/new-chat/favoritesSyncFailure.ts, apps/desktop/src/renderer/components/new-chat/ghostComposerPlacement.ts, apps/desktop/src/renderer/components/new-chat/localAttachmentPicker.ts, apps/desktop/src/renderer/components/new-chat/mentionChipInsertion.ts, apps/desktop/src/renderer/components/new-chat/modelPickerPreferences.ts, apps/desktop/src/renderer/components/new-chat/modelWindowErrorToast.ts, apps/desktop/src/renderer/components/new-chat/pastePipeline.ts, apps/desktop/src/renderer/components/new-chat/pathPaste.ts, apps/desktop/src/renderer/components/new-chat/pendingQueueLocks.ts, apps/desktop/src/renderer/components/new-chat/pendingQueueRowActivity.ts, apps/desktop/src/renderer/components/new-chat/pendingQueueRowPresentation.ts, apps/desktop/src/renderer/components/new-chat/planModeComposerCommand.ts, apps/desktop/src/renderer/components/new-chat/priceTierMarks.tsx, apps/desktop/src/renderer/components/new-chat/remoteBrowseAdapters.ts, apps/desktop/src/renderer/components/new-chat/remoteExistingProjects.ts, apps/desktop/src/renderer/components/new-chat/restoreComposerDocument.ts, apps/desktop/src/renderer/components/new-chat/sessionLinkPaste.ts, apps/desktop/src/renderer/components/new-chat/sourceSwitch.ts, apps/desktop/src/renderer/components/new-chat/unifiedModelSelection.ts, apps/desktop/src/renderer/components/new-chat/usagePalette.ts, apps/desktop/src/renderer/components/new-chat/useAskUserDictation.ts, apps/desktop/src/renderer/components/new-chat/useComposerSendFocusRestore.ts, apps/desktop/src/renderer/components/new-chat/useModelDiscoveryPending.ts, apps/desktop/src/renderer/components/new-chat/useProviderWeeklyQuota.ts, apps/desktop/src/renderer/components/new-chat/useUnifiedRowActions.ts, apps/desktop/src/renderer/components/new-chat/voiceQuestionSelection.ts, apps/desktop/src/renderer/features/bots/BotAddToGroupMenu.tsx, apps/desktop/src/renderer/features/bots/BotAuthorizationCard.tsx, apps/desktop/src/renderer/features/bots/BotAutonomySettings.tsx, apps/desktop/src/renderer/features/bots/BotAvatar.tsx, apps/desktop/src/renderer/features/bots/BotBasicProfileFields.tsx, apps/desktop/src/renderer/features/bots/BotCapabilitySettings.tsx, apps/desktop/src/renderer/features/bots/BotChannelsPage.tsx, apps/desktop/src/renderer/features/bots/BotChatQuoteViews.tsx, apps/desktop/src/renderer/features/bots/BotCollaborationCard.tsx, apps/desktop/src/renderer/features/bots/BotConnectionStatus.tsx, apps/desktop/src/renderer/features/bots/BotCreateMenu.tsx, apps/desktop/src/renderer/features/bots/BotDeleteDialog.tsx, apps/desktop/src/renderer/features/bots/BotDirectMessageCard.tsx, apps/desktop/src/renderer/features/bots/BotDirectMessageView.tsx, apps/desktop/src/renderer/features/bots/BotDmThreadHost.tsx, apps/desktop/src/renderer/features/bots/BotDmThreadPreview.tsx, apps/desktop/src/renderer/features/bots/BotDmThreadViews.tsx, apps/desktop/src/renderer/features/bots/BotFollowMark.tsx, apps/desktop/src/renderer/features/bots/BotFollowUpInvitation.tsx, apps/desktop/src/renderer/features/bots/BotGenerationLabel.tsx, apps/desktop/src/renderer/features/bots/BotGlancePopover.tsx, apps/desktop/src/renderer/features/bots/BotGroupAvatars.tsx, apps/desktop/src/renderer/features/bots/BotGroupChatView.tsx, apps/desktop/src/renderer/features/bots/BotGroupComposer.tsx, apps/desktop/src/renderer/features/bots/BotGroupCreateDialog.tsx, apps/desktop/src/renderer/features/bots/BotGroupElapsed.tsx, apps/desktop/src/renderer/features/bots/BotGroupLivePreview.tsx, apps/desktop/src/renderer/features/bots/BotGroupLiveStatus.tsx, apps/desktop/src/renderer/features/bots/BotGroupMentionCard.tsx, apps/desktop/src/renderer/features/bots/BotGroupMessageAttachments.tsx, apps/desktop/src/renderer/features/bots/BotGroupPendingInteraction.tsx, apps/desktop/src/renderer/features/bots/BotGroupPlan.tsx, apps/desktop/src/renderer/features/bots/BotGroupProcess.tsx, apps/desktop/src/renderer/features/bots/BotGroupQuote.tsx, apps/desktop/src/renderer/features/bots/BotGroupRuntimeFailureNotice.tsx, apps/desktop/src/renderer/features/bots/BotGroupSettingsDrawer.tsx, apps/desktop/src/renderer/features/bots/BotGroupSidebarRow.tsx, apps/desktop/src/renderer/features/bots/BotGroupTaskCard.tsx, apps/desktop/src/renderer/features/bots/BotGroupThreadHint.tsx, apps/desktop/src/renderer/features/bots/BotGroupThreadViews.tsx, apps/desktop/src/renderer/features/bots/BotGroupTimelineItem.tsx, apps/desktop/src/renderer/features/bots/BotGroupTimelineModel.ts, apps/desktop/src/renderer/features/bots/BotHistorySessionView.tsx, apps/desktop/src/renderer/features/bots/BotHomeComputerSetting.tsx, apps/desktop/src/renderer/features/bots/BotImportForm.tsx, apps/desktop/src/renderer/features/bots/BotInvitationWelcome.tsx, apps/desktop/src/renderer/features/bots/BotLearningFooter.tsx, apps/desktop/src/renderer/features/bots/BotLifecycleSettings.tsx, apps/desktop/src/renderer/features/bots/BotMarketDialog.tsx, apps/desktop/src/renderer/features/bots/BotMarketModules.tsx, apps/desktop/src/renderer/features/bots/BotMemorySettings.tsx, apps/desktop/src/renderer/features/bots/BotModelChainEditor.tsx, apps/desktop/src/renderer/features/bots/BotNightlyLearning.tsx, apps/desktop/src/renderer/features/bots/BotOpeningPending.tsx, apps/desktop/src/renderer/features/bots/BotPortraitPicker.tsx, apps/desktop/src/renderer/features/bots/BotProjectsSettings.tsx, apps/desktop/src/renderer/features/bots/BotRosterView.tsx, apps/desktop/src/renderer/features/bots/BotRoutines.tsx, apps/desktop/src/renderer/features/bots/BotSessionContentHeader.tsx, apps/desktop/src/renderer/features/bots/BotSessionTaskLink.tsx, apps/desktop/src/renderer/features/bots/BotSessionTaskResultCard.tsx, apps/desktop/src/renderer/features/bots/BotSessionView.tsx, apps/desktop/src/renderer/features/bots/BotSettingsBlock.tsx, apps/desktop/src/renderer/features/bots/BotSettingsDrawer.tsx, apps/desktop/src/renderer/features/bots/BotSkillPage.tsx, apps/desktop/src/renderer/features/bots/BotStarterOptions.tsx, apps/desktop/src/renderer/features/bots/BotTaskModelEditor.tsx, apps/desktop/src/renderer/features/bots/BotTaskOwnerChip.tsx, apps/desktop/src/renderer/features/bots/BotsFeatureLayout.tsx, apps/desktop/src/renderer/features/bots/BotsGlobalSettingsSection.tsx, apps/desktop/src/renderer/features/bots/BotsHomeView.tsx, apps/desktop/src/renderer/features/bots/BotsListView.tsx, apps/desktop/src/renderer/features/bots/ChatBotAccessField.tsx, apps/desktop/src/renderer/features/bots/ChatGroupInviteMenu.tsx, apps/desktop/src/renderer/features/bots/ChatGroupMembersTab.tsx, apps/desktop/src/renderer/features/bots/ChatGroupSettings.tsx, apps/desktop/src/renderer/features/bots/ChatGroupSettingsRows.tsx, apps/desktop/src/renderer/features/bots/ChatInviteDialog.tsx, apps/desktop/src/renderer/features/bots/ChatInviteHost.tsx, apps/desktop/src/renderer/features/bots/ChatReactionButton.tsx, apps/desktop/src/renderer/features/bots/ChatServerControls.tsx, apps/desktop/src/renderer/features/bots/ChatThreadPanel.tsx, apps/desktop/src/renderer/features/bots/CindyDevicePicker.tsx, apps/desktop/src/renderer/features/bots/CindyDeviceRow.tsx, apps/desktop/src/renderer/features/bots/CindyMergeSetting.tsx, apps/desktop/src/renderer/features/bots/CloudCompanionOfflineView.tsx, apps/desktop/src/renderer/features/bots/CompanionShadowBanner.tsx, apps/desktop/src/renderer/features/bots/GroupMaskControls.tsx, apps/desktop/src/renderer/features/bots/GroupMaskProvider.tsx, apps/desktop/src/renderer/features/bots/GroupMaskSettings.tsx, apps/desktop/src/renderer/features/bots/RemoteBotFollowUpInvitation.tsx, apps/desktop/src/renderer/features/bots/RemoteBotSessionView.tsx, apps/desktop/src/renderer/features/bots/RemoteBotSettings.tsx, apps/desktop/src/renderer/features/bots/RemoteOfficialXCard.tsx, apps/desktop/src/renderer/features/bots/RemoteTeammateSettings.tsx, apps/desktop/src/renderer/features/bots/RemoveChatFromListButton.tsx, apps/desktop/src/renderer/features/bots/TeammateProcessStatus.tsx, apps/desktop/src/renderer/features/bots/TeammateSearchResults.tsx, apps/desktop/src/renderer/features/bots/TeammateStatusDot.tsx, apps/desktop/src/renderer/features/bots/TeammatesSection.tsx, apps/desktop/src/renderer/features/bots/ThreadPanel.css, apps/desktop/src/renderer/features/bots/ThreadPanel.tsx, apps/desktop/src/renderer/features/bots/botAvatarIdentity.ts, apps/desktop/src/renderer/features/bots/botCapabilityDefaults.ts, apps/desktop/src/renderer/features/bots/botChatPresentation.ts, apps/desktop/src/renderer/features/bots/botChatQuoteContext.tsx, apps/desktop/src/renderer/features/bots/botConversationPresentation.ts, apps/desktop/src/renderer/features/bots/botConversationTimeline.ts, apps/desktop/src/renderer/features/bots/botDelegationLive.ts, apps/desktop/src/renderer/features/bots/botDmQuote.ts, apps/desktop/src/renderer/features/bots/botDmThreadRoute.ts, apps/desktop/src/renderer/features/bots/botDmThreads.tsx, apps/desktop/src/renderer/features/bots/botDmThreadsContext.tsx, apps/desktop/src/renderer/features/bots/botFollowScopes.ts, apps/desktop/src/renderer/features/bots/botGeneratedArtifacts.ts, apps/desktop/src/renderer/features/bots/botGlanceTodos.ts, apps/desktop/src/renderer/features/bots/botGroupAttachments.ts, apps/desktop/src/renderer/features/bots/botGroupComposerChrome.ts, apps/desktop/src/renderer/features/bots/botGroupLane.ts, apps/desktop/src/renderer/features/bots/botGroupMentions.ts, apps/desktop/src/renderer/features/bots/botGroupOutbox.ts, apps/desktop/src/renderer/features/bots/botGroupPresentation.ts, apps/desktop/src/renderer/features/bots/botGroupStore.ts, apps/desktop/src/renderer/features/bots/botGroupThreadReplies.ts, apps/desktop/src/renderer/features/bots/botImConnectionClient.ts, apps/desktop/src/renderer/features/bots/botListDisplay.ts, apps/desktop/src/renderer/features/bots/botMarketDisplay.ts, apps/desktop/src/renderer/features/bots/botNavigation.ts, apps/desktop/src/renderer/features/bots/botPronounContext.tsx, apps/desktop/src/renderer/features/bots/botReadState.ts, apps/desktop/src/renderer/features/bots/botRosterDisplay.ts, apps/desktop/src/renderer/features/bots/botSessionOwners.ts, apps/desktop/src/renderer/features/bots/botSettingsAutosave.ts, apps/desktop/src/renderer/features/bots/botSettingsIcons.ts, apps/desktop/src/renderer/features/bots/botSettingsTarget.ts, apps/desktop/src/renderer/features/bots/botStore.ts, apps/desktop/src/renderer/features/bots/botTodo.css, apps/desktop/src/renderer/features/bots/botWelcomeContext.ts, apps/desktop/src/renderer/features/bots/chatError.ts, apps/desktop/src/renderer/features/bots/cindyDeviceRoster.ts, apps/desktop/src/renderer/features/bots/companionAvatarCache.ts, apps/desktop/src/renderer/features/bots/companionCloudPresentation.ts, apps/desktop/src/renderer/features/bots/companionCloudStore.ts, apps/desktop/src/renderer/features/bots/groupMaskPresentation.tsx, apps/desktop/src/renderer/features/bots/islandEntryAlert.ts, apps/desktop/src/renderer/features/bots/localBotSettingsTarget.ts, apps/desktop/src/renderer/features/bots/remoteBotRoster.ts, apps/desktop/src/renderer/features/bots/remoteBotSettingsClient.ts, apps/desktop/src/renderer/features/bots/remoteBotSettingsData.ts, apps/desktop/src/renderer/features/bots/remoteBotSettingsTarget.ts, apps/desktop/src/renderer/features/bots/teammateRosterStatus.ts, apps/desktop/src/renderer/features/bots/teammateSelection.ts, apps/desktop/src/renderer/features/bots/teammatesSectionPrefs.ts, apps/desktop/src/renderer/features/bots/useBotGeneratedFileDeliveries.ts, apps/desktop/src/renderer/features/bots/useBotGroupMentionPicker.tsx, apps/desktop/src/renderer/features/bots/useBotGroupWorkTargets.ts, apps/desktop/src/renderer/features/bots/useBotIslandVisibleSession.ts, apps/desktop/src/renderer/features/bots/useBotPluginFocusedSession.ts, apps/desktop/src/renderer/features/bots/useBotReplyTargetPreference.ts, apps/desktop/src/renderer/features/bots/useBotSettingsAutosave.ts, apps/desktop/src/renderer/features/bots/useBotSettingsTarget.ts, apps/desktop/src/renderer/features/bots/useBotUnreadSync.ts, apps/desktop/src/renderer/features/bots/useCompanionArrivalNotice.ts, apps/desktop/src/renderer/features/bots/useCompanionIslandChats.ts, apps/desktop/src/renderer/features/bots/useRemoteBots.ts, apps/desktop/src/renderer/features/bots/useSessionEntryLocationCommits.ts, apps/desktop/src/renderer/features/bots/useThreadPanelResize.ts, apps/desktop/src/renderer/features/bots/useViewedBotSessionId.ts, apps/desktop/src/renderer/features/bots/useWorkingStatusCopy.ts, apps/desktop/src/renderer/features/bots/voice/CompanionVoicePanel.tsx, apps/desktop/src/renderer/features/bots/voice/CompanionVoiceSettings.tsx, apps/desktop/src/renderer/features/bots/voice/CompanionVoiceToolbar.tsx, apps/desktop/src/renderer/features/bots/voice/GlobalVoiceCall.tsx, apps/desktop/src/renderer/features/bots/voice/MessageReadAloudController.ts, apps/desktop/src/renderer/features/bots/voice/PcmSpeechPlayer.ts, apps/desktop/src/renderer/features/bots/voice/SpeechPlaybackQueue.ts, apps/desktop/src/renderer/features/bots/voice/SpeechTextBuffer.ts, apps/desktop/src/renderer/features/bots/voice/SpeechVoiceMenu.tsx, apps/desktop/src/renderer/features/bots/voice/SpeechVoicePicker.tsx, apps/desktop/src/renderer/features/bots/voice/VoiceCallTranscript.tsx, apps/desktop/src/renderer/features/bots/voice/VoiceConversationController.ts, apps/desktop/src/renderer/features/bots/voice/VoicePreviewController.ts, apps/desktop/src/renderer/features/bots/voice/VoiceReplyTracker.ts, apps/desktop/src/renderer/features/bots/voice/microphoneDiagnostics.ts, apps/desktop/src/renderer/features/bots/voice/speechVoiceLabelCache.ts, apps/desktop/src/renderer/features/bots/voice/useMessageReadAloud.ts, apps/desktop/src/renderer/features/bots/voice/voiceCallPresentation.ts, apps/desktop/src/renderer/features/bots/voice/voiceCallStore.ts, apps/desktop/src/renderer/features/bots/voice/voiceConfiguration.ts, apps/desktop/src/renderer/features/bots/voice/voiceConversation.css, apps/desktop/src/renderer/features/bots/voice/voiceExitIntent.ts, apps/desktop/src/renderer/features/cc-agent/CCAgentFeatureLayout.tsx, apps/desktop/src/renderer/features/cc-agent/CCAgentSessionView.tsx | 305 | 34 | 596 | +| `desktop.chat.files` | desktop | 工作目录文件浏览 | hash `/cc-agent/files/:sessionId`(WorkdirBrowseRoute) | FileBodyView, FileTreeView, WorkdirBrowseRoute | apps/desktop/src/renderer/features/cc-agent/workdir-browse/DocSearchBar.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/DrawioPreview.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/FileBodyView.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/FileFilterInput.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/FileTabsBar.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/FileTreeHeaderActions.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/FileTreeIgnoredDirsToggle.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/FileTreeView.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/FilterResultList.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/ImagePreview.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/OpenInSystemActions.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/PdfPreview.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/SessionTabsBar.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/UnrenderablePlaceholder.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/WorkdirBrowseRoute.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/WorkdirBrowseSidebar.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/fileTreeHeaderButtonClass.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/hooks/useChatRailCollapsed.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/hooks/useChatRailResize.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/hooks/useConfirmSwitchAwayIfDirty.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/hooks/useDelayedFlag.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/hooks/useFileContent.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/hooks/useFileTree.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/hooks/useOpenTabs.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/hooks/useProjectFileList.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/hooks/useRevealFileInTree.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/hooks/useTreeScrollRestore.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/activeFileBodyHandle.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/docModeSwitchProjects.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/drawioExt.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/expandedStore.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/fileContentCache.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/fileIcon.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/fileMeta.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/fileScrollStore.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/fileSelectionParams.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/filterFiles.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/imageExt.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/markdownAutosave.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/openTabsStore.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/pdfExt.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/selectedFileStore.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/treeRows.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/lib/treeScrollStore.ts, apps/desktop/src/renderer/features/cc-agent/workdir-browse/search/SearchInput.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/search/SearchPanel.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/search/SearchResults.tsx, apps/desktop/src/renderer/features/cc-agent/workdir-browse/search/hooks/useProjectSearch.ts | 18 | 5 | 40 | | `desktop.chat.new-draft` | desktop | 新建任务草稿页 | hash `/cc-agent/new`(NewMakerDraftRoute) | NewMakerDraftRoute | apps/desktop/src/renderer/features/cc-agent/NewMakerDraftRoute.tsx | 15 | 0 | 3 | -| `desktop.chat.orca-workflow` | desktop | Orca 协同工作台 | hash `/cc-agent/orca/:sessionId`(OrcaWorkflowRoute) | CCAgentSessionView, OrcaSplitView, OrcaWorkerPanel, OrcaWorkflowRoute | apps/desktop/src/renderer/components/chat/AgentActionRow.tsx, apps/desktop/src/renderer/components/chat/AgentActionsBlock.tsx, apps/desktop/src/renderer/components/chat/AgentTaskCard.tsx, apps/desktop/src/renderer/components/chat/AgentTaskLiveDetails.tsx, apps/desktop/src/renderer/components/chat/AnnotationStrokesSvg.tsx, apps/desktop/src/renderer/components/chat/AskUserQuestionBubble.tsx, apps/desktop/src/renderer/components/chat/AssistantMessage.tsx, apps/desktop/src/renderer/components/chat/AutomationOriginBadge.tsx, apps/desktop/src/renderer/components/chat/ChatAudioCard.tsx, apps/desktop/src/renderer/components/chat/ChatBubble.tsx, apps/desktop/src/renderer/components/chat/ChatDisplaySnapshotContext.tsx, apps/desktop/src/renderer/components/chat/ChatImageView.tsx, apps/desktop/src/renderer/components/chat/ChatLoadingImage.tsx, apps/desktop/src/renderer/components/chat/ChatReplyIcon.tsx, apps/desktop/src/renderer/components/chat/ChatSessionFileContext.tsx, apps/desktop/src/renderer/components/chat/ChatSoundEffectCard.tsx, apps/desktop/src/renderer/components/chat/ChatVideoView.tsx, apps/desktop/src/renderer/components/chat/CindyMakeDoctorCard.tsx, apps/desktop/src/renderer/components/chat/CopyAsImageBlock.tsx, apps/desktop/src/renderer/components/chat/CredentialSwitchWaitBanner.tsx, apps/desktop/src/renderer/components/chat/DiffView.tsx, apps/desktop/src/renderer/components/chat/ErrorBanner.tsx, apps/desktop/src/renderer/components/chat/ErrorMessageCard.tsx, apps/desktop/src/renderer/components/chat/GeneratedFilesCard.tsx, apps/desktop/src/renderer/components/chat/GhostCardForm.tsx, apps/desktop/src/renderer/components/chat/GhostCardHostPrompts.tsx, apps/desktop/src/renderer/components/chat/GhostCardRecipients.tsx, apps/desktop/src/renderer/components/chat/GhostMailHistory.tsx, apps/desktop/src/renderer/components/chat/GhostSummonCard.tsx, apps/desktop/src/renderer/components/chat/GhostToolCard.tsx, apps/desktop/src/renderer/components/chat/HookTaskCard.tsx, apps/desktop/src/renderer/components/chat/ImageGalleryContext.tsx, apps/desktop/src/renderer/components/chat/ImageHoverPreview.tsx, apps/desktop/src/renderer/components/chat/ImageLightbox.tsx, apps/desktop/src/renderer/components/chat/ImageMissingPlaceholder.tsx, apps/desktop/src/renderer/components/chat/InlineReferenceChip.tsx, apps/desktop/src/renderer/components/chat/InterruptedTurnBanner.tsx, apps/desktop/src/renderer/components/chat/JumpToBottomChip.tsx, apps/desktop/src/renderer/components/chat/MailHtmlPreview.tsx, apps/desktop/src/renderer/components/chat/MarkdownDiffBlock.tsx, apps/desktop/src/renderer/components/chat/MarkdownMermaidBlock.tsx, apps/desktop/src/renderer/components/chat/MarkdownRenderer.tsx, apps/desktop/src/renderer/components/chat/McpAppCard.tsx, apps/desktop/src/renderer/components/chat/McpAppPermissionPrompt.tsx, apps/desktop/src/renderer/components/chat/MermaidLightbox.tsx, apps/desktop/src/renderer/components/chat/MessageActionBar.tsx, apps/desktop/src/renderer/components/chat/MessageNavRail.tsx, apps/desktop/src/renderer/components/chat/MessageReadAloudContext.ts, apps/desktop/src/renderer/components/chat/MessageSourceLabels.tsx, apps/desktop/src/renderer/components/chat/MessageStream.tsx, apps/desktop/src/renderer/components/chat/MessageViewport.tsx, apps/desktop/src/renderer/components/chat/ModelLightbox.tsx, apps/desktop/src/renderer/components/chat/NewMessageIndicator.tsx, apps/desktop/src/renderer/components/chat/PlanReviewBubble.tsx, apps/desktop/src/renderer/components/chat/PrevMessageJumpChip.tsx, apps/desktop/src/renderer/components/chat/ProjectLinkChip.tsx, apps/desktop/src/renderer/components/chat/QuoteChip.tsx, apps/desktop/src/renderer/components/chat/RewindPreviewDialog.tsx, apps/desktop/src/renderer/components/chat/RichAnswer.tsx, apps/desktop/src/renderer/components/chat/SelectionQuoteButton.tsx, apps/desktop/src/renderer/components/chat/SentAgentReferenceChip.tsx, apps/desktop/src/renderer/components/chat/SentInlineAtomBody.tsx, apps/desktop/src/renderer/components/chat/SessionHandoffCard.tsx, apps/desktop/src/renderer/components/chat/SessionLinkChip.tsx, apps/desktop/src/renderer/components/chat/SessionQuickSendContext.tsx, apps/desktop/src/renderer/components/chat/ShareMessageCheckbox.tsx, apps/desktop/src/renderer/components/chat/ShareSelectionBar.tsx, apps/desktop/src/renderer/components/chat/SlackIcon.tsx, apps/desktop/src/renderer/components/chat/StreamFadeSpan.tsx, apps/desktop/src/renderer/components/chat/StreamingMarkdownChunk.tsx, apps/desktop/src/renderer/components/chat/SystemCard.tsx, apps/desktop/src/renderer/components/chat/TextLightbox.tsx, apps/desktop/src/renderer/components/chat/ThinkingCard.tsx, apps/desktop/src/renderer/components/chat/ThinkingText.tsx, apps/desktop/src/renderer/components/chat/TodoListCard.tsx, apps/desktop/src/renderer/components/chat/ToolCallCard.tsx, apps/desktop/src/renderer/components/chat/ToolPayloadLightbox.tsx, apps/desktop/src/renderer/components/chat/TopRightChipStack.tsx, apps/desktop/src/renderer/components/chat/TurnChangesCard.tsx, apps/desktop/src/renderer/components/chat/UnreadFailedScheduleBanner.tsx, apps/desktop/src/renderer/components/chat/UpgradeBanner.tsx, apps/desktop/src/renderer/components/chat/UserAttachmentChip.tsx, apps/desktop/src/renderer/components/chat/UserMessage.tsx, apps/desktop/src/renderer/components/chat/UserMessageEditBox.tsx, apps/desktop/src/renderer/components/chat/UserMessageImageLink.tsx, apps/desktop/src/renderer/components/chat/UserMessageUrlLink.tsx, apps/desktop/src/renderer/components/chat/VideoLightbox.tsx, apps/desktop/src/renderer/components/chat/VideoSitePreview.tsx, apps/desktop/src/renderer/components/chat/WorkGroupBlock.tsx, apps/desktop/src/renderer/components/chat/WorktreeRestoreBanner.tsx, apps/desktop/src/renderer/components/chat/XIcon.tsx, apps/desktop/src/renderer/components/chat/activityRowChrome.ts, apps/desktop/src/renderer/components/chat/animateFocusScroll.ts, apps/desktop/src/renderer/components/chat/autoFollowIntent.ts, apps/desktop/src/renderer/components/chat/chatChrome.ts, apps/desktop/src/renderer/components/chat/errorReasonI18n.ts, apps/desktop/src/renderer/components/chat/generatedFilesProjection.ts, apps/desktop/src/renderer/components/chat/historyAfter.ts, apps/desktop/src/renderer/components/chat/imageDisplaySize.ts, apps/desktop/src/renderer/components/chat/lightboxAnnotations.ts, apps/desktop/src/renderer/components/chat/lightboxGestures.ts, apps/desktop/src/renderer/components/chat/linkInteraction.ts, apps/desktop/src/renderer/components/chat/mailBodyEditing.ts, apps/desktop/src/renderer/components/chat/mailCardFooter.ts, apps/desktop/src/renderer/components/chat/mailPreviewAppearance.ts, apps/desktop/src/renderer/components/chat/markdownImageTargets.ts, apps/desktop/src/renderer/components/chat/markdownMedia.ts, apps/desktop/src/renderer/components/chat/messageNavRailModel.ts, apps/desktop/src/renderer/components/chat/messageNavRailWheel.ts, apps/desktop/src/renderer/components/chat/messageNavigationProjection.ts, apps/desktop/src/renderer/components/chat/messageProjectionCache.ts, apps/desktop/src/renderer/components/chat/messageStreamIndicatorPosition.ts, apps/desktop/src/renderer/components/chat/messageViewportCompensation.ts, apps/desktop/src/renderer/components/chat/messageViewportGeometry.ts, apps/desktop/src/renderer/components/chat/messageWorkGroups.ts, apps/desktop/src/renderer/components/chat/observeProgrammaticScroll.ts, apps/desktop/src/renderer/components/chat/rehypeFencedCodeMarker.ts, apps/desktop/src/renderer/components/chat/rehypeHighlightShared.ts, apps/desktop/src/renderer/components/chat/rehypeMathBlockMarker.ts, apps/desktop/src/renderer/components/chat/rehypeStreamWordFade.ts, apps/desktop/src/renderer/components/chat/remarkHtmlImages.ts, apps/desktop/src/renderer/components/chat/remarkLocalPathLinks.ts, apps/desktop/src/renderer/components/chat/remarkPreserveRawLocalDestinations.ts, apps/desktop/src/renderer/components/chat/remarkSessionLinks.ts, apps/desktop/src/renderer/components/chat/remarkStrictInlineMath.ts, apps/desktop/src/renderer/components/chat/remarkTruncateCjkUrls.ts, apps/desktop/src/renderer/components/chat/repairStreamingMarkdown.ts, apps/desktop/src/renderer/components/chat/rich-answer.css, apps/desktop/src/renderer/components/chat/scrollAnchoringDetect.ts, apps/desktop/src/renderer/components/chat/shareSelectionStore.ts, apps/desktop/src/renderer/components/chat/streamingMarkdownBlocks.tsx, apps/desktop/src/renderer/components/chat/streamingMarkdownChunks.ts, apps/desktop/src/renderer/components/chat/streamingMessageProjection.ts, apps/desktop/src/renderer/components/chat/toolLoopI18n.ts, apps/desktop/src/renderer/components/chat/unreadCount.ts, apps/desktop/src/renderer/components/chat/useAutomaticHistoryLoadBudget.ts, apps/desktop/src/renderer/components/chat/useCopyAsImage.tsx, apps/desktop/src/renderer/components/chat/useDeleteMessage.ts, apps/desktop/src/renderer/components/chat/useFileChipContextMenu.tsx, apps/desktop/src/renderer/components/chat/useForkAtMessage.ts, apps/desktop/src/renderer/components/chat/useImageClipboard.ts, apps/desktop/src/renderer/components/chat/useImageLoadFailure.ts, apps/desktop/src/renderer/components/chat/useLinkContextMenu.tsx, apps/desktop/src/renderer/components/chat/useMessageViewport.ts, apps/desktop/src/renderer/components/chat/useNavigationKeyListener.ts, apps/desktop/src/renderer/components/chat/useOpenWithMenu.tsx, apps/desktop/src/renderer/components/chat/usePrevUserMessageInView.ts, apps/desktop/src/renderer/components/chat/useTurnChangeSets.ts, apps/desktop/src/renderer/components/chat/useVideoContextMenu.ts, apps/desktop/src/renderer/components/chat/useWorkspaceMediaPath.tsx, apps/desktop/src/renderer/components/chat/userMessageCollapse.ts, apps/desktop/src/renderer/components/chat/userMessageDisplayText.ts, apps/desktop/src/renderer/components/chat/userMessageLinkify.ts, apps/desktop/src/renderer/components/chat/videoContextMenu.css, apps/desktop/src/renderer/components/chat/viewportFillDetect.ts, apps/desktop/src/renderer/components/new-chat/AddRemoteProjectDialog.tsx, apps/desktop/src/renderer/components/new-chat/AgentSelect.tsx, apps/desktop/src/renderer/components/new-chat/AskUserQuestionPrompt.tsx, apps/desktop/src/renderer/components/new-chat/AtMentionPanel.tsx, apps/desktop/src/renderer/components/new-chat/AttachmentTypeThumb.tsx, apps/desktop/src/renderer/components/new-chat/ChatInput.tsx, apps/desktop/src/renderer/components/new-chat/CjkPunctDecoration.ts, apps/desktop/src/renderer/components/new-chat/CjkPunctuationUtils.ts, apps/desktop/src/renderer/components/new-chat/ComposerAttachments.tsx, apps/desktop/src/renderer/components/new-chat/ComposerCard.tsx, apps/desktop/src/renderer/components/new-chat/ComposerFolderTransfers.tsx, apps/desktop/src/renderer/components/new-chat/ComposerListIndentDecoration.ts, apps/desktop/src/renderer/components/new-chat/ComposerListNodes.ts, apps/desktop/src/renderer/components/new-chat/ComposerQuoteNode.tsx, apps/desktop/src/renderer/components/new-chat/DeviceSwitcherPill.tsx, apps/desktop/src/renderer/components/new-chat/EffortSlider.tsx, apps/desktop/src/renderer/components/new-chat/EmptyDocSelectionGuard.ts, apps/desktop/src/renderer/components/new-chat/ExtraDirsButton.tsx, apps/desktop/src/renderer/components/new-chat/FastModeToggle.tsx, apps/desktop/src/renderer/components/new-chat/FolderPickerPopover.tsx, apps/desktop/src/renderer/components/new-chat/FullAccessConfirmContent.tsx, apps/desktop/src/renderer/components/new-chat/GhostCommandDecoration.ts, apps/desktop/src/renderer/components/new-chat/GoalAdvancedLimits.tsx, apps/desktop/src/renderer/components/new-chat/GoalIndicator.tsx, apps/desktop/src/renderer/components/new-chat/HomeUsageDashboard.tsx, apps/desktop/src/renderer/components/new-chat/ListComposerTextarea.tsx, apps/desktop/src/renderer/components/new-chat/LocalModelCatalogNotice.tsx, apps/desktop/src/renderer/components/new-chat/McpSetupPrompt.tsx, apps/desktop/src/renderer/components/new-chat/MentionChipNode.tsx, apps/desktop/src/renderer/components/new-chat/MentionDragCaretDecoration.ts, apps/desktop/src/renderer/components/new-chat/ModelCompatibilityNotice.tsx, apps/desktop/src/renderer/components/new-chat/ModelConfigFlyout.tsx, apps/desktop/src/renderer/components/new-chat/ModelHarnessPicker.tsx, apps/desktop/src/renderer/components/new-chat/ModelRouteSelector.tsx, apps/desktop/src/renderer/components/new-chat/ModelSelector.tsx, apps/desktop/src/renderer/components/new-chat/ModelSourceDetails.tsx, apps/desktop/src/renderer/components/new-chat/NewGoalDialog.tsx, apps/desktop/src/renderer/components/new-chat/PastedTextChipNode.tsx, apps/desktop/src/renderer/components/new-chat/PendingQueuePanel.tsx, apps/desktop/src/renderer/components/new-chat/PermissionPrompt.tsx, apps/desktop/src/renderer/components/new-chat/PermissionSelector.tsx, apps/desktop/src/renderer/components/new-chat/PinnedPlanPanel.tsx, apps/desktop/src/renderer/components/new-chat/PlanActionCard.tsx, apps/desktop/src/renderer/components/new-chat/PlanModeIndicator.tsx, apps/desktop/src/renderer/components/new-chat/PlanViewerCard.tsx, apps/desktop/src/renderer/components/new-chat/PluginConnectionForm.tsx, apps/desktop/src/renderer/components/new-chat/PluginModelsSection.tsx, apps/desktop/src/renderer/components/new-chat/PluginOauthDeviceCode.tsx, apps/desktop/src/renderer/components/new-chat/PluginSetupPrompt.tsx, apps/desktop/src/renderer/components/new-chat/PluginSetupRemoteDesktopButton.tsx, apps/desktop/src/renderer/components/new-chat/PluginUiEnablePrompt.tsx, apps/desktop/src/renderer/components/new-chat/PluginWriteAccessRecovery.tsx, apps/desktop/src/renderer/components/new-chat/QuickStartPillMark.ts, apps/desktop/src/renderer/components/new-chat/RemoteOauthSetupCard.tsx, apps/desktop/src/renderer/components/new-chat/RemotePluginUiEnablePrompt.tsx, apps/desktop/src/renderer/components/new-chat/SendButton.tsx, apps/desktop/src/renderer/components/new-chat/SlashCommandDecoration.ts, apps/desktop/src/renderer/components/new-chat/SlashCommandPalette.tsx, apps/desktop/src/renderer/components/new-chat/SpeedIcon.tsx, apps/desktop/src/renderer/components/new-chat/TakeoverMask.tsx, apps/desktop/src/renderer/components/new-chat/ThinkingToggle.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedFlyoutHost.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedModelPanel.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedModelRail.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedModelRow.tsx, apps/desktop/src/renderer/components/new-chat/UsageDailyBars.tsx, apps/desktop/src/renderer/components/new-chat/UsageHeatmap.tsx, apps/desktop/src/renderer/components/new-chat/VendorSegmentedSwitcher.tsx, apps/desktop/src/renderer/components/new-chat/VoiceInputDraftDecoration.ts, apps/desktop/src/renderer/components/new-chat/WindowsSelectionReplacement.ts, apps/desktop/src/renderer/components/new-chat/WorktreeChipsRow.tsx, apps/desktop/src/renderer/components/new-chat/WorktreeCreatingOverlay.tsx, apps/desktop/src/renderer/components/new-chat/agentOptions.ts, apps/desktop/src/renderer/components/new-chat/agentSwitchConfirmation.ts, apps/desktop/src/renderer/components/new-chat/askUserDictation.ts, apps/desktop/src/renderer/components/new-chat/askUserVoiceShortcutOwner.ts, apps/desktop/src/renderer/components/new-chat/branchPick.ts, apps/desktop/src/renderer/components/new-chat/composerBlankPointerFocus.ts, apps/desktop/src/renderer/components/new-chat/composerContentSerialization.ts, apps/desktop/src/renderer/components/new-chat/composerDocState.ts, apps/desktop/src/renderer/components/new-chat/composerFrameScheduler.ts, apps/desktop/src/renderer/components/new-chat/composerHistoryProjection.ts, apps/desktop/src/renderer/components/new-chat/composerModelSelection.ts, apps/desktop/src/renderer/components/new-chat/composerPromptRecommendation.ts, apps/desktop/src/renderer/components/new-chat/composerRenderGate.ts, apps/desktop/src/renderer/components/new-chat/composerSendOwnership.ts, apps/desktop/src/renderer/components/new-chat/composerSendSnapshot.ts, apps/desktop/src/renderer/components/new-chat/effortChangeQueue.ts, apps/desktop/src/renderer/components/new-chat/extraDirsActions.ts, apps/desktop/src/renderer/components/new-chat/favoritesSyncFailure.ts, apps/desktop/src/renderer/components/new-chat/ghostComposerPlacement.ts, apps/desktop/src/renderer/components/new-chat/localAttachmentPicker.ts, apps/desktop/src/renderer/components/new-chat/mentionChipInsertion.ts, apps/desktop/src/renderer/components/new-chat/modelPickerPreferences.ts, apps/desktop/src/renderer/components/new-chat/modelWindowErrorToast.ts, apps/desktop/src/renderer/components/new-chat/pastePipeline.ts, apps/desktop/src/renderer/components/new-chat/pathPaste.ts, apps/desktop/src/renderer/components/new-chat/pendingQueueLocks.ts, apps/desktop/src/renderer/components/new-chat/pendingQueueRowActivity.ts, apps/desktop/src/renderer/components/new-chat/pendingQueueRowPresentation.ts, apps/desktop/src/renderer/components/new-chat/planModeComposerCommand.ts, apps/desktop/src/renderer/components/new-chat/priceTierMarks.tsx, apps/desktop/src/renderer/components/new-chat/remoteBrowseAdapters.ts, apps/desktop/src/renderer/components/new-chat/remoteExistingProjects.ts, apps/desktop/src/renderer/components/new-chat/restoreComposerDocument.ts, apps/desktop/src/renderer/components/new-chat/sessionLinkPaste.ts, apps/desktop/src/renderer/components/new-chat/sourceSwitch.ts, apps/desktop/src/renderer/components/new-chat/unifiedModelSelection.ts, apps/desktop/src/renderer/components/new-chat/usagePalette.ts, apps/desktop/src/renderer/components/new-chat/useAskUserDictation.ts, apps/desktop/src/renderer/components/new-chat/useComposerSendFocusRestore.ts, apps/desktop/src/renderer/components/new-chat/useModelDiscoveryPending.ts, apps/desktop/src/renderer/components/new-chat/useProviderWeeklyQuota.ts, apps/desktop/src/renderer/components/new-chat/useUnifiedRowActions.ts, apps/desktop/src/renderer/components/new-chat/voiceQuestionSelection.ts, apps/desktop/src/renderer/features/cc-agent/CCAgentFeatureLayout.tsx, apps/desktop/src/renderer/features/cc-agent/CCAgentSessionView.tsx, apps/desktop/src/renderer/features/cc-agent/OrcaSplitView.tsx, apps/desktop/src/renderer/features/cc-agent/OrcaWorkerPanel.tsx, apps/desktop/src/renderer/features/cc-agent/OrcaWorkflowRoute.tsx | 268 | 38 | 366 | +| `desktop.chat.orca-workflow` | desktop | Orca 协同工作台 | hash `/cc-agent/orca/:sessionId`(OrcaWorkflowRoute) | CCAgentSessionView, OrcaSplitView, OrcaWorkerPanel, OrcaWorkflowRoute | apps/desktop/src/renderer/components/chat/AgentActionRow.tsx, apps/desktop/src/renderer/components/chat/AgentActionsBlock.tsx, apps/desktop/src/renderer/components/chat/AgentTaskCard.tsx, apps/desktop/src/renderer/components/chat/AgentTaskLiveDetails.tsx, apps/desktop/src/renderer/components/chat/AnnotationStrokesSvg.tsx, apps/desktop/src/renderer/components/chat/AskUserQuestionBubble.tsx, apps/desktop/src/renderer/components/chat/AssistantMessage.tsx, apps/desktop/src/renderer/components/chat/AttachmentCollection.tsx, apps/desktop/src/renderer/components/chat/AttachmentCollectionContext.ts, apps/desktop/src/renderer/components/chat/AutomationOriginBadge.tsx, apps/desktop/src/renderer/components/chat/ChatAudioCard.tsx, apps/desktop/src/renderer/components/chat/ChatBubble.tsx, apps/desktop/src/renderer/components/chat/ChatDisplaySnapshotContext.tsx, apps/desktop/src/renderer/components/chat/ChatImageView.tsx, apps/desktop/src/renderer/components/chat/ChatLoadingImage.tsx, apps/desktop/src/renderer/components/chat/ChatReplyIcon.tsx, apps/desktop/src/renderer/components/chat/ChatSessionFileContext.tsx, apps/desktop/src/renderer/components/chat/ChatSoundEffectCard.tsx, apps/desktop/src/renderer/components/chat/ChatVideoView.tsx, apps/desktop/src/renderer/components/chat/CindyMakeDoctorCard.tsx, apps/desktop/src/renderer/components/chat/CopyAsImageBlock.tsx, apps/desktop/src/renderer/components/chat/CredentialSwitchWaitBanner.tsx, apps/desktop/src/renderer/components/chat/DiffView.tsx, apps/desktop/src/renderer/components/chat/ErrorBanner.tsx, apps/desktop/src/renderer/components/chat/ErrorMessageCard.tsx, apps/desktop/src/renderer/components/chat/FileLightbox.tsx, apps/desktop/src/renderer/components/chat/GeneratedFilesCard.tsx, apps/desktop/src/renderer/components/chat/GhostCardForm.tsx, apps/desktop/src/renderer/components/chat/GhostCardHostPrompts.tsx, apps/desktop/src/renderer/components/chat/GhostCardRecipients.tsx, apps/desktop/src/renderer/components/chat/GhostMailHistory.tsx, apps/desktop/src/renderer/components/chat/GhostSummonCard.tsx, apps/desktop/src/renderer/components/chat/GhostToolCard.tsx, apps/desktop/src/renderer/components/chat/HookTaskCard.tsx, apps/desktop/src/renderer/components/chat/ImageGalleryContext.tsx, apps/desktop/src/renderer/components/chat/ImageHoverPreview.tsx, apps/desktop/src/renderer/components/chat/ImageLightbox.tsx, apps/desktop/src/renderer/components/chat/ImageMissingPlaceholder.tsx, apps/desktop/src/renderer/components/chat/InlineReferenceChip.tsx, apps/desktop/src/renderer/components/chat/InterruptedTurnBanner.tsx, apps/desktop/src/renderer/components/chat/JumpToBottomChip.tsx, apps/desktop/src/renderer/components/chat/MailHtmlPreview.tsx, apps/desktop/src/renderer/components/chat/MarkdownDiffBlock.tsx, apps/desktop/src/renderer/components/chat/MarkdownMermaidBlock.tsx, apps/desktop/src/renderer/components/chat/MarkdownRenderer.tsx, apps/desktop/src/renderer/components/chat/McpAppCard.tsx, apps/desktop/src/renderer/components/chat/McpAppPermissionPrompt.tsx, apps/desktop/src/renderer/components/chat/MermaidLightbox.tsx, apps/desktop/src/renderer/components/chat/MessageActionBar.tsx, apps/desktop/src/renderer/components/chat/MessageNavRail.tsx, apps/desktop/src/renderer/components/chat/MessageReadAloudContext.ts, apps/desktop/src/renderer/components/chat/MessageSourceLabels.tsx, apps/desktop/src/renderer/components/chat/MessageStream.tsx, apps/desktop/src/renderer/components/chat/MessageViewport.tsx, apps/desktop/src/renderer/components/chat/ModelLightbox.tsx, apps/desktop/src/renderer/components/chat/NewMessageIndicator.tsx, apps/desktop/src/renderer/components/chat/PlanReviewBubble.tsx, apps/desktop/src/renderer/components/chat/PrevMessageJumpChip.tsx, apps/desktop/src/renderer/components/chat/PreviewChrome.tsx, apps/desktop/src/renderer/components/chat/ProjectLinkChip.tsx, apps/desktop/src/renderer/components/chat/QuoteChip.tsx, apps/desktop/src/renderer/components/chat/RewindPreviewDialog.tsx, apps/desktop/src/renderer/components/chat/RichAnswer.tsx, apps/desktop/src/renderer/components/chat/SelectionQuoteButton.tsx, apps/desktop/src/renderer/components/chat/SentAgentReferenceChip.tsx, apps/desktop/src/renderer/components/chat/SentInlineAtomBody.tsx, apps/desktop/src/renderer/components/chat/SessionHandoffCard.tsx, apps/desktop/src/renderer/components/chat/SessionLinkChip.tsx, apps/desktop/src/renderer/components/chat/SessionQuickSendContext.tsx, apps/desktop/src/renderer/components/chat/ShareMessageCheckbox.tsx, apps/desktop/src/renderer/components/chat/ShareSelectionBar.tsx, apps/desktop/src/renderer/components/chat/SlackIcon.tsx, apps/desktop/src/renderer/components/chat/StreamFadeSpan.tsx, apps/desktop/src/renderer/components/chat/StreamingMarkdownChunk.tsx, apps/desktop/src/renderer/components/chat/SystemCard.tsx, apps/desktop/src/renderer/components/chat/TextLightbox.tsx, apps/desktop/src/renderer/components/chat/ThinkingCard.tsx, apps/desktop/src/renderer/components/chat/ThinkingText.tsx, apps/desktop/src/renderer/components/chat/TodoListCard.tsx, apps/desktop/src/renderer/components/chat/ToolCallCard.tsx, apps/desktop/src/renderer/components/chat/ToolPayloadLightbox.tsx, apps/desktop/src/renderer/components/chat/TopRightChipStack.tsx, apps/desktop/src/renderer/components/chat/TurnChangesCard.tsx, apps/desktop/src/renderer/components/chat/UnreadFailedScheduleBanner.tsx, apps/desktop/src/renderer/components/chat/UpgradeBanner.tsx, apps/desktop/src/renderer/components/chat/UserAttachmentChip.tsx, apps/desktop/src/renderer/components/chat/UserMessage.tsx, apps/desktop/src/renderer/components/chat/UserMessageEditBox.tsx, apps/desktop/src/renderer/components/chat/UserMessageImageLink.tsx, apps/desktop/src/renderer/components/chat/UserMessageUrlLink.tsx, apps/desktop/src/renderer/components/chat/VideoLightbox.tsx, apps/desktop/src/renderer/components/chat/VideoSitePreview.tsx, apps/desktop/src/renderer/components/chat/WorkGroupBlock.tsx, apps/desktop/src/renderer/components/chat/WorktreeRestoreBanner.tsx, apps/desktop/src/renderer/components/chat/XIcon.tsx, apps/desktop/src/renderer/components/chat/activityRowChrome.ts, apps/desktop/src/renderer/components/chat/animateFocusScroll.ts, apps/desktop/src/renderer/components/chat/autoFollowIntent.ts, apps/desktop/src/renderer/components/chat/chatChrome.ts, apps/desktop/src/renderer/components/chat/errorReasonI18n.ts, apps/desktop/src/renderer/components/chat/generatedFilesProjection.ts, apps/desktop/src/renderer/components/chat/historyAfter.ts, apps/desktop/src/renderer/components/chat/imageDisplaySize.ts, apps/desktop/src/renderer/components/chat/lightboxAnnotations.ts, apps/desktop/src/renderer/components/chat/lightboxGestures.ts, apps/desktop/src/renderer/components/chat/linkInteraction.ts, apps/desktop/src/renderer/components/chat/mailBodyEditing.ts, apps/desktop/src/renderer/components/chat/mailCardFooter.ts, apps/desktop/src/renderer/components/chat/mailPreviewAppearance.ts, apps/desktop/src/renderer/components/chat/markdownImageTargets.ts, apps/desktop/src/renderer/components/chat/markdownMedia.ts, apps/desktop/src/renderer/components/chat/messageNavRailModel.ts, apps/desktop/src/renderer/components/chat/messageNavRailWheel.ts, apps/desktop/src/renderer/components/chat/messageNavigationProjection.ts, apps/desktop/src/renderer/components/chat/messageProjectionCache.ts, apps/desktop/src/renderer/components/chat/messageStreamIndicatorPosition.ts, apps/desktop/src/renderer/components/chat/messageViewportCompensation.ts, apps/desktop/src/renderer/components/chat/messageViewportGeometry.ts, apps/desktop/src/renderer/components/chat/messageWorkGroups.ts, apps/desktop/src/renderer/components/chat/observeProgrammaticScroll.ts, apps/desktop/src/renderer/components/chat/rehypeFencedCodeMarker.ts, apps/desktop/src/renderer/components/chat/rehypeHighlightShared.ts, apps/desktop/src/renderer/components/chat/rehypeMathBlockMarker.ts, apps/desktop/src/renderer/components/chat/rehypeStreamWordFade.ts, apps/desktop/src/renderer/components/chat/remarkHtmlImages.ts, apps/desktop/src/renderer/components/chat/remarkLocalPathLinks.ts, apps/desktop/src/renderer/components/chat/remarkPreserveRawLocalDestinations.ts, apps/desktop/src/renderer/components/chat/remarkSessionLinks.ts, apps/desktop/src/renderer/components/chat/remarkStrictInlineMath.ts, apps/desktop/src/renderer/components/chat/remarkTruncateCjkUrls.ts, apps/desktop/src/renderer/components/chat/repairStreamingMarkdown.ts, apps/desktop/src/renderer/components/chat/rich-answer.css, apps/desktop/src/renderer/components/chat/scrollAnchoringDetect.ts, apps/desktop/src/renderer/components/chat/shareSelectionStore.ts, apps/desktop/src/renderer/components/chat/streamingMarkdownBlocks.tsx, apps/desktop/src/renderer/components/chat/streamingMarkdownChunks.ts, apps/desktop/src/renderer/components/chat/streamingMessageProjection.ts, apps/desktop/src/renderer/components/chat/toolLoopI18n.ts, apps/desktop/src/renderer/components/chat/unreadCount.ts, apps/desktop/src/renderer/components/chat/useAutomaticHistoryLoadBudget.ts, apps/desktop/src/renderer/components/chat/useCopyAsImage.tsx, apps/desktop/src/renderer/components/chat/useDeleteMessage.ts, apps/desktop/src/renderer/components/chat/useFileChipContextMenu.tsx, apps/desktop/src/renderer/components/chat/useForkAtMessage.ts, apps/desktop/src/renderer/components/chat/useImageClipboard.ts, apps/desktop/src/renderer/components/chat/useImageLoadFailure.ts, apps/desktop/src/renderer/components/chat/useLinkContextMenu.tsx, apps/desktop/src/renderer/components/chat/useMessageViewport.ts, apps/desktop/src/renderer/components/chat/useNavigationKeyListener.ts, apps/desktop/src/renderer/components/chat/useOpenWithMenu.tsx, apps/desktop/src/renderer/components/chat/usePrevUserMessageInView.ts, apps/desktop/src/renderer/components/chat/useTurnChangeSets.ts, apps/desktop/src/renderer/components/chat/useVideoContextMenu.ts, apps/desktop/src/renderer/components/chat/useWorkspaceMediaPath.tsx, apps/desktop/src/renderer/components/chat/userMessageCollapse.ts, apps/desktop/src/renderer/components/chat/userMessageDisplayText.ts, apps/desktop/src/renderer/components/chat/userMessageLinkify.ts, apps/desktop/src/renderer/components/chat/videoContextMenu.css, apps/desktop/src/renderer/components/chat/viewportFillDetect.ts, apps/desktop/src/renderer/components/new-chat/AddRemoteProjectDialog.tsx, apps/desktop/src/renderer/components/new-chat/AgentSelect.tsx, apps/desktop/src/renderer/components/new-chat/AskUserQuestionPrompt.tsx, apps/desktop/src/renderer/components/new-chat/AtMentionPanel.tsx, apps/desktop/src/renderer/components/new-chat/AttachmentTypeThumb.tsx, apps/desktop/src/renderer/components/new-chat/ChatInput.tsx, apps/desktop/src/renderer/components/new-chat/CjkPunctDecoration.ts, apps/desktop/src/renderer/components/new-chat/CjkPunctuationUtils.ts, apps/desktop/src/renderer/components/new-chat/ComposerAttachments.tsx, apps/desktop/src/renderer/components/new-chat/ComposerCard.tsx, apps/desktop/src/renderer/components/new-chat/ComposerFolderTransfers.tsx, apps/desktop/src/renderer/components/new-chat/ComposerListIndentDecoration.ts, apps/desktop/src/renderer/components/new-chat/ComposerListNodes.ts, apps/desktop/src/renderer/components/new-chat/ComposerQuoteNode.tsx, apps/desktop/src/renderer/components/new-chat/DeviceSwitcherPill.tsx, apps/desktop/src/renderer/components/new-chat/EffortSlider.tsx, apps/desktop/src/renderer/components/new-chat/EmptyDocSelectionGuard.ts, apps/desktop/src/renderer/components/new-chat/ExtraDirsButton.tsx, apps/desktop/src/renderer/components/new-chat/FastModeToggle.tsx, apps/desktop/src/renderer/components/new-chat/FolderPickerPopover.tsx, apps/desktop/src/renderer/components/new-chat/FullAccessConfirmContent.tsx, apps/desktop/src/renderer/components/new-chat/GhostCommandDecoration.ts, apps/desktop/src/renderer/components/new-chat/GoalAdvancedLimits.tsx, apps/desktop/src/renderer/components/new-chat/GoalIndicator.tsx, apps/desktop/src/renderer/components/new-chat/HomeUsageDashboard.tsx, apps/desktop/src/renderer/components/new-chat/ListComposerTextarea.tsx, apps/desktop/src/renderer/components/new-chat/LocalModelCatalogNotice.tsx, apps/desktop/src/renderer/components/new-chat/McpSetupPrompt.tsx, apps/desktop/src/renderer/components/new-chat/MentionChipNode.tsx, apps/desktop/src/renderer/components/new-chat/MentionDragCaretDecoration.ts, apps/desktop/src/renderer/components/new-chat/ModelCompatibilityNotice.tsx, apps/desktop/src/renderer/components/new-chat/ModelConfigFlyout.tsx, apps/desktop/src/renderer/components/new-chat/ModelHarnessPicker.tsx, apps/desktop/src/renderer/components/new-chat/ModelRouteSelector.tsx, apps/desktop/src/renderer/components/new-chat/ModelSelector.tsx, apps/desktop/src/renderer/components/new-chat/ModelSourceDetails.tsx, apps/desktop/src/renderer/components/new-chat/NewGoalDialog.tsx, apps/desktop/src/renderer/components/new-chat/PastedTextChipNode.tsx, apps/desktop/src/renderer/components/new-chat/PendingQueuePanel.tsx, apps/desktop/src/renderer/components/new-chat/PermissionPrompt.tsx, apps/desktop/src/renderer/components/new-chat/PermissionSelector.tsx, apps/desktop/src/renderer/components/new-chat/PinnedPlanPanel.tsx, apps/desktop/src/renderer/components/new-chat/PlanActionCard.tsx, apps/desktop/src/renderer/components/new-chat/PlanModeIndicator.tsx, apps/desktop/src/renderer/components/new-chat/PlanViewerCard.tsx, apps/desktop/src/renderer/components/new-chat/PluginConnectionForm.tsx, apps/desktop/src/renderer/components/new-chat/PluginModelsSection.tsx, apps/desktop/src/renderer/components/new-chat/PluginOauthDeviceCode.tsx, apps/desktop/src/renderer/components/new-chat/PluginSetupPrompt.tsx, apps/desktop/src/renderer/components/new-chat/PluginSetupRemoteDesktopButton.tsx, apps/desktop/src/renderer/components/new-chat/PluginUiEnablePrompt.tsx, apps/desktop/src/renderer/components/new-chat/PluginWriteAccessRecovery.tsx, apps/desktop/src/renderer/components/new-chat/QuickStartPillMark.ts, apps/desktop/src/renderer/components/new-chat/RemoteOauthSetupCard.tsx, apps/desktop/src/renderer/components/new-chat/RemotePluginUiEnablePrompt.tsx, apps/desktop/src/renderer/components/new-chat/SendButton.tsx, apps/desktop/src/renderer/components/new-chat/SlashCommandDecoration.ts, apps/desktop/src/renderer/components/new-chat/SlashCommandPalette.tsx, apps/desktop/src/renderer/components/new-chat/SpeedIcon.tsx, apps/desktop/src/renderer/components/new-chat/TakeoverMask.tsx, apps/desktop/src/renderer/components/new-chat/ThinkingToggle.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedFlyoutHost.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedModelPanel.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedModelRail.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedModelRow.tsx, apps/desktop/src/renderer/components/new-chat/UsageDailyBars.tsx, apps/desktop/src/renderer/components/new-chat/UsageHeatmap.tsx, apps/desktop/src/renderer/components/new-chat/VendorSegmentedSwitcher.tsx, apps/desktop/src/renderer/components/new-chat/VoiceInputDraftDecoration.ts, apps/desktop/src/renderer/components/new-chat/WindowsSelectionReplacement.ts, apps/desktop/src/renderer/components/new-chat/WorktreeChipsRow.tsx, apps/desktop/src/renderer/components/new-chat/WorktreeCreatingOverlay.tsx, apps/desktop/src/renderer/components/new-chat/agentOptions.ts, apps/desktop/src/renderer/components/new-chat/agentSwitchConfirmation.ts, apps/desktop/src/renderer/components/new-chat/askUserDictation.ts, apps/desktop/src/renderer/components/new-chat/askUserVoiceShortcutOwner.ts, apps/desktop/src/renderer/components/new-chat/branchPick.ts, apps/desktop/src/renderer/components/new-chat/composerBlankPointerFocus.ts, apps/desktop/src/renderer/components/new-chat/composerContentSerialization.ts, apps/desktop/src/renderer/components/new-chat/composerDocState.ts, apps/desktop/src/renderer/components/new-chat/composerFrameScheduler.ts, apps/desktop/src/renderer/components/new-chat/composerHistoryProjection.ts, apps/desktop/src/renderer/components/new-chat/composerModelSelection.ts, apps/desktop/src/renderer/components/new-chat/composerPromptRecommendation.ts, apps/desktop/src/renderer/components/new-chat/composerRenderGate.ts, apps/desktop/src/renderer/components/new-chat/composerSendOwnership.ts, apps/desktop/src/renderer/components/new-chat/composerSendSnapshot.ts, apps/desktop/src/renderer/components/new-chat/effortChangeQueue.ts, apps/desktop/src/renderer/components/new-chat/extraDirsActions.ts, apps/desktop/src/renderer/components/new-chat/favoritesSyncFailure.ts, apps/desktop/src/renderer/components/new-chat/ghostComposerPlacement.ts, apps/desktop/src/renderer/components/new-chat/localAttachmentPicker.ts, apps/desktop/src/renderer/components/new-chat/mentionChipInsertion.ts, apps/desktop/src/renderer/components/new-chat/modelPickerPreferences.ts, apps/desktop/src/renderer/components/new-chat/modelWindowErrorToast.ts, apps/desktop/src/renderer/components/new-chat/pastePipeline.ts, apps/desktop/src/renderer/components/new-chat/pathPaste.ts, apps/desktop/src/renderer/components/new-chat/pendingQueueLocks.ts, apps/desktop/src/renderer/components/new-chat/pendingQueueRowActivity.ts, apps/desktop/src/renderer/components/new-chat/pendingQueueRowPresentation.ts, apps/desktop/src/renderer/components/new-chat/planModeComposerCommand.ts, apps/desktop/src/renderer/components/new-chat/priceTierMarks.tsx, apps/desktop/src/renderer/components/new-chat/remoteBrowseAdapters.ts, apps/desktop/src/renderer/components/new-chat/remoteExistingProjects.ts, apps/desktop/src/renderer/components/new-chat/restoreComposerDocument.ts, apps/desktop/src/renderer/components/new-chat/sessionLinkPaste.ts, apps/desktop/src/renderer/components/new-chat/sourceSwitch.ts, apps/desktop/src/renderer/components/new-chat/unifiedModelSelection.ts, apps/desktop/src/renderer/components/new-chat/usagePalette.ts, apps/desktop/src/renderer/components/new-chat/useAskUserDictation.ts, apps/desktop/src/renderer/components/new-chat/useComposerSendFocusRestore.ts, apps/desktop/src/renderer/components/new-chat/useModelDiscoveryPending.ts, apps/desktop/src/renderer/components/new-chat/useProviderWeeklyQuota.ts, apps/desktop/src/renderer/components/new-chat/useUnifiedRowActions.ts, apps/desktop/src/renderer/components/new-chat/voiceQuestionSelection.ts, apps/desktop/src/renderer/features/cc-agent/CCAgentFeatureLayout.tsx, apps/desktop/src/renderer/features/cc-agent/CCAgentSessionView.tsx, apps/desktop/src/renderer/features/cc-agent/OrcaSplitView.tsx, apps/desktop/src/renderer/features/cc-agent/OrcaWorkerPanel.tsx, apps/desktop/src/renderer/features/cc-agent/OrcaWorkflowRoute.tsx | 265 | 34 | 361 | | `desktop.chat.scheduled` | desktop | 调度任务列表 | hash `/cc-agent/scheduled`(SchedulerPage) | SchedulerPage | apps/desktop/src/renderer/features/scheduler/SchedulerPage.tsx, apps/desktop/src/renderer/features/scheduler/components/AgentMark.tsx, apps/desktop/src/renderer/features/scheduler/components/BoundSessionCard.tsx, apps/desktop/src/renderer/features/scheduler/components/PreRunPluginFields.tsx, apps/desktop/src/renderer/features/scheduler/components/ProjectAutomationNotifyBridge.tsx, apps/desktop/src/renderer/features/scheduler/components/RunHistoryCard.tsx, apps/desktop/src/renderer/features/scheduler/components/RunHistoryPane.tsx, apps/desktop/src/renderer/features/scheduler/components/ScheduleChips.tsx, apps/desktop/src/renderer/features/scheduler/components/ScheduleFormDialog.tsx, apps/desktop/src/renderer/features/scheduler/components/ScheduleSessionIndexOwner.tsx, apps/desktop/src/renderer/features/scheduler/components/ScheduleStatusChip.tsx, apps/desktop/src/renderer/features/scheduler/components/ScriptCapabilityMultiSelect.tsx, apps/desktop/src/renderer/features/scheduler/components/TaskListCell.tsx, apps/desktop/src/renderer/features/scheduler/components/TaskListFilterPopover.tsx, apps/desktop/src/renderer/features/scheduler/components/TaskListPane.tsx, apps/desktop/src/renderer/features/scheduler/components/TemplateCard.tsx, apps/desktop/src/renderer/features/scheduler/components/TemplateGallery.tsx, apps/desktop/src/renderer/features/scheduler/components/TemplateParamForm.tsx, apps/desktop/src/renderer/features/scheduler/hooks/useDeleteScheduleWithSessions.tsx, apps/desktop/src/renderer/features/scheduler/hooks/useReadFailedScheduleRuns.ts, apps/desktop/src/renderer/features/scheduler/hooks/useRunNowBusyGuard.ts, apps/desktop/src/renderer/features/scheduler/hooks/useRuns.ts, apps/desktop/src/renderer/features/scheduler/hooks/useScheduleForm.ts, apps/desktop/src/renderer/features/scheduler/hooks/useScheduleUnreadRunCounts.ts, apps/desktop/src/renderer/features/scheduler/hooks/useSchedules.ts, apps/desktop/src/renderer/features/scheduler/hooks/useSessionReferences.ts, apps/desktop/src/renderer/features/scheduler/hooks/useTemplates.ts, apps/desktop/src/renderer/features/scheduler/index.ts, apps/desktop/src/renderer/features/scheduler/lib/archiveScheduleWarning.ts, apps/desktop/src/renderer/features/scheduler/lib/cronCodexPreset.ts, apps/desktop/src/renderer/features/scheduler/lib/cronToHuman.ts, apps/desktop/src/renderer/features/scheduler/lib/failedScheduleDismissal.ts, apps/desktop/src/renderer/features/scheduler/lib/formatters.ts, apps/desktop/src/renderer/features/scheduler/lib/pluginScheduleCreateIntent.ts, apps/desktop/src/renderer/features/scheduler/lib/projectAutomationConfig.ts, apps/desktop/src/renderer/features/scheduler/lib/projectScheduleSidebarIndex.ts, apps/desktop/src/renderer/features/scheduler/lib/runHistoryGrouping.ts, apps/desktop/src/renderer/features/scheduler/lib/runUnread.ts, apps/desktop/src/renderer/features/scheduler/lib/scheduleBindingIndex.ts, apps/desktop/src/renderer/features/scheduler/lib/scheduleFormLogic.ts, apps/desktop/src/renderer/features/scheduler/lib/scheduleRunReadSync.ts, apps/desktop/src/renderer/features/scheduler/lib/scheduleSessionBinding.ts, apps/desktop/src/renderer/features/scheduler/lib/scheduleSidebarIndexRuns.ts, apps/desktop/src/renderer/features/scheduler/lib/schedulesStore.ts, apps/desktop/src/renderer/features/scheduler/lib/statusFilter.ts, apps/desktop/src/renderer/features/scheduler/lib/statusFilterStorage.ts, apps/desktop/src/renderer/features/scheduler/lib/templateLocalization.ts | 32 | 0 | 89 | -| `desktop.chat.session` | desktop | 会话工作台 | hash `/cc-agent/:sessionId`;副窗经 `/cc-agent/boot` 解析后落到同一会话视图 | AssistantMessage, CCAgentFeatureLayout, CCAgentSessionView, ChatInput, PermissionPrompt | apps/desktop/src/renderer/components/chat/AgentActionRow.tsx, apps/desktop/src/renderer/components/chat/AgentActionsBlock.tsx, apps/desktop/src/renderer/components/chat/AgentTaskCard.tsx, apps/desktop/src/renderer/components/chat/AgentTaskLiveDetails.tsx, apps/desktop/src/renderer/components/chat/AnnotationStrokesSvg.tsx, apps/desktop/src/renderer/components/chat/AskUserQuestionBubble.tsx, apps/desktop/src/renderer/components/chat/AssistantMessage.tsx, apps/desktop/src/renderer/components/chat/AutomationOriginBadge.tsx, apps/desktop/src/renderer/components/chat/ChatAudioCard.tsx, apps/desktop/src/renderer/components/chat/ChatBubble.tsx, apps/desktop/src/renderer/components/chat/ChatDisplaySnapshotContext.tsx, apps/desktop/src/renderer/components/chat/ChatImageView.tsx, apps/desktop/src/renderer/components/chat/ChatLoadingImage.tsx, apps/desktop/src/renderer/components/chat/ChatReplyIcon.tsx, apps/desktop/src/renderer/components/chat/ChatSessionFileContext.tsx, apps/desktop/src/renderer/components/chat/ChatSoundEffectCard.tsx, apps/desktop/src/renderer/components/chat/ChatVideoView.tsx, apps/desktop/src/renderer/components/chat/CindyMakeDoctorCard.tsx, apps/desktop/src/renderer/components/chat/CopyAsImageBlock.tsx, apps/desktop/src/renderer/components/chat/CredentialSwitchWaitBanner.tsx, apps/desktop/src/renderer/components/chat/DiffView.tsx, apps/desktop/src/renderer/components/chat/ErrorBanner.tsx, apps/desktop/src/renderer/components/chat/ErrorMessageCard.tsx, apps/desktop/src/renderer/components/chat/GeneratedFilesCard.tsx, apps/desktop/src/renderer/components/chat/GhostCardForm.tsx, apps/desktop/src/renderer/components/chat/GhostCardHostPrompts.tsx, apps/desktop/src/renderer/components/chat/GhostCardRecipients.tsx, apps/desktop/src/renderer/components/chat/GhostMailHistory.tsx, apps/desktop/src/renderer/components/chat/GhostSummonCard.tsx, apps/desktop/src/renderer/components/chat/GhostToolCard.tsx, apps/desktop/src/renderer/components/chat/HookTaskCard.tsx, apps/desktop/src/renderer/components/chat/ImageGalleryContext.tsx, apps/desktop/src/renderer/components/chat/ImageHoverPreview.tsx, apps/desktop/src/renderer/components/chat/ImageLightbox.tsx, apps/desktop/src/renderer/components/chat/ImageMissingPlaceholder.tsx, apps/desktop/src/renderer/components/chat/InlineReferenceChip.tsx, apps/desktop/src/renderer/components/chat/InterruptedTurnBanner.tsx, apps/desktop/src/renderer/components/chat/JumpToBottomChip.tsx, apps/desktop/src/renderer/components/chat/MailHtmlPreview.tsx, apps/desktop/src/renderer/components/chat/MarkdownDiffBlock.tsx, apps/desktop/src/renderer/components/chat/MarkdownMermaidBlock.tsx, apps/desktop/src/renderer/components/chat/MarkdownRenderer.tsx, apps/desktop/src/renderer/components/chat/McpAppCard.tsx, apps/desktop/src/renderer/components/chat/McpAppPermissionPrompt.tsx, apps/desktop/src/renderer/components/chat/MermaidLightbox.tsx, apps/desktop/src/renderer/components/chat/MessageActionBar.tsx, apps/desktop/src/renderer/components/chat/MessageNavRail.tsx, apps/desktop/src/renderer/components/chat/MessageReadAloudContext.ts, apps/desktop/src/renderer/components/chat/MessageSourceLabels.tsx, apps/desktop/src/renderer/components/chat/MessageStream.tsx, apps/desktop/src/renderer/components/chat/MessageViewport.tsx, apps/desktop/src/renderer/components/chat/ModelLightbox.tsx, apps/desktop/src/renderer/components/chat/NewMessageIndicator.tsx, apps/desktop/src/renderer/components/chat/PlanReviewBubble.tsx, apps/desktop/src/renderer/components/chat/PrevMessageJumpChip.tsx, apps/desktop/src/renderer/components/chat/ProjectLinkChip.tsx, apps/desktop/src/renderer/components/chat/QuoteChip.tsx, apps/desktop/src/renderer/components/chat/RewindPreviewDialog.tsx, apps/desktop/src/renderer/components/chat/RichAnswer.tsx, apps/desktop/src/renderer/components/chat/SelectionQuoteButton.tsx, apps/desktop/src/renderer/components/chat/SentAgentReferenceChip.tsx, apps/desktop/src/renderer/components/chat/SentInlineAtomBody.tsx, apps/desktop/src/renderer/components/chat/SessionHandoffCard.tsx, apps/desktop/src/renderer/components/chat/SessionLinkChip.tsx, apps/desktop/src/renderer/components/chat/SessionQuickSendContext.tsx, apps/desktop/src/renderer/components/chat/ShareMessageCheckbox.tsx, apps/desktop/src/renderer/components/chat/ShareSelectionBar.tsx, apps/desktop/src/renderer/components/chat/SlackIcon.tsx, apps/desktop/src/renderer/components/chat/StreamFadeSpan.tsx, apps/desktop/src/renderer/components/chat/StreamingMarkdownChunk.tsx, apps/desktop/src/renderer/components/chat/SystemCard.tsx, apps/desktop/src/renderer/components/chat/TextLightbox.tsx, apps/desktop/src/renderer/components/chat/ThinkingCard.tsx, apps/desktop/src/renderer/components/chat/ThinkingText.tsx, apps/desktop/src/renderer/components/chat/TodoListCard.tsx, apps/desktop/src/renderer/components/chat/ToolCallCard.tsx, apps/desktop/src/renderer/components/chat/ToolPayloadLightbox.tsx, apps/desktop/src/renderer/components/chat/TopRightChipStack.tsx, apps/desktop/src/renderer/components/chat/TurnChangesCard.tsx, apps/desktop/src/renderer/components/chat/UnreadFailedScheduleBanner.tsx, apps/desktop/src/renderer/components/chat/UpgradeBanner.tsx, apps/desktop/src/renderer/components/chat/UserAttachmentChip.tsx, apps/desktop/src/renderer/components/chat/UserMessage.tsx, apps/desktop/src/renderer/components/chat/UserMessageEditBox.tsx, apps/desktop/src/renderer/components/chat/UserMessageImageLink.tsx, apps/desktop/src/renderer/components/chat/UserMessageUrlLink.tsx, apps/desktop/src/renderer/components/chat/VideoLightbox.tsx, apps/desktop/src/renderer/components/chat/VideoSitePreview.tsx, apps/desktop/src/renderer/components/chat/WorkGroupBlock.tsx, apps/desktop/src/renderer/components/chat/WorktreeRestoreBanner.tsx, apps/desktop/src/renderer/components/chat/XIcon.tsx, apps/desktop/src/renderer/components/chat/activityRowChrome.ts, apps/desktop/src/renderer/components/chat/animateFocusScroll.ts, apps/desktop/src/renderer/components/chat/autoFollowIntent.ts, apps/desktop/src/renderer/components/chat/chatChrome.ts, apps/desktop/src/renderer/components/chat/errorReasonI18n.ts, apps/desktop/src/renderer/components/chat/generatedFilesProjection.ts, apps/desktop/src/renderer/components/chat/historyAfter.ts, apps/desktop/src/renderer/components/chat/imageDisplaySize.ts, apps/desktop/src/renderer/components/chat/lightboxAnnotations.ts, apps/desktop/src/renderer/components/chat/lightboxGestures.ts, apps/desktop/src/renderer/components/chat/linkInteraction.ts, apps/desktop/src/renderer/components/chat/mailBodyEditing.ts, apps/desktop/src/renderer/components/chat/mailCardFooter.ts, apps/desktop/src/renderer/components/chat/mailPreviewAppearance.ts, apps/desktop/src/renderer/components/chat/markdownImageTargets.ts, apps/desktop/src/renderer/components/chat/markdownMedia.ts, apps/desktop/src/renderer/components/chat/messageNavRailModel.ts, apps/desktop/src/renderer/components/chat/messageNavRailWheel.ts, apps/desktop/src/renderer/components/chat/messageNavigationProjection.ts, apps/desktop/src/renderer/components/chat/messageProjectionCache.ts, apps/desktop/src/renderer/components/chat/messageStreamIndicatorPosition.ts, apps/desktop/src/renderer/components/chat/messageViewportCompensation.ts, apps/desktop/src/renderer/components/chat/messageViewportGeometry.ts, apps/desktop/src/renderer/components/chat/messageWorkGroups.ts, apps/desktop/src/renderer/components/chat/observeProgrammaticScroll.ts, apps/desktop/src/renderer/components/chat/rehypeFencedCodeMarker.ts, apps/desktop/src/renderer/components/chat/rehypeHighlightShared.ts, apps/desktop/src/renderer/components/chat/rehypeMathBlockMarker.ts, apps/desktop/src/renderer/components/chat/rehypeStreamWordFade.ts, apps/desktop/src/renderer/components/chat/remarkHtmlImages.ts, apps/desktop/src/renderer/components/chat/remarkLocalPathLinks.ts, apps/desktop/src/renderer/components/chat/remarkPreserveRawLocalDestinations.ts, apps/desktop/src/renderer/components/chat/remarkSessionLinks.ts, apps/desktop/src/renderer/components/chat/remarkStrictInlineMath.ts, apps/desktop/src/renderer/components/chat/remarkTruncateCjkUrls.ts, apps/desktop/src/renderer/components/chat/repairStreamingMarkdown.ts, apps/desktop/src/renderer/components/chat/rich-answer.css, apps/desktop/src/renderer/components/chat/scrollAnchoringDetect.ts, apps/desktop/src/renderer/components/chat/shareSelectionStore.ts, apps/desktop/src/renderer/components/chat/streamingMarkdownBlocks.tsx, apps/desktop/src/renderer/components/chat/streamingMarkdownChunks.ts, apps/desktop/src/renderer/components/chat/streamingMessageProjection.ts, apps/desktop/src/renderer/components/chat/toolLoopI18n.ts, apps/desktop/src/renderer/components/chat/unreadCount.ts, apps/desktop/src/renderer/components/chat/useAutomaticHistoryLoadBudget.ts, apps/desktop/src/renderer/components/chat/useCopyAsImage.tsx, apps/desktop/src/renderer/components/chat/useDeleteMessage.ts, apps/desktop/src/renderer/components/chat/useFileChipContextMenu.tsx, apps/desktop/src/renderer/components/chat/useForkAtMessage.ts, apps/desktop/src/renderer/components/chat/useImageClipboard.ts, apps/desktop/src/renderer/components/chat/useImageLoadFailure.ts, apps/desktop/src/renderer/components/chat/useLinkContextMenu.tsx, apps/desktop/src/renderer/components/chat/useMessageViewport.ts, apps/desktop/src/renderer/components/chat/useNavigationKeyListener.ts, apps/desktop/src/renderer/components/chat/useOpenWithMenu.tsx, apps/desktop/src/renderer/components/chat/usePrevUserMessageInView.ts, apps/desktop/src/renderer/components/chat/useTurnChangeSets.ts, apps/desktop/src/renderer/components/chat/useVideoContextMenu.ts, apps/desktop/src/renderer/components/chat/useWorkspaceMediaPath.tsx, apps/desktop/src/renderer/components/chat/userMessageCollapse.ts, apps/desktop/src/renderer/components/chat/userMessageDisplayText.ts, apps/desktop/src/renderer/components/chat/userMessageLinkify.ts, apps/desktop/src/renderer/components/chat/videoContextMenu.css, apps/desktop/src/renderer/components/chat/viewportFillDetect.ts, apps/desktop/src/renderer/components/new-chat/AddRemoteProjectDialog.tsx, apps/desktop/src/renderer/components/new-chat/AgentSelect.tsx, apps/desktop/src/renderer/components/new-chat/AskUserQuestionPrompt.tsx, apps/desktop/src/renderer/components/new-chat/AtMentionPanel.tsx, apps/desktop/src/renderer/components/new-chat/AttachmentTypeThumb.tsx, apps/desktop/src/renderer/components/new-chat/ChatInput.tsx, apps/desktop/src/renderer/components/new-chat/CjkPunctDecoration.ts, apps/desktop/src/renderer/components/new-chat/CjkPunctuationUtils.ts, apps/desktop/src/renderer/components/new-chat/ComposerAttachments.tsx, apps/desktop/src/renderer/components/new-chat/ComposerCard.tsx, apps/desktop/src/renderer/components/new-chat/ComposerFolderTransfers.tsx, apps/desktop/src/renderer/components/new-chat/ComposerListIndentDecoration.ts, apps/desktop/src/renderer/components/new-chat/ComposerListNodes.ts, apps/desktop/src/renderer/components/new-chat/ComposerQuoteNode.tsx, apps/desktop/src/renderer/components/new-chat/DeviceSwitcherPill.tsx, apps/desktop/src/renderer/components/new-chat/EffortSlider.tsx, apps/desktop/src/renderer/components/new-chat/EmptyDocSelectionGuard.ts, apps/desktop/src/renderer/components/new-chat/ExtraDirsButton.tsx, apps/desktop/src/renderer/components/new-chat/FastModeToggle.tsx, apps/desktop/src/renderer/components/new-chat/FolderPickerPopover.tsx, apps/desktop/src/renderer/components/new-chat/FullAccessConfirmContent.tsx, apps/desktop/src/renderer/components/new-chat/GhostCommandDecoration.ts, apps/desktop/src/renderer/components/new-chat/GoalAdvancedLimits.tsx, apps/desktop/src/renderer/components/new-chat/GoalIndicator.tsx, apps/desktop/src/renderer/components/new-chat/HomeUsageDashboard.tsx, apps/desktop/src/renderer/components/new-chat/ListComposerTextarea.tsx, apps/desktop/src/renderer/components/new-chat/LocalModelCatalogNotice.tsx, apps/desktop/src/renderer/components/new-chat/McpSetupPrompt.tsx, apps/desktop/src/renderer/components/new-chat/MentionChipNode.tsx, apps/desktop/src/renderer/components/new-chat/MentionDragCaretDecoration.ts, apps/desktop/src/renderer/components/new-chat/ModelCompatibilityNotice.tsx, apps/desktop/src/renderer/components/new-chat/ModelConfigFlyout.tsx, apps/desktop/src/renderer/components/new-chat/ModelHarnessPicker.tsx, apps/desktop/src/renderer/components/new-chat/ModelRouteSelector.tsx, apps/desktop/src/renderer/components/new-chat/ModelSelector.tsx, apps/desktop/src/renderer/components/new-chat/ModelSourceDetails.tsx, apps/desktop/src/renderer/components/new-chat/NewGoalDialog.tsx, apps/desktop/src/renderer/components/new-chat/PastedTextChipNode.tsx, apps/desktop/src/renderer/components/new-chat/PendingQueuePanel.tsx, apps/desktop/src/renderer/components/new-chat/PermissionPrompt.tsx, apps/desktop/src/renderer/components/new-chat/PermissionSelector.tsx, apps/desktop/src/renderer/components/new-chat/PinnedPlanPanel.tsx, apps/desktop/src/renderer/components/new-chat/PlanActionCard.tsx, apps/desktop/src/renderer/components/new-chat/PlanModeIndicator.tsx, apps/desktop/src/renderer/components/new-chat/PlanViewerCard.tsx, apps/desktop/src/renderer/components/new-chat/PluginConnectionForm.tsx, apps/desktop/src/renderer/components/new-chat/PluginModelsSection.tsx, apps/desktop/src/renderer/components/new-chat/PluginOauthDeviceCode.tsx, apps/desktop/src/renderer/components/new-chat/PluginSetupPrompt.tsx, apps/desktop/src/renderer/components/new-chat/PluginSetupRemoteDesktopButton.tsx, apps/desktop/src/renderer/components/new-chat/PluginUiEnablePrompt.tsx, apps/desktop/src/renderer/components/new-chat/PluginWriteAccessRecovery.tsx, apps/desktop/src/renderer/components/new-chat/QuickStartPillMark.ts, apps/desktop/src/renderer/components/new-chat/RemoteOauthSetupCard.tsx, apps/desktop/src/renderer/components/new-chat/RemotePluginUiEnablePrompt.tsx, apps/desktop/src/renderer/components/new-chat/SendButton.tsx, apps/desktop/src/renderer/components/new-chat/SlashCommandDecoration.ts, apps/desktop/src/renderer/components/new-chat/SlashCommandPalette.tsx, apps/desktop/src/renderer/components/new-chat/SpeedIcon.tsx, apps/desktop/src/renderer/components/new-chat/TakeoverMask.tsx, apps/desktop/src/renderer/components/new-chat/ThinkingToggle.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedFlyoutHost.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedModelPanel.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedModelRail.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedModelRow.tsx, apps/desktop/src/renderer/components/new-chat/UsageDailyBars.tsx, apps/desktop/src/renderer/components/new-chat/UsageHeatmap.tsx, apps/desktop/src/renderer/components/new-chat/VendorSegmentedSwitcher.tsx, apps/desktop/src/renderer/components/new-chat/VoiceInputDraftDecoration.ts, apps/desktop/src/renderer/components/new-chat/WindowsSelectionReplacement.ts, apps/desktop/src/renderer/components/new-chat/WorktreeChipsRow.tsx, apps/desktop/src/renderer/components/new-chat/WorktreeCreatingOverlay.tsx, apps/desktop/src/renderer/components/new-chat/agentOptions.ts, apps/desktop/src/renderer/components/new-chat/agentSwitchConfirmation.ts, apps/desktop/src/renderer/components/new-chat/askUserDictation.ts, apps/desktop/src/renderer/components/new-chat/askUserVoiceShortcutOwner.ts, apps/desktop/src/renderer/components/new-chat/branchPick.ts, apps/desktop/src/renderer/components/new-chat/composerBlankPointerFocus.ts, apps/desktop/src/renderer/components/new-chat/composerContentSerialization.ts, apps/desktop/src/renderer/components/new-chat/composerDocState.ts, apps/desktop/src/renderer/components/new-chat/composerFrameScheduler.ts, apps/desktop/src/renderer/components/new-chat/composerHistoryProjection.ts, apps/desktop/src/renderer/components/new-chat/composerModelSelection.ts, apps/desktop/src/renderer/components/new-chat/composerPromptRecommendation.ts, apps/desktop/src/renderer/components/new-chat/composerRenderGate.ts, apps/desktop/src/renderer/components/new-chat/composerSendOwnership.ts, apps/desktop/src/renderer/components/new-chat/composerSendSnapshot.ts, apps/desktop/src/renderer/components/new-chat/effortChangeQueue.ts, apps/desktop/src/renderer/components/new-chat/extraDirsActions.ts, apps/desktop/src/renderer/components/new-chat/favoritesSyncFailure.ts, apps/desktop/src/renderer/components/new-chat/ghostComposerPlacement.ts, apps/desktop/src/renderer/components/new-chat/localAttachmentPicker.ts, apps/desktop/src/renderer/components/new-chat/mentionChipInsertion.ts, apps/desktop/src/renderer/components/new-chat/modelPickerPreferences.ts, apps/desktop/src/renderer/components/new-chat/modelWindowErrorToast.ts, apps/desktop/src/renderer/components/new-chat/pastePipeline.ts, apps/desktop/src/renderer/components/new-chat/pathPaste.ts, apps/desktop/src/renderer/components/new-chat/pendingQueueLocks.ts, apps/desktop/src/renderer/components/new-chat/pendingQueueRowActivity.ts, apps/desktop/src/renderer/components/new-chat/pendingQueueRowPresentation.ts, apps/desktop/src/renderer/components/new-chat/planModeComposerCommand.ts, apps/desktop/src/renderer/components/new-chat/priceTierMarks.tsx, apps/desktop/src/renderer/components/new-chat/remoteBrowseAdapters.ts, apps/desktop/src/renderer/components/new-chat/remoteExistingProjects.ts, apps/desktop/src/renderer/components/new-chat/restoreComposerDocument.ts, apps/desktop/src/renderer/components/new-chat/sessionLinkPaste.ts, apps/desktop/src/renderer/components/new-chat/sourceSwitch.ts, apps/desktop/src/renderer/components/new-chat/unifiedModelSelection.ts, apps/desktop/src/renderer/components/new-chat/usagePalette.ts, apps/desktop/src/renderer/components/new-chat/useAskUserDictation.ts, apps/desktop/src/renderer/components/new-chat/useComposerSendFocusRestore.ts, apps/desktop/src/renderer/components/new-chat/useModelDiscoveryPending.ts, apps/desktop/src/renderer/components/new-chat/useProviderWeeklyQuota.ts, apps/desktop/src/renderer/components/new-chat/useUnifiedRowActions.ts, apps/desktop/src/renderer/components/new-chat/voiceQuestionSelection.ts, apps/desktop/src/renderer/features/cc-agent/CCAgentFeatureLayout.tsx, apps/desktop/src/renderer/features/cc-agent/CCAgentSessionView.tsx | 268 | 38 | 365 | +| `desktop.chat.session` | desktop | 会话工作台 | hash `/cc-agent/:sessionId`;副窗经 `/cc-agent/boot` 解析后落到同一会话视图 | AssistantMessage, CCAgentFeatureLayout, CCAgentSessionView, ChatInput, PermissionPrompt | apps/desktop/src/renderer/components/chat/AgentActionRow.tsx, apps/desktop/src/renderer/components/chat/AgentActionsBlock.tsx, apps/desktop/src/renderer/components/chat/AgentTaskCard.tsx, apps/desktop/src/renderer/components/chat/AgentTaskLiveDetails.tsx, apps/desktop/src/renderer/components/chat/AnnotationStrokesSvg.tsx, apps/desktop/src/renderer/components/chat/AskUserQuestionBubble.tsx, apps/desktop/src/renderer/components/chat/AssistantMessage.tsx, apps/desktop/src/renderer/components/chat/AttachmentCollection.tsx, apps/desktop/src/renderer/components/chat/AttachmentCollectionContext.ts, apps/desktop/src/renderer/components/chat/AutomationOriginBadge.tsx, apps/desktop/src/renderer/components/chat/ChatAudioCard.tsx, apps/desktop/src/renderer/components/chat/ChatBubble.tsx, apps/desktop/src/renderer/components/chat/ChatDisplaySnapshotContext.tsx, apps/desktop/src/renderer/components/chat/ChatImageView.tsx, apps/desktop/src/renderer/components/chat/ChatLoadingImage.tsx, apps/desktop/src/renderer/components/chat/ChatReplyIcon.tsx, apps/desktop/src/renderer/components/chat/ChatSessionFileContext.tsx, apps/desktop/src/renderer/components/chat/ChatSoundEffectCard.tsx, apps/desktop/src/renderer/components/chat/ChatVideoView.tsx, apps/desktop/src/renderer/components/chat/CindyMakeDoctorCard.tsx, apps/desktop/src/renderer/components/chat/CopyAsImageBlock.tsx, apps/desktop/src/renderer/components/chat/CredentialSwitchWaitBanner.tsx, apps/desktop/src/renderer/components/chat/DiffView.tsx, apps/desktop/src/renderer/components/chat/ErrorBanner.tsx, apps/desktop/src/renderer/components/chat/ErrorMessageCard.tsx, apps/desktop/src/renderer/components/chat/FileLightbox.tsx, apps/desktop/src/renderer/components/chat/GeneratedFilesCard.tsx, apps/desktop/src/renderer/components/chat/GhostCardForm.tsx, apps/desktop/src/renderer/components/chat/GhostCardHostPrompts.tsx, apps/desktop/src/renderer/components/chat/GhostCardRecipients.tsx, apps/desktop/src/renderer/components/chat/GhostMailHistory.tsx, apps/desktop/src/renderer/components/chat/GhostSummonCard.tsx, apps/desktop/src/renderer/components/chat/GhostToolCard.tsx, apps/desktop/src/renderer/components/chat/HookTaskCard.tsx, apps/desktop/src/renderer/components/chat/ImageGalleryContext.tsx, apps/desktop/src/renderer/components/chat/ImageHoverPreview.tsx, apps/desktop/src/renderer/components/chat/ImageLightbox.tsx, apps/desktop/src/renderer/components/chat/ImageMissingPlaceholder.tsx, apps/desktop/src/renderer/components/chat/InlineReferenceChip.tsx, apps/desktop/src/renderer/components/chat/InterruptedTurnBanner.tsx, apps/desktop/src/renderer/components/chat/JumpToBottomChip.tsx, apps/desktop/src/renderer/components/chat/MailHtmlPreview.tsx, apps/desktop/src/renderer/components/chat/MarkdownDiffBlock.tsx, apps/desktop/src/renderer/components/chat/MarkdownMermaidBlock.tsx, apps/desktop/src/renderer/components/chat/MarkdownRenderer.tsx, apps/desktop/src/renderer/components/chat/McpAppCard.tsx, apps/desktop/src/renderer/components/chat/McpAppPermissionPrompt.tsx, apps/desktop/src/renderer/components/chat/MermaidLightbox.tsx, apps/desktop/src/renderer/components/chat/MessageActionBar.tsx, apps/desktop/src/renderer/components/chat/MessageNavRail.tsx, apps/desktop/src/renderer/components/chat/MessageReadAloudContext.ts, apps/desktop/src/renderer/components/chat/MessageSourceLabels.tsx, apps/desktop/src/renderer/components/chat/MessageStream.tsx, apps/desktop/src/renderer/components/chat/MessageViewport.tsx, apps/desktop/src/renderer/components/chat/ModelLightbox.tsx, apps/desktop/src/renderer/components/chat/NewMessageIndicator.tsx, apps/desktop/src/renderer/components/chat/PlanReviewBubble.tsx, apps/desktop/src/renderer/components/chat/PrevMessageJumpChip.tsx, apps/desktop/src/renderer/components/chat/PreviewChrome.tsx, apps/desktop/src/renderer/components/chat/ProjectLinkChip.tsx, apps/desktop/src/renderer/components/chat/QuoteChip.tsx, apps/desktop/src/renderer/components/chat/RewindPreviewDialog.tsx, apps/desktop/src/renderer/components/chat/RichAnswer.tsx, apps/desktop/src/renderer/components/chat/SelectionQuoteButton.tsx, apps/desktop/src/renderer/components/chat/SentAgentReferenceChip.tsx, apps/desktop/src/renderer/components/chat/SentInlineAtomBody.tsx, apps/desktop/src/renderer/components/chat/SessionHandoffCard.tsx, apps/desktop/src/renderer/components/chat/SessionLinkChip.tsx, apps/desktop/src/renderer/components/chat/SessionQuickSendContext.tsx, apps/desktop/src/renderer/components/chat/ShareMessageCheckbox.tsx, apps/desktop/src/renderer/components/chat/ShareSelectionBar.tsx, apps/desktop/src/renderer/components/chat/SlackIcon.tsx, apps/desktop/src/renderer/components/chat/StreamFadeSpan.tsx, apps/desktop/src/renderer/components/chat/StreamingMarkdownChunk.tsx, apps/desktop/src/renderer/components/chat/SystemCard.tsx, apps/desktop/src/renderer/components/chat/TextLightbox.tsx, apps/desktop/src/renderer/components/chat/ThinkingCard.tsx, apps/desktop/src/renderer/components/chat/ThinkingText.tsx, apps/desktop/src/renderer/components/chat/TodoListCard.tsx, apps/desktop/src/renderer/components/chat/ToolCallCard.tsx, apps/desktop/src/renderer/components/chat/ToolPayloadLightbox.tsx, apps/desktop/src/renderer/components/chat/TopRightChipStack.tsx, apps/desktop/src/renderer/components/chat/TurnChangesCard.tsx, apps/desktop/src/renderer/components/chat/UnreadFailedScheduleBanner.tsx, apps/desktop/src/renderer/components/chat/UpgradeBanner.tsx, apps/desktop/src/renderer/components/chat/UserAttachmentChip.tsx, apps/desktop/src/renderer/components/chat/UserMessage.tsx, apps/desktop/src/renderer/components/chat/UserMessageEditBox.tsx, apps/desktop/src/renderer/components/chat/UserMessageImageLink.tsx, apps/desktop/src/renderer/components/chat/UserMessageUrlLink.tsx, apps/desktop/src/renderer/components/chat/VideoLightbox.tsx, apps/desktop/src/renderer/components/chat/VideoSitePreview.tsx, apps/desktop/src/renderer/components/chat/WorkGroupBlock.tsx, apps/desktop/src/renderer/components/chat/WorktreeRestoreBanner.tsx, apps/desktop/src/renderer/components/chat/XIcon.tsx, apps/desktop/src/renderer/components/chat/activityRowChrome.ts, apps/desktop/src/renderer/components/chat/animateFocusScroll.ts, apps/desktop/src/renderer/components/chat/autoFollowIntent.ts, apps/desktop/src/renderer/components/chat/chatChrome.ts, apps/desktop/src/renderer/components/chat/errorReasonI18n.ts, apps/desktop/src/renderer/components/chat/generatedFilesProjection.ts, apps/desktop/src/renderer/components/chat/historyAfter.ts, apps/desktop/src/renderer/components/chat/imageDisplaySize.ts, apps/desktop/src/renderer/components/chat/lightboxAnnotations.ts, apps/desktop/src/renderer/components/chat/lightboxGestures.ts, apps/desktop/src/renderer/components/chat/linkInteraction.ts, apps/desktop/src/renderer/components/chat/mailBodyEditing.ts, apps/desktop/src/renderer/components/chat/mailCardFooter.ts, apps/desktop/src/renderer/components/chat/mailPreviewAppearance.ts, apps/desktop/src/renderer/components/chat/markdownImageTargets.ts, apps/desktop/src/renderer/components/chat/markdownMedia.ts, apps/desktop/src/renderer/components/chat/messageNavRailModel.ts, apps/desktop/src/renderer/components/chat/messageNavRailWheel.ts, apps/desktop/src/renderer/components/chat/messageNavigationProjection.ts, apps/desktop/src/renderer/components/chat/messageProjectionCache.ts, apps/desktop/src/renderer/components/chat/messageStreamIndicatorPosition.ts, apps/desktop/src/renderer/components/chat/messageViewportCompensation.ts, apps/desktop/src/renderer/components/chat/messageViewportGeometry.ts, apps/desktop/src/renderer/components/chat/messageWorkGroups.ts, apps/desktop/src/renderer/components/chat/observeProgrammaticScroll.ts, apps/desktop/src/renderer/components/chat/rehypeFencedCodeMarker.ts, apps/desktop/src/renderer/components/chat/rehypeHighlightShared.ts, apps/desktop/src/renderer/components/chat/rehypeMathBlockMarker.ts, apps/desktop/src/renderer/components/chat/rehypeStreamWordFade.ts, apps/desktop/src/renderer/components/chat/remarkHtmlImages.ts, apps/desktop/src/renderer/components/chat/remarkLocalPathLinks.ts, apps/desktop/src/renderer/components/chat/remarkPreserveRawLocalDestinations.ts, apps/desktop/src/renderer/components/chat/remarkSessionLinks.ts, apps/desktop/src/renderer/components/chat/remarkStrictInlineMath.ts, apps/desktop/src/renderer/components/chat/remarkTruncateCjkUrls.ts, apps/desktop/src/renderer/components/chat/repairStreamingMarkdown.ts, apps/desktop/src/renderer/components/chat/rich-answer.css, apps/desktop/src/renderer/components/chat/scrollAnchoringDetect.ts, apps/desktop/src/renderer/components/chat/shareSelectionStore.ts, apps/desktop/src/renderer/components/chat/streamingMarkdownBlocks.tsx, apps/desktop/src/renderer/components/chat/streamingMarkdownChunks.ts, apps/desktop/src/renderer/components/chat/streamingMessageProjection.ts, apps/desktop/src/renderer/components/chat/toolLoopI18n.ts, apps/desktop/src/renderer/components/chat/unreadCount.ts, apps/desktop/src/renderer/components/chat/useAutomaticHistoryLoadBudget.ts, apps/desktop/src/renderer/components/chat/useCopyAsImage.tsx, apps/desktop/src/renderer/components/chat/useDeleteMessage.ts, apps/desktop/src/renderer/components/chat/useFileChipContextMenu.tsx, apps/desktop/src/renderer/components/chat/useForkAtMessage.ts, apps/desktop/src/renderer/components/chat/useImageClipboard.ts, apps/desktop/src/renderer/components/chat/useImageLoadFailure.ts, apps/desktop/src/renderer/components/chat/useLinkContextMenu.tsx, apps/desktop/src/renderer/components/chat/useMessageViewport.ts, apps/desktop/src/renderer/components/chat/useNavigationKeyListener.ts, apps/desktop/src/renderer/components/chat/useOpenWithMenu.tsx, apps/desktop/src/renderer/components/chat/usePrevUserMessageInView.ts, apps/desktop/src/renderer/components/chat/useTurnChangeSets.ts, apps/desktop/src/renderer/components/chat/useVideoContextMenu.ts, apps/desktop/src/renderer/components/chat/useWorkspaceMediaPath.tsx, apps/desktop/src/renderer/components/chat/userMessageCollapse.ts, apps/desktop/src/renderer/components/chat/userMessageDisplayText.ts, apps/desktop/src/renderer/components/chat/userMessageLinkify.ts, apps/desktop/src/renderer/components/chat/videoContextMenu.css, apps/desktop/src/renderer/components/chat/viewportFillDetect.ts, apps/desktop/src/renderer/components/new-chat/AddRemoteProjectDialog.tsx, apps/desktop/src/renderer/components/new-chat/AgentSelect.tsx, apps/desktop/src/renderer/components/new-chat/AskUserQuestionPrompt.tsx, apps/desktop/src/renderer/components/new-chat/AtMentionPanel.tsx, apps/desktop/src/renderer/components/new-chat/AttachmentTypeThumb.tsx, apps/desktop/src/renderer/components/new-chat/ChatInput.tsx, apps/desktop/src/renderer/components/new-chat/CjkPunctDecoration.ts, apps/desktop/src/renderer/components/new-chat/CjkPunctuationUtils.ts, apps/desktop/src/renderer/components/new-chat/ComposerAttachments.tsx, apps/desktop/src/renderer/components/new-chat/ComposerCard.tsx, apps/desktop/src/renderer/components/new-chat/ComposerFolderTransfers.tsx, apps/desktop/src/renderer/components/new-chat/ComposerListIndentDecoration.ts, apps/desktop/src/renderer/components/new-chat/ComposerListNodes.ts, apps/desktop/src/renderer/components/new-chat/ComposerQuoteNode.tsx, apps/desktop/src/renderer/components/new-chat/DeviceSwitcherPill.tsx, apps/desktop/src/renderer/components/new-chat/EffortSlider.tsx, apps/desktop/src/renderer/components/new-chat/EmptyDocSelectionGuard.ts, apps/desktop/src/renderer/components/new-chat/ExtraDirsButton.tsx, apps/desktop/src/renderer/components/new-chat/FastModeToggle.tsx, apps/desktop/src/renderer/components/new-chat/FolderPickerPopover.tsx, apps/desktop/src/renderer/components/new-chat/FullAccessConfirmContent.tsx, apps/desktop/src/renderer/components/new-chat/GhostCommandDecoration.ts, apps/desktop/src/renderer/components/new-chat/GoalAdvancedLimits.tsx, apps/desktop/src/renderer/components/new-chat/GoalIndicator.tsx, apps/desktop/src/renderer/components/new-chat/HomeUsageDashboard.tsx, apps/desktop/src/renderer/components/new-chat/ListComposerTextarea.tsx, apps/desktop/src/renderer/components/new-chat/LocalModelCatalogNotice.tsx, apps/desktop/src/renderer/components/new-chat/McpSetupPrompt.tsx, apps/desktop/src/renderer/components/new-chat/MentionChipNode.tsx, apps/desktop/src/renderer/components/new-chat/MentionDragCaretDecoration.ts, apps/desktop/src/renderer/components/new-chat/ModelCompatibilityNotice.tsx, apps/desktop/src/renderer/components/new-chat/ModelConfigFlyout.tsx, apps/desktop/src/renderer/components/new-chat/ModelHarnessPicker.tsx, apps/desktop/src/renderer/components/new-chat/ModelRouteSelector.tsx, apps/desktop/src/renderer/components/new-chat/ModelSelector.tsx, apps/desktop/src/renderer/components/new-chat/ModelSourceDetails.tsx, apps/desktop/src/renderer/components/new-chat/NewGoalDialog.tsx, apps/desktop/src/renderer/components/new-chat/PastedTextChipNode.tsx, apps/desktop/src/renderer/components/new-chat/PendingQueuePanel.tsx, apps/desktop/src/renderer/components/new-chat/PermissionPrompt.tsx, apps/desktop/src/renderer/components/new-chat/PermissionSelector.tsx, apps/desktop/src/renderer/components/new-chat/PinnedPlanPanel.tsx, apps/desktop/src/renderer/components/new-chat/PlanActionCard.tsx, apps/desktop/src/renderer/components/new-chat/PlanModeIndicator.tsx, apps/desktop/src/renderer/components/new-chat/PlanViewerCard.tsx, apps/desktop/src/renderer/components/new-chat/PluginConnectionForm.tsx, apps/desktop/src/renderer/components/new-chat/PluginModelsSection.tsx, apps/desktop/src/renderer/components/new-chat/PluginOauthDeviceCode.tsx, apps/desktop/src/renderer/components/new-chat/PluginSetupPrompt.tsx, apps/desktop/src/renderer/components/new-chat/PluginSetupRemoteDesktopButton.tsx, apps/desktop/src/renderer/components/new-chat/PluginUiEnablePrompt.tsx, apps/desktop/src/renderer/components/new-chat/PluginWriteAccessRecovery.tsx, apps/desktop/src/renderer/components/new-chat/QuickStartPillMark.ts, apps/desktop/src/renderer/components/new-chat/RemoteOauthSetupCard.tsx, apps/desktop/src/renderer/components/new-chat/RemotePluginUiEnablePrompt.tsx, apps/desktop/src/renderer/components/new-chat/SendButton.tsx, apps/desktop/src/renderer/components/new-chat/SlashCommandDecoration.ts, apps/desktop/src/renderer/components/new-chat/SlashCommandPalette.tsx, apps/desktop/src/renderer/components/new-chat/SpeedIcon.tsx, apps/desktop/src/renderer/components/new-chat/TakeoverMask.tsx, apps/desktop/src/renderer/components/new-chat/ThinkingToggle.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedFlyoutHost.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedModelPanel.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedModelRail.tsx, apps/desktop/src/renderer/components/new-chat/UnifiedModelRow.tsx, apps/desktop/src/renderer/components/new-chat/UsageDailyBars.tsx, apps/desktop/src/renderer/components/new-chat/UsageHeatmap.tsx, apps/desktop/src/renderer/components/new-chat/VendorSegmentedSwitcher.tsx, apps/desktop/src/renderer/components/new-chat/VoiceInputDraftDecoration.ts, apps/desktop/src/renderer/components/new-chat/WindowsSelectionReplacement.ts, apps/desktop/src/renderer/components/new-chat/WorktreeChipsRow.tsx, apps/desktop/src/renderer/components/new-chat/WorktreeCreatingOverlay.tsx, apps/desktop/src/renderer/components/new-chat/agentOptions.ts, apps/desktop/src/renderer/components/new-chat/agentSwitchConfirmation.ts, apps/desktop/src/renderer/components/new-chat/askUserDictation.ts, apps/desktop/src/renderer/components/new-chat/askUserVoiceShortcutOwner.ts, apps/desktop/src/renderer/components/new-chat/branchPick.ts, apps/desktop/src/renderer/components/new-chat/composerBlankPointerFocus.ts, apps/desktop/src/renderer/components/new-chat/composerContentSerialization.ts, apps/desktop/src/renderer/components/new-chat/composerDocState.ts, apps/desktop/src/renderer/components/new-chat/composerFrameScheduler.ts, apps/desktop/src/renderer/components/new-chat/composerHistoryProjection.ts, apps/desktop/src/renderer/components/new-chat/composerModelSelection.ts, apps/desktop/src/renderer/components/new-chat/composerPromptRecommendation.ts, apps/desktop/src/renderer/components/new-chat/composerRenderGate.ts, apps/desktop/src/renderer/components/new-chat/composerSendOwnership.ts, apps/desktop/src/renderer/components/new-chat/composerSendSnapshot.ts, apps/desktop/src/renderer/components/new-chat/effortChangeQueue.ts, apps/desktop/src/renderer/components/new-chat/extraDirsActions.ts, apps/desktop/src/renderer/components/new-chat/favoritesSyncFailure.ts, apps/desktop/src/renderer/components/new-chat/ghostComposerPlacement.ts, apps/desktop/src/renderer/components/new-chat/localAttachmentPicker.ts, apps/desktop/src/renderer/components/new-chat/mentionChipInsertion.ts, apps/desktop/src/renderer/components/new-chat/modelPickerPreferences.ts, apps/desktop/src/renderer/components/new-chat/modelWindowErrorToast.ts, apps/desktop/src/renderer/components/new-chat/pastePipeline.ts, apps/desktop/src/renderer/components/new-chat/pathPaste.ts, apps/desktop/src/renderer/components/new-chat/pendingQueueLocks.ts, apps/desktop/src/renderer/components/new-chat/pendingQueueRowActivity.ts, apps/desktop/src/renderer/components/new-chat/pendingQueueRowPresentation.ts, apps/desktop/src/renderer/components/new-chat/planModeComposerCommand.ts, apps/desktop/src/renderer/components/new-chat/priceTierMarks.tsx, apps/desktop/src/renderer/components/new-chat/remoteBrowseAdapters.ts, apps/desktop/src/renderer/components/new-chat/remoteExistingProjects.ts, apps/desktop/src/renderer/components/new-chat/restoreComposerDocument.ts, apps/desktop/src/renderer/components/new-chat/sessionLinkPaste.ts, apps/desktop/src/renderer/components/new-chat/sourceSwitch.ts, apps/desktop/src/renderer/components/new-chat/unifiedModelSelection.ts, apps/desktop/src/renderer/components/new-chat/usagePalette.ts, apps/desktop/src/renderer/components/new-chat/useAskUserDictation.ts, apps/desktop/src/renderer/components/new-chat/useComposerSendFocusRestore.ts, apps/desktop/src/renderer/components/new-chat/useModelDiscoveryPending.ts, apps/desktop/src/renderer/components/new-chat/useProviderWeeklyQuota.ts, apps/desktop/src/renderer/components/new-chat/useUnifiedRowActions.ts, apps/desktop/src/renderer/components/new-chat/voiceQuestionSelection.ts, apps/desktop/src/renderer/features/cc-agent/CCAgentFeatureLayout.tsx, apps/desktop/src/renderer/features/cc-agent/CCAgentSessionView.tsx | 265 | 34 | 360 | | `desktop.dev.maker-experimental` | desktop | Maker 实验诊断页 | hash `/maker-experimental`(MakerExperimentalView;EXPERIMENTAL_FEATURES 现为空,仍是生产路由) | MakerExperimentalView | apps/desktop/src/renderer/features/maker-experimental/MakerExperimentalView.tsx | 9 | 0 | 7 | | `desktop.issues.guide` | desktop | Issue 引导页 | hash `/issues`(IssueTrackerFeatureLayout,已迁 GitHub 后的引导页) | IssueTrackerFeatureLayout | apps/desktop/src/renderer/features/issue-tracker/IssueTrackerFeatureLayout.tsx, apps/desktop/src/renderer/features/issue-tracker/MyIssueList.tsx, apps/desktop/src/renderer/features/issue-tracker/hooks/useMyIssues.ts, apps/desktop/src/renderer/features/issue-tracker/lib/myIssuesNotices.ts, apps/desktop/src/renderer/features/issue-tracker/lib/startIssueChat.ts | 0 | 0 | 7 | | `desktop.native.app-menu` | desktop | 应用菜单 | Menu.setApplicationMenu(用户可见出口,不展开业务逻辑) | applicationMenuLabels | apps/desktop/src/main/applicationMenuLabels.ts | 0 | 0 | 0 | @@ -57,19 +57,19 @@ | `mobile.account-deletion` | mobile | 账号注销 | app/account-deletion.tsx | AdaptiveWindow, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, BlurBackdrop, CenteredScreen, ChatBackgroundBridge, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, CompanionNativeContent.ios, CompanionSheet, CompanionSheet.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, DeviceLinkContext, DirectLinkTransport, ErrorNotice, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, HomeHeaderGlassButton, HomeHeaderGlassButton.ios, HomeNativeStackHeader, HomeStackHeader, IncomingShareBridge, McpAppViews, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileOutboxBridge, MobilePrimitives, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, OutsideTap, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PushNotificationsBridge, QuietSyncIndicator, RecentMessageHistories, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, ResidentHomeList, SessionPaneDivider, SheetModal, SheetSurface, SimpleStackHeader, StartupSplashOverlay, ThemeProvider, UsageWidgetBridge, _layout, account-deletion, gestureHandler, nativeToolbarMenuItems, peerFileTransport, useLocale | apps/mobile/app/_layout.tsx, apps/mobile/app/account-deletion.tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/HomeNativeStackHeader.tsx, apps/mobile/src/platform/chrome/HomeStackHeader.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/chrome/nativeToolbarMenuItems.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/HomeHeaderGlassButton.ios.tsx, apps/mobile/src/session/HomeHeaderGlassButton.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 226 | 80 | | `mobile.auth` | mobile | 登录与添加账号 | app/(auth)/login.tsx / app/add-account.tsx | AccountSwitcherSheet, AdaptiveWindow, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, BlurBackdrop, CenteredScreen, ChatBackgroundBridge, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, CompanionNativeContent.ios, CompanionSheet, CompanionSheet.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, DeviceLinkContext, DirectLinkTransport, ErrorNotice, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, HomeHeaderGlassButton, HomeHeaderGlassButton.ios, IncomingShareBridge, LoginCaptchaWebView, LoginNativeButton, LoginNativeButton.ios, LoginSkinControls, McpAppViews, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileOutboxBridge, MobilePrimitives, MobileUserAvatar, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, OutsideTap, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PushNotificationsBridge, QuietSyncIndicator, RecentMessageHistories, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, ResidentHomeList, SessionPaneDivider, SheetModal, SheetSurface, StartupSplashOverlay, SystemNavigationBack, ThemeProvider, UsageWidgetBridge, _layout, add-account, gestureHandler, login, peerFileTransport, useLocale | apps/mobile/app/(auth)/login.tsx, apps/mobile/app/_layout.tsx, apps/mobile/app/add-account.tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/LoginCaptchaWebView.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/LoginNativeButton.ios.tsx, apps/mobile/src/components/LoginNativeButton.tsx, apps/mobile/src/components/LoginSkinControls.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/MobileUserAvatar.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SystemNavigationBack.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/AccountSwitcherSheet.tsx, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/HomeHeaderGlassButton.ios.tsx, apps/mobile/src/session/HomeHeaderGlassButton.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 233 | 102 | | `mobile.chat.incoming-share` | mobile | 外部分享内容预览与任务、伙伴目标选择 | app/incoming-share.tsx | AdaptiveWindow, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, BlurBackdrop, CenteredScreen, ChatBackgroundBridge, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, CompanionNativeContent.ios, CompanionSheet, CompanionSheet.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, DeviceLinkContext, DirectLinkTransport, ErrorNotice, FileTypeIcon, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, IncomingShareBridge, McpAppViews, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileOutboxBridge, MobilePrimitives, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, OutsideTap, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PushNotificationsBridge, QuietSyncIndicator, RecentMessageHistories, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, ResidentHomeList, SessionPaneDivider, SheetModal, SheetSurface, SimpleStackHeader, StartupSplashOverlay, ThemeProvider, UsageWidgetBridge, _layout, gestureHandler, incoming-share, peerFileTransport, useLocale | apps/mobile/app/_layout.tsx, apps/mobile/app/incoming-share.tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FileTypeIcon.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 226 | 77 | -| `mobile.chat.new` | mobile | 新建任务 | app/sessions/new.tsx | AccountSwitcherSheet, AdaptiveWindow, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AnnotationBurnInWebView, AppText, AuthContext, AutomationTimerIcon, BlurBackdrop, BotGroupAvatars, BotGroupCreateSheet, BotGroupList, CenteredScreen, ChatBackgroundBridge, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, CompanionChannelTasks, CompanionChoice, CompanionCreateNativeView, CompanionCreateNativeView.ios, CompanionImportSheet, CompanionListRow, CompanionMemoryPage, CompanionMemoryPage.ios, CompanionModelChain, CompanionNativeContent.ios, CompanionPortraitPicker, CompanionPresenceRing, CompanionProfileArtifacts, CompanionProfileNativeView, CompanionProfileNativeView.ios, CompanionProfileSheet, CompanionSettingsRow, CompanionSettingsRow.ios, CompanionSheet, CompanionSheet.ios, ComposerActivityPill, ComposerAttachmentTray, ComposerDockKeyboardFollow, ComposerFrame, ComposerFrame.ios, ComposerKeyboardAvoidingView, ComposerMorphSource, ComposerMorphSource.ios, ComposerNativeRow, ComposerNativeRow.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, ContextSheetCollabView, ContextSheetGoalView, ContextSheetGoalView.ios, ContextSheetMediaViews, ContextSheetPlugins, ConversationSearchFilterSheet, DeviceLinkContext, DirectLinkTransport, ErrorNotice, FileTypeIcon, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, FormSheet, FormSheet.ios, HomeChromeDrawer, HomeChromeFrost, HomeDisplayMenu, HomeDisplayMenu.ios, HomeGlassMenuPanel, HomeHeaderGlassButton, HomeHeaderGlassButton.ios, HomeListVisuals, HomeNativeStackHeader, HomeNewTaskButton, HomeProjectMachineLabel, HomeRosterContext, HomeSearchBar, HomeSessionInfoMeta, HomeStackHeader, HomeSurface, HomeTeammatesSection, ImageLightbox, IncomingShareBridge, McpAppViews, MobileAgentMark, MobileAgentSwitcher, MobileChoicePickerList, MobileComposerInputRow, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileModelPickerList, MobileModelPickerList.ios, MobileOutboxBridge, MobilePermissionPickerList, MobilePermissionPickerList.ios, MobilePrimitives, MobileProviderMark, MobileUserAvatar, MobileVendorIcon, ModalContentArea, ModelOptionsSheetView, ModelOptionsSheetView.ios, ModelPickerNativeHeader, ModelPickerNativeHeader.ios, ModelPickerSheet, ModelQuotaBar, ModelSpeedIcon, MountOnFirstOpen, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePermissionSheet, NativePermissionSheet.ios, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, NativeVideoPlayer, NewSessionLayout, NewTaskSelectionRow, NewTaskSelectionSheet, NewTaskSelectionSheet.android, NewTaskSelectionSheet.ios, OrcaWorkerDirectoryPicker, OutsideTap, PaperPlaneIcon, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PlanModeChip, PluginManagementConfirmation, PluginManagementConfirmation.ios, PluginMenuUnreadDot, PluginNotice, PluginNotice.ios, PluginUiEnablePrompt, PushNotificationsBridge, QuietSyncIndicator, RecentMessageHistories, RemoteAccessGuide, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, RemoteSourceMark, RemoteTaskSuggestions, RenameSessionModal, ResidentHomeList, SessionActionSheet, SessionActionSheetFrame, SessionActionSheetFrame.android, SessionListDrawer, SessionOptionsExpoSheet, SessionPaneDivider, SessionRightSpinner, SheetModal, SheetSurface, SimpleStackHeader, SlowSendNotice, StartupSplashOverlay, SwipeableProjectHeader, SwipeableSessionRow, SwipeableSessionRow.ios, SystemNavigationBack, TaskFollowMark, TaskTags, TaskTagsNative, TaskTagsNative.ios, TeammateCreateController, TeammateGenerationLabel, TeammateList, TeammateStatusDot, ThemeProvider, UnifiedModelPickerSheet, UnifiedModelPickerView, UnifiedModelPickerView.ios, UsageWidgetBridge, VideoDownloadButton, VoiceRecordingPill, WorkingStatusText, _layout, composerPullThrough, gestureHandler, listDisclosureTransition, mediaPlayerWebView, nativeToolbarMenuItems, new, peerFileTransport, useComposerPendingImagePreview, useLocale, voicePillWidthMotion | apps/mobile/app/_layout.tsx, apps/mobile/app/sessions/new.tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FileTypeIcon.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileAgentMark.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/MobileUserAvatar.tsx, apps/mobile/src/components/MobileVendorIcon.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/PaperPlaneIcon.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteAccessGuide.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/ModalContentArea.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/HomeNativeStackHeader.tsx, apps/mobile/src/platform/chrome/HomeStackHeader.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/chrome/SystemNavigationBack.tsx, apps/mobile/src/platform/chrome/nativeToolbarMenuItems.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/plugins/PluginManagementConfirmation.ios.tsx, apps/mobile/src/plugins/PluginManagementConfirmation.tsx, apps/mobile/src/plugins/PluginMenuUnreadDot.tsx, apps/mobile/src/plugins/PluginNotice.ios.tsx, apps/mobile/src/plugins/PluginNotice.tsx, apps/mobile/src/plugins/PluginUiEnablePrompt.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/AccountSwitcherSheet.tsx, apps/mobile/src/session/AnnotationBurnInWebView.tsx, apps/mobile/src/session/AutomationTimerIcon.tsx, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/BotGroupAvatars.tsx, apps/mobile/src/session/BotGroupCreateSheet.tsx, apps/mobile/src/session/BotGroupList.tsx, apps/mobile/src/session/CompanionChannelTasks.tsx, apps/mobile/src/session/CompanionChoice.tsx, apps/mobile/src/session/CompanionCreateNativeView.ios.tsx, apps/mobile/src/session/CompanionCreateNativeView.tsx, apps/mobile/src/session/CompanionImportSheet.tsx, apps/mobile/src/session/CompanionListRow.tsx, apps/mobile/src/session/CompanionMemoryPage.ios.tsx, apps/mobile/src/session/CompanionMemoryPage.tsx, apps/mobile/src/session/CompanionModelChain.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionPortraitPicker.tsx, apps/mobile/src/session/CompanionPresenceRing.tsx, apps/mobile/src/session/CompanionProfileArtifacts.tsx, apps/mobile/src/session/CompanionProfileNativeView.ios.tsx, apps/mobile/src/session/CompanionProfileNativeView.tsx, apps/mobile/src/session/CompanionProfileSheet.tsx, apps/mobile/src/session/CompanionSettingsRow.ios.tsx, apps/mobile/src/session/CompanionSettingsRow.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/ComposerActivityPill.tsx, apps/mobile/src/session/ComposerAttachmentTray.tsx, apps/mobile/src/session/ComposerDockKeyboardFollow.tsx, apps/mobile/src/session/ComposerFrame.ios.tsx, apps/mobile/src/session/ComposerFrame.tsx, apps/mobile/src/session/ComposerKeyboardAvoidingView.tsx, apps/mobile/src/session/ComposerMorphSource.ios.tsx, apps/mobile/src/session/ComposerMorphSource.tsx, apps/mobile/src/session/ComposerNativeRow.ios.tsx, apps/mobile/src/session/ComposerNativeRow.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/ContextSheetCollabView.tsx, apps/mobile/src/session/ContextSheetGoalView.ios.tsx, apps/mobile/src/session/ContextSheetGoalView.tsx, apps/mobile/src/session/ContextSheetMediaViews.tsx, apps/mobile/src/session/ContextSheetPlugins.tsx, apps/mobile/src/session/ConversationSearchFilterSheet.tsx, apps/mobile/src/session/FormSheet.ios.tsx, apps/mobile/src/session/FormSheet.tsx, apps/mobile/src/session/HomeChromeDrawer.tsx, apps/mobile/src/session/HomeChromeFrost.tsx, apps/mobile/src/session/HomeDisplayMenu.ios.tsx, apps/mobile/src/session/HomeDisplayMenu.tsx, apps/mobile/src/session/HomeGlassMenuPanel.tsx, apps/mobile/src/session/HomeHeaderGlassButton.ios.tsx, apps/mobile/src/session/HomeHeaderGlassButton.tsx, apps/mobile/src/session/HomeListVisuals.tsx, apps/mobile/src/session/HomeNewTaskButton.tsx, apps/mobile/src/session/HomeProjectMachineLabel.tsx, apps/mobile/src/session/HomeRosterContext.tsx, apps/mobile/src/session/HomeSearchBar.tsx, apps/mobile/src/session/HomeSessionInfoMeta.tsx, apps/mobile/src/session/HomeSurface.tsx, apps/mobile/src/session/HomeTeammatesSection.tsx, apps/mobile/src/session/ImageLightbox.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MobileAgentSwitcher.tsx, apps/mobile/src/session/MobileChoicePickerList.tsx, apps/mobile/src/session/MobileComposerInputRow.tsx, apps/mobile/src/session/MobileModelPickerList.ios.tsx, apps/mobile/src/session/MobileModelPickerList.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/MobilePermissionPickerList.ios.tsx, apps/mobile/src/session/MobilePermissionPickerList.tsx, apps/mobile/src/session/MobileProviderMark.tsx, apps/mobile/src/session/ModelOptionsSheetView.ios.tsx, apps/mobile/src/session/ModelOptionsSheetView.tsx, apps/mobile/src/session/ModelPickerNativeHeader.ios.tsx, apps/mobile/src/session/ModelPickerNativeHeader.tsx, apps/mobile/src/session/ModelPickerSheet.tsx, apps/mobile/src/session/ModelQuotaBar.tsx, apps/mobile/src/session/ModelSpeedIcon.tsx, apps/mobile/src/session/MountOnFirstOpen.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativePermissionSheet.ios.tsx, apps/mobile/src/session/NativePermissionSheet.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/NativeVideoPlayer.tsx, apps/mobile/src/session/NewSessionLayout.tsx, apps/mobile/src/session/NewTaskSelectionRow.tsx, apps/mobile/src/session/NewTaskSelectionSheet.android.tsx, apps/mobile/src/session/NewTaskSelectionSheet.ios.tsx, apps/mobile/src/session/NewTaskSelectionSheet.tsx, apps/mobile/src/session/OrcaWorkerDirectoryPicker.tsx, apps/mobile/src/session/PlanModeChip.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/RemoteSourceMark.tsx, apps/mobile/src/session/RemoteTaskSuggestions.tsx, apps/mobile/src/session/RenameSessionModal.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/SessionActionSheet.tsx, apps/mobile/src/session/SessionActionSheetFrame.android.tsx, apps/mobile/src/session/SessionActionSheetFrame.tsx, apps/mobile/src/session/SessionListDrawer.tsx, apps/mobile/src/session/SessionOptionsExpoSheet.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SessionRightSpinner.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/session/SlowSendNotice.tsx, apps/mobile/src/session/SwipeableProjectHeader.tsx, apps/mobile/src/session/SwipeableSessionRow.ios.tsx, apps/mobile/src/session/SwipeableSessionRow.tsx, apps/mobile/src/session/TaskFollowMark.tsx, apps/mobile/src/session/TaskTags.tsx, apps/mobile/src/session/TaskTagsNative.ios.tsx, apps/mobile/src/session/TaskTagsNative.tsx, apps/mobile/src/session/TeammateCreateController.tsx, apps/mobile/src/session/TeammateGenerationLabel.tsx, apps/mobile/src/session/TeammateList.tsx, apps/mobile/src/session/TeammateStatusDot.tsx, apps/mobile/src/session/UnifiedModelPickerSheet.tsx, apps/mobile/src/session/UnifiedModelPickerView.ios.tsx, apps/mobile/src/session/UnifiedModelPickerView.tsx, apps/mobile/src/session/VideoDownloadButton.tsx, apps/mobile/src/session/VoiceRecordingPill.tsx, apps/mobile/src/session/WorkingStatusText.tsx, apps/mobile/src/session/composerPullThrough.tsx, apps/mobile/src/session/listDisclosureTransition.tsx, apps/mobile/src/session/mediaPlayerWebView.tsx, apps/mobile/src/session/mediaPlayerWebViewHtml.ts, apps/mobile/src/session/useComposerPendingImagePreview.tsx, apps/mobile/src/session/voicePillWidthMotion.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 265 | 218 | -| `mobile.chat.session` | mobile | 任务内容与输入 | app/sessions/[sessionId].tsx | AccountSwitcherSheet, AdaptiveWindow, AgentErrorDetails, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AnnotationBurnInWebView, AppText, AuthContext, AuthorizationMessageCard, AutomationTimerIcon, BlurBackdrop, BotGroupAvatars, BotGroupCreateSheet, BotGroupList, BotGroupMessageText, BotGroupQuote, CenteredScreen, ChatBackgroundBridge, ChatFileChipMenuSheet, ChatIdentityHeader, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, ChatPathChipSpan, ChatReplyIcon, ChatThreadSummary, CindyMakeSessionGate, CloudHomeSwitchSheet, CloudHomeUnreachableNotice, CodexResetAutoUseControl, CompanionCardButton, CompanionChannelTasks, CompanionChoice, CompanionCreateNativeView, CompanionCreateNativeView.ios, CompanionEntering, CompanionFollowUpInvitation, CompanionGlanceSheet, CompanionHeader, CompanionImportSheet, CompanionLearningFooter, CompanionListRow, CompanionMemoryPage, CompanionMemoryPage.ios, CompanionMessageActions, CompanionMessageCard, CompanionModelChain, CompanionNativeContent.ios, CompanionPortraitPicker, CompanionPresenceRing, CompanionProfileArtifacts, CompanionProfileNativeView, CompanionProfileNativeView.ios, CompanionProfileSheet, CompanionSettingsRow, CompanionSettingsRow.ios, CompanionSheet, CompanionSheet.ios, CompanionTaskResultCard, CompanionThreads, CompanionWorkingStatus, ComposerActivityPill, ComposerAttachmentTray, ComposerDockKeyboardFollow, ComposerExpandButton, ComposerExpandButton.ios, ComposerFrame, ComposerFrame.ios, ComposerKeyboardAvoidingView, ComposerMorphSource, ComposerMorphSource.ios, ComposerNativeInput, ComposerNativeInput.ios, ComposerNativeRow, ComposerNativeRow.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerRichInput, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, ContextSheetCollabView, ContextSheetGoalView, ContextSheetGoalView.ios, ContextSheetMediaViews, ContextSheetPlugins, ConversationSearchFilterSheet, ConversationShareSvg, ConversationShareWebView, DeviceLinkContext, DirectLinkTransport, ErrorNotice, FailedScheduleNotice, FileTypeIcon, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, FormSheet, FormSheet.ios, HomeChromeDrawer, HomeChromeFrost, HomeDisplayMenu, HomeDisplayMenu.ios, HomeGlassMenuPanel, HomeHeaderGlassButton, HomeHeaderGlassButton.ios, HomeListVisuals, HomeNativeStackHeader, HomeNewTaskButton, HomeProjectMachineLabel, HomeRosterContext, HomeSearchBar, HomeSessionInfoMeta, HomeStackHeader, HomeSurface, HomeTeammatesSection, ImageLightbox, IncomingShareBridge, InlineQueueSection, InlineQuoteChip, InlineReferenceChip, InlineVideoPlayer, InputHandoffCard, InputHandoffDeviceCode, InputHandoffPictureStep, InputHandoffQrCode, InputHandoffRecordRow, InputHandoffSheet, InputHandoffSheet.ios, InputHandoffStepFields, InteractionPanel, MailLinkSpan, MarkdownBlockContent, MarkdownCodeCopyButton, MarkdownSelectableText, McpAppCard, McpAppViews, MessageActionSheet, MessageLoadingImage, MessageRenderer, MessageUsageCost, MobileAgentMark, MobileAgentSwitcher, MobileBoundaryNotice, MobileComposerInputRow, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileMailHtmlPreview, MobileModelPickerList, MobileModelPickerList.ios, MobileOutboxBridge, MobilePermissionPickerList, MobilePermissionPickerList.ios, MobilePrimitives, MobileProviderMark, MobileUserAvatar, MobileVendorIcon, ModalContentArea, ModelOptionsSheetView, ModelOptionsSheetView.ios, ModelPickerNativeHeader, ModelPickerNativeHeader.ios, ModelPickerSheet, ModelQuotaBar, ModelSpeedIcon, MountOnFirstOpen, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePermissionSheet, NativePermissionSheet.ios, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, NativeVideoPlayer, NewTaskSelectionRow, NewTaskSelectionSheet, NewTaskSelectionSheet.android, NewTaskSelectionSheet.ios, OrcaWorkerDirectoryPicker, OutsideTap, PaperPlaneIcon, PendingSendBubble, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PlanModeChip, PluginCardActions, PluginCardRecipients, PluginInvocationHeader, PluginManagementConfirmation, PluginManagementConfirmation.ios, PluginMenuUnreadDot, PluginNotice, PluginNotice.ios, PluginSetupNativeActions, PluginSetupNativeForm, PluginUiEnablePrompt, PromptRecommendation, PushNotificationsBridge, QuestionReferenceText, QuietSyncIndicator, QuoteCapsule, ReadableHistoryRecovery, RecentMessageHistories, RemoteAccessGuide, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, RemoteSourceMark, RemoteTaskSuggestions, RenameSessionModal, ResidentHomeList, RewindPreviewPanel, RichAnswer, RunningTokenRatePopover, SelectionMenuText, SentInlineAtomBody, SessionActionSheet, SessionActionSheetFrame, SessionActionSheetFrame.android, SessionDetailsNative, SessionDetailsNative.ios, SessionEntryRecovery, SessionHeaderNativeControls, SessionHeaderNativeControls.ios, SessionListDrawer, SessionMenuSheet, SessionOptionsExpoSheet, SessionPaneDivider, SessionPrLinks, SessionQuickSendContext, SessionResourceCards, SessionRightSpinner, SessionSearchNative, SessionSearchNative.ios, SessionTailBanner, SessionTaskLayout, SessionTransientNotice, SessionUsageSummary, ShareImageNativeButton, ShareImageNativeButton.ios, ShareMessageCheckbox, ShareSelectAllButton, ShareSelectionBar, SharedTaskEndedState, SharedTaskScreen, SheetModal, SheetSurface, SideChatTaskBoundary, SideChatsSheet, SimpleStackHeader, SlowSendNotice, StartupSplashOverlay, SwipeableProjectHeader, SwipeableSessionRow, SwipeableSessionRow.ios, SystemNavigationBack, TaskFollowMark, TaskTags, TaskTagsNative, TaskTagsNative.ios, TaskTagsSheet, TeammateCreateController, TeammateGenerationLabel, TeammateList, TeammateProcess, TeammateStatusDot, ThemeProvider, ThinkingDots, UnifiedModelPickerSheet, UnifiedModelPickerView, UnifiedModelPickerView.ios, UsageCostDetails, UsageWidgetBridge, VideoDownloadButton, VideoSitePreview, VoiceRecordingPill, WorkingStatusText, [sessionId], _layout, composerPullThrough, gestureHandler, listDisclosureTransition, mathWebView, mediaPlayerWebView, mermaidWebView, nativeToolbarMenuItems, peerFileTransport, useComposerPendingImagePreview, useLocale, useSharedTaskConfirmation, voicePillWidthMotion | apps/mobile/app/_layout.tsx, apps/mobile/app/sessions/[sessionId].tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ChatReplyIcon.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FileTypeIcon.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileAgentMark.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/MobileUserAvatar.tsx, apps/mobile/src/components/MobileVendorIcon.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/PaperPlaneIcon.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteAccessGuide.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/inputHandoff/InputHandoffCard.tsx, apps/mobile/src/inputHandoff/InputHandoffDeviceCode.tsx, apps/mobile/src/inputHandoff/InputHandoffPictureStep.tsx, apps/mobile/src/inputHandoff/InputHandoffQrCode.tsx, apps/mobile/src/inputHandoff/InputHandoffRecordRow.tsx, apps/mobile/src/inputHandoff/InputHandoffSheet.ios.tsx, apps/mobile/src/inputHandoff/InputHandoffSheet.tsx, apps/mobile/src/inputHandoff/InputHandoffStepFields.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/ModalContentArea.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/HomeNativeStackHeader.tsx, apps/mobile/src/platform/chrome/HomeStackHeader.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/chrome/SystemNavigationBack.tsx, apps/mobile/src/platform/chrome/nativeToolbarMenuItems.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/plugins/MobileMailHtmlPreview.tsx, apps/mobile/src/plugins/PluginCardActions.tsx, apps/mobile/src/plugins/PluginCardRecipients.tsx, apps/mobile/src/plugins/PluginManagementConfirmation.ios.tsx, apps/mobile/src/plugins/PluginManagementConfirmation.tsx, apps/mobile/src/plugins/PluginMenuUnreadDot.tsx, apps/mobile/src/plugins/PluginNotice.ios.tsx, apps/mobile/src/plugins/PluginNotice.tsx, apps/mobile/src/plugins/PluginSetupNativeActions.tsx, apps/mobile/src/plugins/PluginSetupNativeForm.tsx, apps/mobile/src/plugins/PluginUiEnablePrompt.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/AccountSwitcherSheet.tsx, apps/mobile/src/session/AgentErrorDetails.tsx, apps/mobile/src/session/AnnotationBurnInWebView.tsx, apps/mobile/src/session/AuthorizationMessageCard.tsx, apps/mobile/src/session/AutomationTimerIcon.tsx, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/BotGroupAvatars.tsx, apps/mobile/src/session/BotGroupCreateSheet.tsx, apps/mobile/src/session/BotGroupList.tsx, apps/mobile/src/session/BotGroupMessageText.tsx, apps/mobile/src/session/BotGroupQuote.tsx, apps/mobile/src/session/ChatFileChipMenuSheet.tsx, apps/mobile/src/session/ChatIdentityHeader.tsx, apps/mobile/src/session/ChatPathChipSpan.tsx, apps/mobile/src/session/ChatThreadSummary.tsx, apps/mobile/src/session/CindyMakeSessionGate.tsx, apps/mobile/src/session/CloudHomeSwitchSheet.tsx, apps/mobile/src/session/CloudHomeUnreachableNotice.tsx, apps/mobile/src/session/CodexResetAutoUseControl.tsx, apps/mobile/src/session/CompanionCardButton.tsx, apps/mobile/src/session/CompanionChannelTasks.tsx, apps/mobile/src/session/CompanionChoice.tsx, apps/mobile/src/session/CompanionCreateNativeView.ios.tsx, apps/mobile/src/session/CompanionCreateNativeView.tsx, apps/mobile/src/session/CompanionEntering.tsx, apps/mobile/src/session/CompanionFollowUpInvitation.tsx, apps/mobile/src/session/CompanionGlanceSheet.tsx, apps/mobile/src/session/CompanionHeader.tsx, apps/mobile/src/session/CompanionImportSheet.tsx, apps/mobile/src/session/CompanionLearningFooter.tsx, apps/mobile/src/session/CompanionListRow.tsx, apps/mobile/src/session/CompanionMemoryPage.ios.tsx, apps/mobile/src/session/CompanionMemoryPage.tsx, apps/mobile/src/session/CompanionMessageActions.tsx, apps/mobile/src/session/CompanionMessageCard.tsx, apps/mobile/src/session/CompanionModelChain.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionPortraitPicker.tsx, apps/mobile/src/session/CompanionPresenceRing.tsx, apps/mobile/src/session/CompanionProfileArtifacts.tsx, apps/mobile/src/session/CompanionProfileNativeView.ios.tsx, apps/mobile/src/session/CompanionProfileNativeView.tsx, apps/mobile/src/session/CompanionProfileSheet.tsx, apps/mobile/src/session/CompanionSettingsRow.ios.tsx, apps/mobile/src/session/CompanionSettingsRow.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/CompanionTaskResultCard.tsx, apps/mobile/src/session/CompanionThreads.tsx, apps/mobile/src/session/CompanionWorkingStatus.tsx, apps/mobile/src/session/ComposerActivityPill.tsx, apps/mobile/src/session/ComposerAttachmentTray.tsx, apps/mobile/src/session/ComposerDockKeyboardFollow.tsx, apps/mobile/src/session/ComposerExpandButton.ios.tsx, apps/mobile/src/session/ComposerExpandButton.tsx, apps/mobile/src/session/ComposerFrame.ios.tsx, apps/mobile/src/session/ComposerFrame.tsx, apps/mobile/src/session/ComposerKeyboardAvoidingView.tsx, apps/mobile/src/session/ComposerMorphSource.ios.tsx, apps/mobile/src/session/ComposerMorphSource.tsx, apps/mobile/src/session/ComposerNativeInput.ios.tsx, apps/mobile/src/session/ComposerNativeInput.tsx, apps/mobile/src/session/ComposerNativeRow.ios.tsx, apps/mobile/src/session/ComposerNativeRow.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerRichInput.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/ContextSheetCollabView.tsx, apps/mobile/src/session/ContextSheetGoalView.ios.tsx, apps/mobile/src/session/ContextSheetGoalView.tsx, apps/mobile/src/session/ContextSheetMediaViews.tsx, apps/mobile/src/session/ContextSheetPlugins.tsx, apps/mobile/src/session/ConversationSearchFilterSheet.tsx, apps/mobile/src/session/ConversationShareSvg.tsx, apps/mobile/src/session/ConversationShareWebView.tsx, apps/mobile/src/session/FailedScheduleNotice.tsx, apps/mobile/src/session/FormSheet.ios.tsx, apps/mobile/src/session/FormSheet.tsx, apps/mobile/src/session/HomeChromeDrawer.tsx, apps/mobile/src/session/HomeChromeFrost.tsx, apps/mobile/src/session/HomeDisplayMenu.ios.tsx, apps/mobile/src/session/HomeDisplayMenu.tsx, apps/mobile/src/session/HomeGlassMenuPanel.tsx, apps/mobile/src/session/HomeHeaderGlassButton.ios.tsx, apps/mobile/src/session/HomeHeaderGlassButton.tsx, apps/mobile/src/session/HomeListVisuals.tsx, apps/mobile/src/session/HomeNewTaskButton.tsx, apps/mobile/src/session/HomeProjectMachineLabel.tsx, apps/mobile/src/session/HomeRosterContext.tsx, apps/mobile/src/session/HomeSearchBar.tsx, apps/mobile/src/session/HomeSessionInfoMeta.tsx, apps/mobile/src/session/HomeSurface.tsx, apps/mobile/src/session/HomeTeammatesSection.tsx, apps/mobile/src/session/ImageLightbox.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/InlineQueueSection.tsx, apps/mobile/src/session/InlineQuoteChip.tsx, apps/mobile/src/session/InlineReferenceChip.tsx, apps/mobile/src/session/InlineVideoPlayer.tsx, apps/mobile/src/session/InteractionPanel.tsx, apps/mobile/src/session/MailLinkSpan.tsx, apps/mobile/src/session/MarkdownBlockContent.tsx, apps/mobile/src/session/MarkdownCodeCopyButton.tsx, apps/mobile/src/session/MarkdownSelectableText.tsx, apps/mobile/src/session/McpAppCard.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MessageActionSheet.tsx, apps/mobile/src/session/MessageLoadingImage.tsx, apps/mobile/src/session/MessageRenderer.tsx, apps/mobile/src/session/MessageUsageCost.tsx, apps/mobile/src/session/MobileAgentSwitcher.tsx, apps/mobile/src/session/MobileBoundaryNotice.tsx, apps/mobile/src/session/MobileComposerInputRow.tsx, apps/mobile/src/session/MobileModelPickerList.ios.tsx, apps/mobile/src/session/MobileModelPickerList.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/MobilePermissionPickerList.ios.tsx, apps/mobile/src/session/MobilePermissionPickerList.tsx, apps/mobile/src/session/MobileProviderMark.tsx, apps/mobile/src/session/ModelOptionsSheetView.ios.tsx, apps/mobile/src/session/ModelOptionsSheetView.tsx, apps/mobile/src/session/ModelPickerNativeHeader.ios.tsx, apps/mobile/src/session/ModelPickerNativeHeader.tsx, apps/mobile/src/session/ModelPickerSheet.tsx, apps/mobile/src/session/ModelQuotaBar.tsx, apps/mobile/src/session/ModelSpeedIcon.tsx, apps/mobile/src/session/MountOnFirstOpen.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativePermissionSheet.ios.tsx, apps/mobile/src/session/NativePermissionSheet.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/NativeVideoPlayer.tsx, apps/mobile/src/session/NewTaskSelectionRow.tsx, apps/mobile/src/session/NewTaskSelectionSheet.android.tsx, apps/mobile/src/session/NewTaskSelectionSheet.ios.tsx, apps/mobile/src/session/NewTaskSelectionSheet.tsx, apps/mobile/src/session/OrcaWorkerDirectoryPicker.tsx, apps/mobile/src/session/PendingSendBubble.tsx, apps/mobile/src/session/PlanModeChip.tsx, apps/mobile/src/session/PluginInvocationHeader.tsx, apps/mobile/src/session/PromptRecommendation.tsx, apps/mobile/src/session/QuestionReferenceText.tsx, apps/mobile/src/session/QuoteCapsule.tsx, apps/mobile/src/session/ReadableHistoryRecovery.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/RemoteSourceMark.tsx, apps/mobile/src/session/RemoteTaskSuggestions.tsx, apps/mobile/src/session/RenameSessionModal.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/RewindPreviewPanel.tsx, apps/mobile/src/session/RichAnswer.tsx, apps/mobile/src/session/RunningTokenRatePopover.tsx, apps/mobile/src/session/SelectionMenuText.tsx, apps/mobile/src/session/SentInlineAtomBody.tsx, apps/mobile/src/session/SessionActionSheet.tsx, apps/mobile/src/session/SessionActionSheetFrame.android.tsx, apps/mobile/src/session/SessionActionSheetFrame.tsx, apps/mobile/src/session/SessionDetailsNative.ios.tsx, apps/mobile/src/session/SessionDetailsNative.tsx, apps/mobile/src/session/SessionEntryRecovery.tsx, apps/mobile/src/session/SessionHeaderNativeControls.ios.tsx, apps/mobile/src/session/SessionHeaderNativeControls.tsx, apps/mobile/src/session/SessionListDrawer.tsx, apps/mobile/src/session/SessionMenuSheet.tsx, apps/mobile/src/session/SessionOptionsExpoSheet.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SessionPrLinks.tsx, apps/mobile/src/session/SessionQuickSendContext.tsx, apps/mobile/src/session/SessionResourceCards.tsx, apps/mobile/src/session/SessionRightSpinner.tsx, apps/mobile/src/session/SessionSearchNative.ios.tsx, apps/mobile/src/session/SessionSearchNative.tsx, apps/mobile/src/session/SessionTailBanner.tsx, apps/mobile/src/session/SessionTaskLayout.tsx, apps/mobile/src/session/SessionTransientNotice.tsx, apps/mobile/src/session/SessionUsageSummary.tsx, apps/mobile/src/session/ShareImageNativeButton.ios.tsx, apps/mobile/src/session/ShareImageNativeButton.tsx, apps/mobile/src/session/ShareMessageCheckbox.tsx, apps/mobile/src/session/ShareSelectAllButton.tsx, apps/mobile/src/session/ShareSelectionBar.tsx, apps/mobile/src/session/SharedTaskEndedState.tsx, apps/mobile/src/session/SharedTaskScreen.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/session/SideChatTaskBoundary.tsx, apps/mobile/src/session/SideChatsSheet.tsx, apps/mobile/src/session/SlowSendNotice.tsx, apps/mobile/src/session/SwipeableProjectHeader.tsx, apps/mobile/src/session/SwipeableSessionRow.ios.tsx, apps/mobile/src/session/SwipeableSessionRow.tsx, apps/mobile/src/session/TaskFollowMark.tsx, apps/mobile/src/session/TaskTags.tsx, apps/mobile/src/session/TaskTagsNative.ios.tsx, apps/mobile/src/session/TaskTagsNative.tsx, apps/mobile/src/session/TaskTagsSheet.tsx, apps/mobile/src/session/TeammateCreateController.tsx, apps/mobile/src/session/TeammateGenerationLabel.tsx, apps/mobile/src/session/TeammateList.tsx, apps/mobile/src/session/TeammateProcess.tsx, apps/mobile/src/session/TeammateStatusDot.tsx, apps/mobile/src/session/ThinkingDots.tsx, apps/mobile/src/session/UnifiedModelPickerSheet.tsx, apps/mobile/src/session/UnifiedModelPickerView.ios.tsx, apps/mobile/src/session/UnifiedModelPickerView.tsx, apps/mobile/src/session/UsageCostDetails.tsx, apps/mobile/src/session/VideoDownloadButton.tsx, apps/mobile/src/session/VideoSitePreview.tsx, apps/mobile/src/session/VoiceRecordingPill.tsx, apps/mobile/src/session/WorkingStatusText.tsx, apps/mobile/src/session/composerPullThrough.tsx, apps/mobile/src/session/composerRichInputHtml.ts, apps/mobile/src/session/conversationShareWebViewHtml.ts, apps/mobile/src/session/listDisclosureTransition.tsx, apps/mobile/src/session/mathWebView.tsx, apps/mobile/src/session/mathWebViewHtml.ts, apps/mobile/src/session/mediaPlayerWebView.tsx, apps/mobile/src/session/mediaPlayerWebViewHtml.ts, apps/mobile/src/session/mermaidWebView.tsx, apps/mobile/src/session/mermaidWebViewHtml.ts, apps/mobile/src/session/selectableMarkdownHtml.ts, apps/mobile/src/session/useComposerPendingImagePreview.tsx, apps/mobile/src/session/useSharedTaskConfirmation.tsx, apps/mobile/src/session/voicePillWidthMotion.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 7 | 266 | 380 | +| `mobile.chat.new` | mobile | 新建任务 | app/sessions/new.tsx | AccountSwitcherSheet, AdaptiveWindow, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AnnotationBurnInWebView, AppText, AuthContext, AutomationTimerIcon, BlurBackdrop, BotGroupAvatars, BotGroupCreateSheet, BotGroupList, BotGroupTextAttachmentPreview, CenteredScreen, ChatBackgroundBridge, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, CompanionChannelTasks, CompanionChoice, CompanionCreateNativeView, CompanionCreateNativeView.ios, CompanionImportSheet, CompanionListRow, CompanionMemoryPage, CompanionMemoryPage.ios, CompanionModelChain, CompanionNativeContent.ios, CompanionPortraitPicker, CompanionPresenceRing, CompanionProfileArtifacts, CompanionProfileNativeView, CompanionProfileNativeView.ios, CompanionProfileSheet, CompanionSettingsRow, CompanionSettingsRow.ios, CompanionSheet, CompanionSheet.ios, ComposerActivityPill, ComposerAttachmentPreview, ComposerAttachmentTray, ComposerDockKeyboardFollow, ComposerFrame, ComposerFrame.ios, ComposerKeyboardAvoidingView, ComposerMorphSource, ComposerMorphSource.ios, ComposerNativeRow, ComposerNativeRow.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, ContextSheetCollabView, ContextSheetGoalView, ContextSheetGoalView.ios, ContextSheetMediaViews, ContextSheetPlugins, ConversationSearchFilterSheet, DeviceLinkContext, DiagramDocumentPreview, DirectLinkTransport, ErrorNotice, FileTypeIcon, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, FormSheet, FormSheet.ios, HomeChromeDrawer, HomeChromeFrost, HomeDisplayMenu, HomeDisplayMenu.ios, HomeGlassMenuPanel, HomeHeaderGlassButton, HomeHeaderGlassButton.ios, HomeListVisuals, HomeNativeStackHeader, HomeNewTaskButton, HomeProjectMachineLabel, HomeRosterContext, HomeSearchBar, HomeSessionInfoMeta, HomeStackHeader, HomeSurface, HomeTeammatesSection, HtmlFileReader, ImageLightbox, IncomingShareBridge, MarkdownFileReader, McpAppViews, MobileAgentMark, MobileAgentSwitcher, MobileChoicePickerList, MobileComposerInputRow, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileModelPickerList, MobileModelPickerList.ios, MobileOutboxBridge, MobilePermissionPickerList, MobilePermissionPickerList.ios, MobilePrimitives, MobileProviderMark, MobileUserAvatar, MobileVendorIcon, ModalContentArea, ModelDocumentPreview, ModelOptionsSheetView, ModelOptionsSheetView.ios, ModelPickerNativeHeader, ModelPickerNativeHeader.ios, ModelPickerSheet, ModelQuotaBar, ModelSpeedIcon, MountOnFirstOpen, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePermissionSheet, NativePermissionSheet.ios, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, NativeVideoPlayer, NewSessionLayout, NewTaskSelectionRow, NewTaskSelectionSheet, NewTaskSelectionSheet.android, NewTaskSelectionSheet.ios, OrcaWorkerDirectoryPicker, OutsideTap, PaperPlaneIcon, PdfDocumentPreview, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PlanModeChip, PluginManagementConfirmation, PluginManagementConfirmation.ios, PluginMenuUnreadDot, PluginNotice, PluginNotice.ios, PluginUiEnablePrompt, PreviewChrome, PushNotificationsBridge, QuietSyncIndicator, RecentMessageHistories, RemoteAccessGuide, RemoteAttachmentPreview, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, RemoteSourceMark, RemoteTaskSuggestions, RenameSessionModal, ResidentHomeList, SessionActionSheet, SessionActionSheetFrame, SessionActionSheetFrame.android, SessionListDrawer, SessionOptionsExpoSheet, SessionPaneDivider, SessionRightSpinner, SheetModal, SheetSurface, SimpleStackHeader, SlowSendNotice, StartupSplashOverlay, SwipeableProjectHeader, SwipeableSessionRow, SwipeableSessionRow.ios, SystemNavigationBack, TaskFollowMark, TaskTags, TaskTagsNative, TaskTagsNative.ios, TeammateCreateController, TeammateGenerationLabel, TeammateList, TeammateStatusDot, TextDocumentPreview, ThemeProvider, UnifiedModelPickerSheet, UnifiedModelPickerView, UnifiedModelPickerView.ios, UsageWidgetBridge, VideoDownloadButton, VoiceRecordingPill, WorkingStatusText, _layout, composerPullThrough, gestureHandler, listDisclosureTransition, mediaPlayerWebView, mermaidWebView, nativeToolbarMenuItems, new, peerFileTransport, useComposerPendingImagePreview, useLocale, voicePillWidthMotion | apps/mobile/app/_layout.tsx, apps/mobile/app/sessions/new.tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FileTypeIcon.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileAgentMark.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/MobileUserAvatar.tsx, apps/mobile/src/components/MobileVendorIcon.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/PaperPlaneIcon.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteAccessGuide.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/ModalContentArea.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/HomeNativeStackHeader.tsx, apps/mobile/src/platform/chrome/HomeStackHeader.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/chrome/SystemNavigationBack.tsx, apps/mobile/src/platform/chrome/nativeToolbarMenuItems.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/plugins/PluginManagementConfirmation.ios.tsx, apps/mobile/src/plugins/PluginManagementConfirmation.tsx, apps/mobile/src/plugins/PluginMenuUnreadDot.tsx, apps/mobile/src/plugins/PluginNotice.ios.tsx, apps/mobile/src/plugins/PluginNotice.tsx, apps/mobile/src/plugins/PluginUiEnablePrompt.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/AccountSwitcherSheet.tsx, apps/mobile/src/session/AnnotationBurnInWebView.tsx, apps/mobile/src/session/AutomationTimerIcon.tsx, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/BotGroupAvatars.tsx, apps/mobile/src/session/BotGroupCreateSheet.tsx, apps/mobile/src/session/BotGroupList.tsx, apps/mobile/src/session/BotGroupTextAttachmentPreview.tsx, apps/mobile/src/session/CompanionChannelTasks.tsx, apps/mobile/src/session/CompanionChoice.tsx, apps/mobile/src/session/CompanionCreateNativeView.ios.tsx, apps/mobile/src/session/CompanionCreateNativeView.tsx, apps/mobile/src/session/CompanionImportSheet.tsx, apps/mobile/src/session/CompanionListRow.tsx, apps/mobile/src/session/CompanionMemoryPage.ios.tsx, apps/mobile/src/session/CompanionMemoryPage.tsx, apps/mobile/src/session/CompanionModelChain.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionPortraitPicker.tsx, apps/mobile/src/session/CompanionPresenceRing.tsx, apps/mobile/src/session/CompanionProfileArtifacts.tsx, apps/mobile/src/session/CompanionProfileNativeView.ios.tsx, apps/mobile/src/session/CompanionProfileNativeView.tsx, apps/mobile/src/session/CompanionProfileSheet.tsx, apps/mobile/src/session/CompanionSettingsRow.ios.tsx, apps/mobile/src/session/CompanionSettingsRow.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/ComposerActivityPill.tsx, apps/mobile/src/session/ComposerAttachmentPreview.tsx, apps/mobile/src/session/ComposerAttachmentTray.tsx, apps/mobile/src/session/ComposerDockKeyboardFollow.tsx, apps/mobile/src/session/ComposerFrame.ios.tsx, apps/mobile/src/session/ComposerFrame.tsx, apps/mobile/src/session/ComposerKeyboardAvoidingView.tsx, apps/mobile/src/session/ComposerMorphSource.ios.tsx, apps/mobile/src/session/ComposerMorphSource.tsx, apps/mobile/src/session/ComposerNativeRow.ios.tsx, apps/mobile/src/session/ComposerNativeRow.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/ContextSheetCollabView.tsx, apps/mobile/src/session/ContextSheetGoalView.ios.tsx, apps/mobile/src/session/ContextSheetGoalView.tsx, apps/mobile/src/session/ContextSheetMediaViews.tsx, apps/mobile/src/session/ContextSheetPlugins.tsx, apps/mobile/src/session/ConversationSearchFilterSheet.tsx, apps/mobile/src/session/DiagramDocumentPreview.tsx, apps/mobile/src/session/FormSheet.ios.tsx, apps/mobile/src/session/FormSheet.tsx, apps/mobile/src/session/HomeChromeDrawer.tsx, apps/mobile/src/session/HomeChromeFrost.tsx, apps/mobile/src/session/HomeDisplayMenu.ios.tsx, apps/mobile/src/session/HomeDisplayMenu.tsx, apps/mobile/src/session/HomeGlassMenuPanel.tsx, apps/mobile/src/session/HomeHeaderGlassButton.ios.tsx, apps/mobile/src/session/HomeHeaderGlassButton.tsx, apps/mobile/src/session/HomeListVisuals.tsx, apps/mobile/src/session/HomeNewTaskButton.tsx, apps/mobile/src/session/HomeProjectMachineLabel.tsx, apps/mobile/src/session/HomeRosterContext.tsx, apps/mobile/src/session/HomeSearchBar.tsx, apps/mobile/src/session/HomeSessionInfoMeta.tsx, apps/mobile/src/session/HomeSurface.tsx, apps/mobile/src/session/HomeTeammatesSection.tsx, apps/mobile/src/session/HtmlFileReader.tsx, apps/mobile/src/session/ImageLightbox.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/MarkdownFileReader.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MobileAgentSwitcher.tsx, apps/mobile/src/session/MobileChoicePickerList.tsx, apps/mobile/src/session/MobileComposerInputRow.tsx, apps/mobile/src/session/MobileModelPickerList.ios.tsx, apps/mobile/src/session/MobileModelPickerList.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/MobilePermissionPickerList.ios.tsx, apps/mobile/src/session/MobilePermissionPickerList.tsx, apps/mobile/src/session/MobileProviderMark.tsx, apps/mobile/src/session/ModelDocumentPreview.tsx, apps/mobile/src/session/ModelOptionsSheetView.ios.tsx, apps/mobile/src/session/ModelOptionsSheetView.tsx, apps/mobile/src/session/ModelPickerNativeHeader.ios.tsx, apps/mobile/src/session/ModelPickerNativeHeader.tsx, apps/mobile/src/session/ModelPickerSheet.tsx, apps/mobile/src/session/ModelQuotaBar.tsx, apps/mobile/src/session/ModelSpeedIcon.tsx, apps/mobile/src/session/MountOnFirstOpen.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativePermissionSheet.ios.tsx, apps/mobile/src/session/NativePermissionSheet.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/NativeVideoPlayer.tsx, apps/mobile/src/session/NewSessionLayout.tsx, apps/mobile/src/session/NewTaskSelectionRow.tsx, apps/mobile/src/session/NewTaskSelectionSheet.android.tsx, apps/mobile/src/session/NewTaskSelectionSheet.ios.tsx, apps/mobile/src/session/NewTaskSelectionSheet.tsx, apps/mobile/src/session/OrcaWorkerDirectoryPicker.tsx, apps/mobile/src/session/PdfDocumentPreview.tsx, apps/mobile/src/session/PlanModeChip.tsx, apps/mobile/src/session/PreviewChrome.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteAttachmentPreview.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/RemoteSourceMark.tsx, apps/mobile/src/session/RemoteTaskSuggestions.tsx, apps/mobile/src/session/RenameSessionModal.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/SessionActionSheet.tsx, apps/mobile/src/session/SessionActionSheetFrame.android.tsx, apps/mobile/src/session/SessionActionSheetFrame.tsx, apps/mobile/src/session/SessionListDrawer.tsx, apps/mobile/src/session/SessionOptionsExpoSheet.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SessionRightSpinner.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/session/SlowSendNotice.tsx, apps/mobile/src/session/SwipeableProjectHeader.tsx, apps/mobile/src/session/SwipeableSessionRow.ios.tsx, apps/mobile/src/session/SwipeableSessionRow.tsx, apps/mobile/src/session/TaskFollowMark.tsx, apps/mobile/src/session/TaskTags.tsx, apps/mobile/src/session/TaskTagsNative.ios.tsx, apps/mobile/src/session/TaskTagsNative.tsx, apps/mobile/src/session/TeammateCreateController.tsx, apps/mobile/src/session/TeammateGenerationLabel.tsx, apps/mobile/src/session/TeammateList.tsx, apps/mobile/src/session/TeammateStatusDot.tsx, apps/mobile/src/session/TextDocumentPreview.tsx, apps/mobile/src/session/UnifiedModelPickerSheet.tsx, apps/mobile/src/session/UnifiedModelPickerView.ios.tsx, apps/mobile/src/session/UnifiedModelPickerView.tsx, apps/mobile/src/session/VideoDownloadButton.tsx, apps/mobile/src/session/VoiceRecordingPill.tsx, apps/mobile/src/session/WorkingStatusText.tsx, apps/mobile/src/session/composerPullThrough.tsx, apps/mobile/src/session/listDisclosureTransition.tsx, apps/mobile/src/session/mathWebViewHtml.ts, apps/mobile/src/session/mediaPlayerWebView.tsx, apps/mobile/src/session/mediaPlayerWebViewHtml.ts, apps/mobile/src/session/mermaidWebView.tsx, apps/mobile/src/session/mermaidWebViewHtml.ts, apps/mobile/src/session/pdfWebViewHtml.ts, apps/mobile/src/session/selectableMarkdownHtml.ts, apps/mobile/src/session/useComposerPendingImagePreview.tsx, apps/mobile/src/session/voicePillWidthMotion.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 234 | 224 | +| `mobile.chat.session` | mobile | 任务内容与输入 | app/sessions/[sessionId].tsx | AccountSwitcherSheet, AdaptiveWindow, AgentErrorDetails, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AnnotationBurnInWebView, AppText, AuthContext, AuthorizationMessageCard, AutomationTimerIcon, BlurBackdrop, BotGroupAvatars, BotGroupCreateSheet, BotGroupList, BotGroupMessageText, BotGroupQuote, BotGroupTextAttachmentPreview, CenteredScreen, ChatBackgroundBridge, ChatFileChipMenuSheet, ChatIdentityHeader, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, ChatPathChipSpan, ChatReplyIcon, ChatThreadSummary, CindyMakeSessionGate, CloudHomeSwitchSheet, CloudHomeUnreachableNotice, CodexResetAutoUseControl, CompanionCardButton, CompanionChannelTasks, CompanionChoice, CompanionCreateNativeView, CompanionCreateNativeView.ios, CompanionEntering, CompanionFollowUpInvitation, CompanionGlanceSheet, CompanionHeader, CompanionImportSheet, CompanionLearningFooter, CompanionListRow, CompanionMemoryPage, CompanionMemoryPage.ios, CompanionMessageActions, CompanionMessageCard, CompanionModelChain, CompanionNativeContent.ios, CompanionPortraitPicker, CompanionPresenceRing, CompanionProfileArtifacts, CompanionProfileNativeView, CompanionProfileNativeView.ios, CompanionProfileSheet, CompanionSettingsRow, CompanionSettingsRow.ios, CompanionSheet, CompanionSheet.ios, CompanionTaskResultCard, CompanionThreads, CompanionWorkingStatus, ComposerActivityPill, ComposerAttachmentPreview, ComposerAttachmentTray, ComposerDockKeyboardFollow, ComposerExpandButton, ComposerExpandButton.ios, ComposerFrame, ComposerFrame.ios, ComposerKeyboardAvoidingView, ComposerMorphSource, ComposerMorphSource.ios, ComposerNativeInput, ComposerNativeInput.ios, ComposerNativeRow, ComposerNativeRow.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerRichInput, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, ContextSheetCollabView, ContextSheetGoalView, ContextSheetGoalView.ios, ContextSheetMediaViews, ContextSheetPlugins, ConversationSearchFilterSheet, ConversationShareSvg, ConversationShareWebView, DeviceLinkContext, DiagramDocumentPreview, DirectLinkTransport, ErrorNotice, FailedScheduleNotice, FileTypeIcon, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, FormSheet, FormSheet.ios, HomeChromeDrawer, HomeChromeFrost, HomeDisplayMenu, HomeDisplayMenu.ios, HomeGlassMenuPanel, HomeHeaderGlassButton, HomeHeaderGlassButton.ios, HomeListVisuals, HomeNativeStackHeader, HomeNewTaskButton, HomeProjectMachineLabel, HomeRosterContext, HomeSearchBar, HomeSessionInfoMeta, HomeStackHeader, HomeSurface, HomeTeammatesSection, HtmlFileReader, ImageLightbox, IncomingShareBridge, InlineQueueSection, InlineQuoteChip, InlineReferenceChip, InlineVideoPlayer, InputHandoffCard, InputHandoffDeviceCode, InputHandoffPictureStep, InputHandoffQrCode, InputHandoffRecordRow, InputHandoffSheet, InputHandoffSheet.ios, InputHandoffStepFields, InteractionPanel, MailLinkSpan, MarkdownBlockContent, MarkdownCodeCopyButton, MarkdownFileReader, MarkdownSelectableText, McpAppCard, McpAppViews, MessageActionSheet, MessageLoadingImage, MessageRenderer, MessageUsageCost, MobileAgentMark, MobileAgentSwitcher, MobileBoundaryNotice, MobileComposerInputRow, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileMailHtmlPreview, MobileModelPickerList, MobileModelPickerList.ios, MobileOutboxBridge, MobilePermissionPickerList, MobilePermissionPickerList.ios, MobilePrimitives, MobileProviderMark, MobileUserAvatar, MobileVendorIcon, ModalContentArea, ModelDocumentPreview, ModelOptionsSheetView, ModelOptionsSheetView.ios, ModelPickerNativeHeader, ModelPickerNativeHeader.ios, ModelPickerSheet, ModelQuotaBar, ModelSpeedIcon, MountOnFirstOpen, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePermissionSheet, NativePermissionSheet.ios, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, NativeVideoPlayer, NewTaskSelectionRow, NewTaskSelectionSheet, NewTaskSelectionSheet.android, NewTaskSelectionSheet.ios, OrcaWorkerDirectoryPicker, OutsideTap, PaperPlaneIcon, PdfDocumentPreview, PendingSendBubble, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PlanModeChip, PluginCardActions, PluginCardRecipients, PluginInvocationHeader, PluginManagementConfirmation, PluginManagementConfirmation.ios, PluginMenuUnreadDot, PluginNotice, PluginNotice.ios, PluginSetupNativeActions, PluginSetupNativeForm, PluginUiEnablePrompt, PreviewChrome, PromptRecommendation, PushNotificationsBridge, QuestionReferenceText, QuietSyncIndicator, QuoteCapsule, ReadableHistoryRecovery, RecentMessageHistories, RemoteAccessGuide, RemoteAttachmentPreview, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, RemoteSourceMark, RemoteTaskSuggestions, RenameSessionModal, ResidentHomeList, RewindPreviewPanel, RichAnswer, RunningTokenRatePopover, SelectionMenuText, SentInlineAtomBody, SessionActionSheet, SessionActionSheetFrame, SessionActionSheetFrame.android, SessionDetailsNative, SessionDetailsNative.ios, SessionEntryRecovery, SessionHeaderNativeControls, SessionHeaderNativeControls.ios, SessionListDrawer, SessionMenuSheet, SessionOptionsExpoSheet, SessionPaneDivider, SessionPrLinks, SessionQuickSendContext, SessionResourceCards, SessionRightSpinner, SessionSearchNative, SessionSearchNative.ios, SessionTailBanner, SessionTaskLayout, SessionTransientNotice, SessionUsageSummary, ShareImageNativeButton, ShareImageNativeButton.ios, ShareMessageCheckbox, ShareSelectAllButton, ShareSelectionBar, SharedTaskEndedState, SharedTaskScreen, SheetModal, SheetSurface, SideChatTaskBoundary, SideChatsSheet, SimpleStackHeader, SlowSendNotice, StartupSplashOverlay, SwipeableProjectHeader, SwipeableSessionRow, SwipeableSessionRow.ios, SystemNavigationBack, TaskFollowMark, TaskTags, TaskTagsNative, TaskTagsNative.ios, TaskTagsSheet, TeammateCreateController, TeammateGenerationLabel, TeammateList, TeammateProcess, TeammateStatusDot, TextDocumentPreview, ThemeProvider, ThinkingDots, UnifiedModelPickerSheet, UnifiedModelPickerView, UnifiedModelPickerView.ios, UsageCostDetails, UsageWidgetBridge, VideoDownloadButton, VideoSitePreview, VoiceRecordingPill, WorkingStatusText, [sessionId], _layout, composerPullThrough, gestureHandler, listDisclosureTransition, mathWebView, mediaPlayerWebView, mermaidWebView, nativeToolbarMenuItems, peerFileTransport, useComposerPendingImagePreview, useLocale, useSharedTaskConfirmation, voicePillWidthMotion | apps/mobile/app/_layout.tsx, apps/mobile/app/sessions/[sessionId].tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ChatReplyIcon.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FileTypeIcon.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileAgentMark.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/MobileUserAvatar.tsx, apps/mobile/src/components/MobileVendorIcon.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/PaperPlaneIcon.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteAccessGuide.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/inputHandoff/InputHandoffCard.tsx, apps/mobile/src/inputHandoff/InputHandoffDeviceCode.tsx, apps/mobile/src/inputHandoff/InputHandoffPictureStep.tsx, apps/mobile/src/inputHandoff/InputHandoffQrCode.tsx, apps/mobile/src/inputHandoff/InputHandoffRecordRow.tsx, apps/mobile/src/inputHandoff/InputHandoffSheet.ios.tsx, apps/mobile/src/inputHandoff/InputHandoffSheet.tsx, apps/mobile/src/inputHandoff/InputHandoffStepFields.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/ModalContentArea.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/HomeNativeStackHeader.tsx, apps/mobile/src/platform/chrome/HomeStackHeader.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/chrome/SystemNavigationBack.tsx, apps/mobile/src/platform/chrome/nativeToolbarMenuItems.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/plugins/MobileMailHtmlPreview.tsx, apps/mobile/src/plugins/PluginCardActions.tsx, apps/mobile/src/plugins/PluginCardRecipients.tsx, apps/mobile/src/plugins/PluginManagementConfirmation.ios.tsx, apps/mobile/src/plugins/PluginManagementConfirmation.tsx, apps/mobile/src/plugins/PluginMenuUnreadDot.tsx, apps/mobile/src/plugins/PluginNotice.ios.tsx, apps/mobile/src/plugins/PluginNotice.tsx, apps/mobile/src/plugins/PluginSetupNativeActions.tsx, apps/mobile/src/plugins/PluginSetupNativeForm.tsx, apps/mobile/src/plugins/PluginUiEnablePrompt.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/AccountSwitcherSheet.tsx, apps/mobile/src/session/AgentErrorDetails.tsx, apps/mobile/src/session/AnnotationBurnInWebView.tsx, apps/mobile/src/session/AuthorizationMessageCard.tsx, apps/mobile/src/session/AutomationTimerIcon.tsx, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/BotGroupAvatars.tsx, apps/mobile/src/session/BotGroupCreateSheet.tsx, apps/mobile/src/session/BotGroupList.tsx, apps/mobile/src/session/BotGroupMessageText.tsx, apps/mobile/src/session/BotGroupQuote.tsx, apps/mobile/src/session/BotGroupTextAttachmentPreview.tsx, apps/mobile/src/session/ChatFileChipMenuSheet.tsx, apps/mobile/src/session/ChatIdentityHeader.tsx, apps/mobile/src/session/ChatPathChipSpan.tsx, apps/mobile/src/session/ChatThreadSummary.tsx, apps/mobile/src/session/CindyMakeSessionGate.tsx, apps/mobile/src/session/CloudHomeSwitchSheet.tsx, apps/mobile/src/session/CloudHomeUnreachableNotice.tsx, apps/mobile/src/session/CodexResetAutoUseControl.tsx, apps/mobile/src/session/CompanionCardButton.tsx, apps/mobile/src/session/CompanionChannelTasks.tsx, apps/mobile/src/session/CompanionChoice.tsx, apps/mobile/src/session/CompanionCreateNativeView.ios.tsx, apps/mobile/src/session/CompanionCreateNativeView.tsx, apps/mobile/src/session/CompanionEntering.tsx, apps/mobile/src/session/CompanionFollowUpInvitation.tsx, apps/mobile/src/session/CompanionGlanceSheet.tsx, apps/mobile/src/session/CompanionHeader.tsx, apps/mobile/src/session/CompanionImportSheet.tsx, apps/mobile/src/session/CompanionLearningFooter.tsx, apps/mobile/src/session/CompanionListRow.tsx, apps/mobile/src/session/CompanionMemoryPage.ios.tsx, apps/mobile/src/session/CompanionMemoryPage.tsx, apps/mobile/src/session/CompanionMessageActions.tsx, apps/mobile/src/session/CompanionMessageCard.tsx, apps/mobile/src/session/CompanionModelChain.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionPortraitPicker.tsx, apps/mobile/src/session/CompanionPresenceRing.tsx, apps/mobile/src/session/CompanionProfileArtifacts.tsx, apps/mobile/src/session/CompanionProfileNativeView.ios.tsx, apps/mobile/src/session/CompanionProfileNativeView.tsx, apps/mobile/src/session/CompanionProfileSheet.tsx, apps/mobile/src/session/CompanionSettingsRow.ios.tsx, apps/mobile/src/session/CompanionSettingsRow.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/CompanionTaskResultCard.tsx, apps/mobile/src/session/CompanionThreads.tsx, apps/mobile/src/session/CompanionWorkingStatus.tsx, apps/mobile/src/session/ComposerActivityPill.tsx, apps/mobile/src/session/ComposerAttachmentPreview.tsx, apps/mobile/src/session/ComposerAttachmentTray.tsx, apps/mobile/src/session/ComposerDockKeyboardFollow.tsx, apps/mobile/src/session/ComposerExpandButton.ios.tsx, apps/mobile/src/session/ComposerExpandButton.tsx, apps/mobile/src/session/ComposerFrame.ios.tsx, apps/mobile/src/session/ComposerFrame.tsx, apps/mobile/src/session/ComposerKeyboardAvoidingView.tsx, apps/mobile/src/session/ComposerMorphSource.ios.tsx, apps/mobile/src/session/ComposerMorphSource.tsx, apps/mobile/src/session/ComposerNativeInput.ios.tsx, apps/mobile/src/session/ComposerNativeInput.tsx, apps/mobile/src/session/ComposerNativeRow.ios.tsx, apps/mobile/src/session/ComposerNativeRow.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerRichInput.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/ContextSheetCollabView.tsx, apps/mobile/src/session/ContextSheetGoalView.ios.tsx, apps/mobile/src/session/ContextSheetGoalView.tsx, apps/mobile/src/session/ContextSheetMediaViews.tsx, apps/mobile/src/session/ContextSheetPlugins.tsx, apps/mobile/src/session/ConversationSearchFilterSheet.tsx, apps/mobile/src/session/ConversationShareSvg.tsx, apps/mobile/src/session/ConversationShareWebView.tsx, apps/mobile/src/session/DiagramDocumentPreview.tsx, apps/mobile/src/session/FailedScheduleNotice.tsx, apps/mobile/src/session/FormSheet.ios.tsx, apps/mobile/src/session/FormSheet.tsx, apps/mobile/src/session/HomeChromeDrawer.tsx, apps/mobile/src/session/HomeChromeFrost.tsx, apps/mobile/src/session/HomeDisplayMenu.ios.tsx, apps/mobile/src/session/HomeDisplayMenu.tsx, apps/mobile/src/session/HomeGlassMenuPanel.tsx, apps/mobile/src/session/HomeHeaderGlassButton.ios.tsx, apps/mobile/src/session/HomeHeaderGlassButton.tsx, apps/mobile/src/session/HomeListVisuals.tsx, apps/mobile/src/session/HomeNewTaskButton.tsx, apps/mobile/src/session/HomeProjectMachineLabel.tsx, apps/mobile/src/session/HomeRosterContext.tsx, apps/mobile/src/session/HomeSearchBar.tsx, apps/mobile/src/session/HomeSessionInfoMeta.tsx, apps/mobile/src/session/HomeSurface.tsx, apps/mobile/src/session/HomeTeammatesSection.tsx, apps/mobile/src/session/HtmlFileReader.tsx, apps/mobile/src/session/ImageLightbox.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/InlineQueueSection.tsx, apps/mobile/src/session/InlineQuoteChip.tsx, apps/mobile/src/session/InlineReferenceChip.tsx, apps/mobile/src/session/InlineVideoPlayer.tsx, apps/mobile/src/session/InteractionPanel.tsx, apps/mobile/src/session/MailLinkSpan.tsx, apps/mobile/src/session/MarkdownBlockContent.tsx, apps/mobile/src/session/MarkdownCodeCopyButton.tsx, apps/mobile/src/session/MarkdownFileReader.tsx, apps/mobile/src/session/MarkdownSelectableText.tsx, apps/mobile/src/session/McpAppCard.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MessageActionSheet.tsx, apps/mobile/src/session/MessageLoadingImage.tsx, apps/mobile/src/session/MessageRenderer.tsx, apps/mobile/src/session/MessageUsageCost.tsx, apps/mobile/src/session/MobileAgentSwitcher.tsx, apps/mobile/src/session/MobileBoundaryNotice.tsx, apps/mobile/src/session/MobileComposerInputRow.tsx, apps/mobile/src/session/MobileModelPickerList.ios.tsx, apps/mobile/src/session/MobileModelPickerList.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/MobilePermissionPickerList.ios.tsx, apps/mobile/src/session/MobilePermissionPickerList.tsx, apps/mobile/src/session/MobileProviderMark.tsx, apps/mobile/src/session/ModelDocumentPreview.tsx, apps/mobile/src/session/ModelOptionsSheetView.ios.tsx, apps/mobile/src/session/ModelOptionsSheetView.tsx, apps/mobile/src/session/ModelPickerNativeHeader.ios.tsx, apps/mobile/src/session/ModelPickerNativeHeader.tsx, apps/mobile/src/session/ModelPickerSheet.tsx, apps/mobile/src/session/ModelQuotaBar.tsx, apps/mobile/src/session/ModelSpeedIcon.tsx, apps/mobile/src/session/MountOnFirstOpen.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativePermissionSheet.ios.tsx, apps/mobile/src/session/NativePermissionSheet.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/NativeVideoPlayer.tsx, apps/mobile/src/session/NewTaskSelectionRow.tsx, apps/mobile/src/session/NewTaskSelectionSheet.android.tsx, apps/mobile/src/session/NewTaskSelectionSheet.ios.tsx, apps/mobile/src/session/NewTaskSelectionSheet.tsx, apps/mobile/src/session/OrcaWorkerDirectoryPicker.tsx, apps/mobile/src/session/PdfDocumentPreview.tsx, apps/mobile/src/session/PendingSendBubble.tsx, apps/mobile/src/session/PlanModeChip.tsx, apps/mobile/src/session/PluginInvocationHeader.tsx, apps/mobile/src/session/PreviewChrome.tsx, apps/mobile/src/session/PromptRecommendation.tsx, apps/mobile/src/session/QuestionReferenceText.tsx, apps/mobile/src/session/QuoteCapsule.tsx, apps/mobile/src/session/ReadableHistoryRecovery.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteAttachmentPreview.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/RemoteSourceMark.tsx, apps/mobile/src/session/RemoteTaskSuggestions.tsx, apps/mobile/src/session/RenameSessionModal.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/RewindPreviewPanel.tsx, apps/mobile/src/session/RichAnswer.tsx, apps/mobile/src/session/RunningTokenRatePopover.tsx, apps/mobile/src/session/SelectionMenuText.tsx, apps/mobile/src/session/SentInlineAtomBody.tsx, apps/mobile/src/session/SessionActionSheet.tsx, apps/mobile/src/session/SessionActionSheetFrame.android.tsx, apps/mobile/src/session/SessionActionSheetFrame.tsx, apps/mobile/src/session/SessionDetailsNative.ios.tsx, apps/mobile/src/session/SessionDetailsNative.tsx, apps/mobile/src/session/SessionEntryRecovery.tsx, apps/mobile/src/session/SessionHeaderNativeControls.ios.tsx, apps/mobile/src/session/SessionHeaderNativeControls.tsx, apps/mobile/src/session/SessionListDrawer.tsx, apps/mobile/src/session/SessionMenuSheet.tsx, apps/mobile/src/session/SessionOptionsExpoSheet.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SessionPrLinks.tsx, apps/mobile/src/session/SessionQuickSendContext.tsx, apps/mobile/src/session/SessionResourceCards.tsx, apps/mobile/src/session/SessionRightSpinner.tsx, apps/mobile/src/session/SessionSearchNative.ios.tsx, apps/mobile/src/session/SessionSearchNative.tsx, apps/mobile/src/session/SessionTailBanner.tsx, apps/mobile/src/session/SessionTaskLayout.tsx, apps/mobile/src/session/SessionTransientNotice.tsx, apps/mobile/src/session/SessionUsageSummary.tsx, apps/mobile/src/session/ShareImageNativeButton.ios.tsx, apps/mobile/src/session/ShareImageNativeButton.tsx, apps/mobile/src/session/ShareMessageCheckbox.tsx, apps/mobile/src/session/ShareSelectAllButton.tsx, apps/mobile/src/session/ShareSelectionBar.tsx, apps/mobile/src/session/SharedTaskEndedState.tsx, apps/mobile/src/session/SharedTaskScreen.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/session/SideChatTaskBoundary.tsx, apps/mobile/src/session/SideChatsSheet.tsx, apps/mobile/src/session/SlowSendNotice.tsx, apps/mobile/src/session/SwipeableProjectHeader.tsx, apps/mobile/src/session/SwipeableSessionRow.ios.tsx, apps/mobile/src/session/SwipeableSessionRow.tsx, apps/mobile/src/session/TaskFollowMark.tsx, apps/mobile/src/session/TaskTags.tsx, apps/mobile/src/session/TaskTagsNative.ios.tsx, apps/mobile/src/session/TaskTagsNative.tsx, apps/mobile/src/session/TaskTagsSheet.tsx, apps/mobile/src/session/TeammateCreateController.tsx, apps/mobile/src/session/TeammateGenerationLabel.tsx, apps/mobile/src/session/TeammateList.tsx, apps/mobile/src/session/TeammateProcess.tsx, apps/mobile/src/session/TeammateStatusDot.tsx, apps/mobile/src/session/TextDocumentPreview.tsx, apps/mobile/src/session/ThinkingDots.tsx, apps/mobile/src/session/UnifiedModelPickerSheet.tsx, apps/mobile/src/session/UnifiedModelPickerView.ios.tsx, apps/mobile/src/session/UnifiedModelPickerView.tsx, apps/mobile/src/session/UsageCostDetails.tsx, apps/mobile/src/session/VideoDownloadButton.tsx, apps/mobile/src/session/VideoSitePreview.tsx, apps/mobile/src/session/VoiceRecordingPill.tsx, apps/mobile/src/session/WorkingStatusText.tsx, apps/mobile/src/session/composerPullThrough.tsx, apps/mobile/src/session/composerRichInputHtml.ts, apps/mobile/src/session/conversationShareWebViewHtml.ts, apps/mobile/src/session/listDisclosureTransition.tsx, apps/mobile/src/session/mathWebView.tsx, apps/mobile/src/session/mathWebViewHtml.ts, apps/mobile/src/session/mediaPlayerWebView.tsx, apps/mobile/src/session/mediaPlayerWebViewHtml.ts, apps/mobile/src/session/mermaidWebView.tsx, apps/mobile/src/session/mermaidWebViewHtml.ts, apps/mobile/src/session/pdfWebViewHtml.ts, apps/mobile/src/session/selectableMarkdownHtml.ts, apps/mobile/src/session/useComposerPendingImagePreview.tsx, apps/mobile/src/session/useSharedTaskConfirmation.tsx, apps/mobile/src/session/voicePillWidthMotion.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 7 | 235 | 380 | | `mobile.chat.sharing` | mobile | 共享任务邀请与成员;供应商分享链接提示 | app/shared-session.tsx / app/provider-share.tsx | AdaptiveWindow, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, BlurBackdrop, CenteredScreen, ChatBackgroundBridge, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, CompanionNativeContent.ios, CompanionSheet, CompanionSheet.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, DeviceLinkContext, DirectLinkTransport, ErrorNotice, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, IncomingShareBridge, McpAppViews, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileOutboxBridge, MobilePrimitives, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, OutsideTap, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PushNotificationsBridge, QuietSyncIndicator, RecentMessageHistories, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, ResidentHomeList, SessionPaneDivider, SharedTaskAdmissionDialog, SharedTaskEndedState, SharedTaskScreen, SheetModal, SheetSurface, SimpleStackHeader, StartupSplashOverlay, ThemeProvider, UsageWidgetBridge, _layout, gestureHandler, peerFileTransport, provider-share, shared-session, useLocale, useSharedTaskConfirmation | apps/mobile/app/_layout.tsx, apps/mobile/app/provider-share.tsx, apps/mobile/app/shared-session.tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SharedTaskAdmissionDialog.tsx, apps/mobile/src/session/SharedTaskEndedState.tsx, apps/mobile/src/session/SharedTaskScreen.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/session/useSharedTaskConfirmation.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 226 | 89 | -| `mobile.companions.cloud` | mobile | 云端伙伴:主力电脑离线时的对话与换电脑 | app/companions/cloud/[actorId].tsx | AdaptiveWindow, AgentErrorDetails, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, AuthorizationMessageCard, BlurBackdrop, BotGroupAvatars, BotGroupMessageAttachments, BotGroupMessageText, BotGroupQuote, BotGroupTextAttachmentPreview, CenteredScreen, ChatBackgroundBridge, ChatIdentityHeader, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, ChatPathChipSpan, ChatReplyIcon, ChatThreadSummary, CloudCompanionScreen, CompanionCardButton, CompanionEntering, CompanionLearningFooter, CompanionMessageActions, CompanionMessageCard, CompanionNativeContent.ios, CompanionPresenceRing, CompanionSettingsRow, CompanionSettingsRow.ios, CompanionSheet, CompanionSheet.ios, CompanionTaskResultCard, CompanionThreads, ComposerFrame, ComposerFrame.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, DeviceLinkContext, DirectLinkTransport, ErrorNotice, FileTypeIcon, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, HomeHeaderGlassButton, HomeHeaderGlassButton.ios, HomeNativeStackHeader, HomeStackHeader, HtmlFileReader, ImageLightbox, IncomingShareBridge, InlineQuoteChip, InlineReferenceChip, InlineVideoPlayer, InputHandoffCard, InputHandoffDeviceCode, InputHandoffPictureStep, InputHandoffQrCode, InputHandoffRecordRow, InputHandoffSheet, InputHandoffSheet.ios, InputHandoffStepFields, InteractionPanel, MailLinkSpan, MarkdownBlockContent, MarkdownCodeCopyButton, MarkdownFileReader, MarkdownSelectableText, McpAppCard, McpAppViews, MessageActionSheet, MessageLoadingImage, MessageRenderer, MessageUsageCost, MobileBoundaryNotice, MobileComposerInputRow, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileMailHtmlPreview, MobileOutboxBridge, MobilePrimitives, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, NativeVideoPlayer, OutsideTap, PendingSendBubble, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PluginCardActions, PluginCardRecipients, PluginInvocationHeader, PluginSetupNativeActions, PluginSetupNativeForm, PushNotificationsBridge, QuestionReferenceText, QuietSyncIndicator, QuoteCapsule, RecentMessageHistories, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, ResidentHomeList, RichAnswer, SelectionMenuText, SentInlineAtomBody, SessionPaneDivider, SessionQuickSendContext, ShareMessageCheckbox, SheetModal, SheetSurface, SimpleStackHeader, StartupSplashOverlay, TeammateProcess, ThemeProvider, UsageCostDetails, UsageWidgetBridge, VideoDownloadButton, VideoSitePreview, [actorId], _layout, gestureHandler, mathWebView, mediaPlayerWebView, mermaidWebView, nativeToolbarMenuItems, peerFileTransport, useLocale, voicePillWidthMotion | apps/mobile/app/_layout.tsx, apps/mobile/app/companions/cloud/[actorId].tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ChatReplyIcon.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FileTypeIcon.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/inputHandoff/InputHandoffCard.tsx, apps/mobile/src/inputHandoff/InputHandoffDeviceCode.tsx, apps/mobile/src/inputHandoff/InputHandoffPictureStep.tsx, apps/mobile/src/inputHandoff/InputHandoffQrCode.tsx, apps/mobile/src/inputHandoff/InputHandoffRecordRow.tsx, apps/mobile/src/inputHandoff/InputHandoffSheet.ios.tsx, apps/mobile/src/inputHandoff/InputHandoffSheet.tsx, apps/mobile/src/inputHandoff/InputHandoffStepFields.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/HomeNativeStackHeader.tsx, apps/mobile/src/platform/chrome/HomeStackHeader.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/chrome/nativeToolbarMenuItems.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/plugins/MobileMailHtmlPreview.tsx, apps/mobile/src/plugins/PluginCardActions.tsx, apps/mobile/src/plugins/PluginCardRecipients.tsx, apps/mobile/src/plugins/PluginSetupNativeActions.tsx, apps/mobile/src/plugins/PluginSetupNativeForm.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/AgentErrorDetails.tsx, apps/mobile/src/session/AuthorizationMessageCard.tsx, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/BotGroupAvatars.tsx, apps/mobile/src/session/BotGroupMessageAttachments.tsx, apps/mobile/src/session/BotGroupMessageText.tsx, apps/mobile/src/session/BotGroupQuote.tsx, apps/mobile/src/session/BotGroupTextAttachmentPreview.tsx, apps/mobile/src/session/ChatIdentityHeader.tsx, apps/mobile/src/session/ChatPathChipSpan.tsx, apps/mobile/src/session/ChatThreadSummary.tsx, apps/mobile/src/session/CloudCompanionScreen.tsx, apps/mobile/src/session/CompanionCardButton.tsx, apps/mobile/src/session/CompanionEntering.tsx, apps/mobile/src/session/CompanionLearningFooter.tsx, apps/mobile/src/session/CompanionMessageActions.tsx, apps/mobile/src/session/CompanionMessageCard.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionPresenceRing.tsx, apps/mobile/src/session/CompanionSettingsRow.ios.tsx, apps/mobile/src/session/CompanionSettingsRow.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/CompanionTaskResultCard.tsx, apps/mobile/src/session/CompanionThreads.tsx, apps/mobile/src/session/ComposerFrame.ios.tsx, apps/mobile/src/session/ComposerFrame.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/HomeHeaderGlassButton.ios.tsx, apps/mobile/src/session/HomeHeaderGlassButton.tsx, apps/mobile/src/session/HtmlFileReader.tsx, apps/mobile/src/session/ImageLightbox.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/InlineQuoteChip.tsx, apps/mobile/src/session/InlineReferenceChip.tsx, apps/mobile/src/session/InlineVideoPlayer.tsx, apps/mobile/src/session/InteractionPanel.tsx, apps/mobile/src/session/MailLinkSpan.tsx, apps/mobile/src/session/MarkdownBlockContent.tsx, apps/mobile/src/session/MarkdownCodeCopyButton.tsx, apps/mobile/src/session/MarkdownFileReader.tsx, apps/mobile/src/session/MarkdownSelectableText.tsx, apps/mobile/src/session/McpAppCard.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MessageActionSheet.tsx, apps/mobile/src/session/MessageLoadingImage.tsx, apps/mobile/src/session/MessageRenderer.tsx, apps/mobile/src/session/MessageUsageCost.tsx, apps/mobile/src/session/MobileBoundaryNotice.tsx, apps/mobile/src/session/MobileComposerInputRow.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/NativeVideoPlayer.tsx, apps/mobile/src/session/PendingSendBubble.tsx, apps/mobile/src/session/PluginInvocationHeader.tsx, apps/mobile/src/session/QuestionReferenceText.tsx, apps/mobile/src/session/QuoteCapsule.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/RichAnswer.tsx, apps/mobile/src/session/SelectionMenuText.tsx, apps/mobile/src/session/SentInlineAtomBody.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SessionQuickSendContext.tsx, apps/mobile/src/session/ShareMessageCheckbox.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/session/TeammateProcess.tsx, apps/mobile/src/session/UsageCostDetails.tsx, apps/mobile/src/session/VideoDownloadButton.tsx, apps/mobile/src/session/VideoSitePreview.tsx, apps/mobile/src/session/mathWebView.tsx, apps/mobile/src/session/mathWebViewHtml.ts, apps/mobile/src/session/mediaPlayerWebView.tsx, apps/mobile/src/session/mediaPlayerWebViewHtml.ts, apps/mobile/src/session/mermaidWebView.tsx, apps/mobile/src/session/mermaidWebViewHtml.ts, apps/mobile/src/session/selectableMarkdownHtml.ts, apps/mobile/src/session/voicePillWidthMotion.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 265 | 219 | +| `mobile.companions.cloud` | mobile | 云端伙伴:主力电脑离线时的对话与换电脑 | app/companions/cloud/[actorId].tsx | AdaptiveWindow, AgentErrorDetails, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, AuthorizationMessageCard, BlurBackdrop, BotGroupAvatars, BotGroupMessageAttachments, BotGroupMessageText, BotGroupQuote, BotGroupTextAttachmentPreview, CenteredScreen, ChatBackgroundBridge, ChatIdentityHeader, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, ChatPathChipSpan, ChatReplyIcon, ChatThreadSummary, CloudCompanionScreen, CompanionCardButton, CompanionEntering, CompanionLearningFooter, CompanionMessageActions, CompanionMessageCard, CompanionNativeContent.ios, CompanionPresenceRing, CompanionSettingsRow, CompanionSettingsRow.ios, CompanionSheet, CompanionSheet.ios, CompanionTaskResultCard, CompanionThreads, ComposerFrame, ComposerFrame.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, DeviceLinkContext, DiagramDocumentPreview, DirectLinkTransport, ErrorNotice, FileTypeIcon, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, HomeHeaderGlassButton, HomeHeaderGlassButton.ios, HomeNativeStackHeader, HomeStackHeader, HtmlFileReader, ImageLightbox, IncomingShareBridge, InlineQuoteChip, InlineReferenceChip, InlineVideoPlayer, InputHandoffCard, InputHandoffDeviceCode, InputHandoffPictureStep, InputHandoffQrCode, InputHandoffRecordRow, InputHandoffSheet, InputHandoffSheet.ios, InputHandoffStepFields, InteractionPanel, MailLinkSpan, MarkdownBlockContent, MarkdownCodeCopyButton, MarkdownFileReader, MarkdownSelectableText, McpAppCard, McpAppViews, MessageActionSheet, MessageLoadingImage, MessageRenderer, MessageUsageCost, MobileBoundaryNotice, MobileComposerInputRow, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileMailHtmlPreview, MobileOutboxBridge, MobilePrimitives, ModelDocumentPreview, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, NativeVideoPlayer, OutsideTap, PdfDocumentPreview, PendingSendBubble, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PluginCardActions, PluginCardRecipients, PluginInvocationHeader, PluginSetupNativeActions, PluginSetupNativeForm, PreviewChrome, PushNotificationsBridge, QuestionReferenceText, QuietSyncIndicator, QuoteCapsule, RecentMessageHistories, RemoteAttachmentPreview, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, ResidentHomeList, RichAnswer, SelectionMenuText, SentInlineAtomBody, SessionPaneDivider, SessionQuickSendContext, ShareMessageCheckbox, SheetModal, SheetSurface, SimpleStackHeader, StartupSplashOverlay, TeammateProcess, TextDocumentPreview, ThemeProvider, UsageCostDetails, UsageWidgetBridge, VideoDownloadButton, VideoSitePreview, [actorId], _layout, gestureHandler, mathWebView, mediaPlayerWebView, mermaidWebView, nativeToolbarMenuItems, peerFileTransport, useLocale, voicePillWidthMotion | apps/mobile/app/_layout.tsx, apps/mobile/app/companions/cloud/[actorId].tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ChatReplyIcon.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FileTypeIcon.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/inputHandoff/InputHandoffCard.tsx, apps/mobile/src/inputHandoff/InputHandoffDeviceCode.tsx, apps/mobile/src/inputHandoff/InputHandoffPictureStep.tsx, apps/mobile/src/inputHandoff/InputHandoffQrCode.tsx, apps/mobile/src/inputHandoff/InputHandoffRecordRow.tsx, apps/mobile/src/inputHandoff/InputHandoffSheet.ios.tsx, apps/mobile/src/inputHandoff/InputHandoffSheet.tsx, apps/mobile/src/inputHandoff/InputHandoffStepFields.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/HomeNativeStackHeader.tsx, apps/mobile/src/platform/chrome/HomeStackHeader.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/chrome/nativeToolbarMenuItems.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/plugins/MobileMailHtmlPreview.tsx, apps/mobile/src/plugins/PluginCardActions.tsx, apps/mobile/src/plugins/PluginCardRecipients.tsx, apps/mobile/src/plugins/PluginSetupNativeActions.tsx, apps/mobile/src/plugins/PluginSetupNativeForm.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/AgentErrorDetails.tsx, apps/mobile/src/session/AuthorizationMessageCard.tsx, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/BotGroupAvatars.tsx, apps/mobile/src/session/BotGroupMessageAttachments.tsx, apps/mobile/src/session/BotGroupMessageText.tsx, apps/mobile/src/session/BotGroupQuote.tsx, apps/mobile/src/session/BotGroupTextAttachmentPreview.tsx, apps/mobile/src/session/ChatIdentityHeader.tsx, apps/mobile/src/session/ChatPathChipSpan.tsx, apps/mobile/src/session/ChatThreadSummary.tsx, apps/mobile/src/session/CloudCompanionScreen.tsx, apps/mobile/src/session/CompanionCardButton.tsx, apps/mobile/src/session/CompanionEntering.tsx, apps/mobile/src/session/CompanionLearningFooter.tsx, apps/mobile/src/session/CompanionMessageActions.tsx, apps/mobile/src/session/CompanionMessageCard.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionPresenceRing.tsx, apps/mobile/src/session/CompanionSettingsRow.ios.tsx, apps/mobile/src/session/CompanionSettingsRow.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/CompanionTaskResultCard.tsx, apps/mobile/src/session/CompanionThreads.tsx, apps/mobile/src/session/ComposerFrame.ios.tsx, apps/mobile/src/session/ComposerFrame.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/DiagramDocumentPreview.tsx, apps/mobile/src/session/HomeHeaderGlassButton.ios.tsx, apps/mobile/src/session/HomeHeaderGlassButton.tsx, apps/mobile/src/session/HtmlFileReader.tsx, apps/mobile/src/session/ImageLightbox.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/InlineQuoteChip.tsx, apps/mobile/src/session/InlineReferenceChip.tsx, apps/mobile/src/session/InlineVideoPlayer.tsx, apps/mobile/src/session/InteractionPanel.tsx, apps/mobile/src/session/MailLinkSpan.tsx, apps/mobile/src/session/MarkdownBlockContent.tsx, apps/mobile/src/session/MarkdownCodeCopyButton.tsx, apps/mobile/src/session/MarkdownFileReader.tsx, apps/mobile/src/session/MarkdownSelectableText.tsx, apps/mobile/src/session/McpAppCard.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MessageActionSheet.tsx, apps/mobile/src/session/MessageLoadingImage.tsx, apps/mobile/src/session/MessageRenderer.tsx, apps/mobile/src/session/MessageUsageCost.tsx, apps/mobile/src/session/MobileBoundaryNotice.tsx, apps/mobile/src/session/MobileComposerInputRow.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/ModelDocumentPreview.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/NativeVideoPlayer.tsx, apps/mobile/src/session/PdfDocumentPreview.tsx, apps/mobile/src/session/PendingSendBubble.tsx, apps/mobile/src/session/PluginInvocationHeader.tsx, apps/mobile/src/session/PreviewChrome.tsx, apps/mobile/src/session/QuestionReferenceText.tsx, apps/mobile/src/session/QuoteCapsule.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteAttachmentPreview.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/RichAnswer.tsx, apps/mobile/src/session/SelectionMenuText.tsx, apps/mobile/src/session/SentInlineAtomBody.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SessionQuickSendContext.tsx, apps/mobile/src/session/ShareMessageCheckbox.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/session/TeammateProcess.tsx, apps/mobile/src/session/TextDocumentPreview.tsx, apps/mobile/src/session/UsageCostDetails.tsx, apps/mobile/src/session/VideoDownloadButton.tsx, apps/mobile/src/session/VideoSitePreview.tsx, apps/mobile/src/session/mathWebView.tsx, apps/mobile/src/session/mathWebViewHtml.ts, apps/mobile/src/session/mediaPlayerWebView.tsx, apps/mobile/src/session/mediaPlayerWebViewHtml.ts, apps/mobile/src/session/mermaidWebView.tsx, apps/mobile/src/session/mermaidWebViewHtml.ts, apps/mobile/src/session/pdfWebViewHtml.ts, apps/mobile/src/session/selectableMarkdownHtml.ts, apps/mobile/src/session/voicePillWidthMotion.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 234 | 218 | | `mobile.companions.direct` | mobile | 伙伴私聊回看 | app/companions/direct/[threadId].tsx | AdaptiveWindow, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, BlurBackdrop, CenteredScreen, ChatBackgroundBridge, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, CompanionNativeContent.ios, CompanionSheet, CompanionSheet.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, DeviceLinkContext, DirectLinkTransport, ErrorNotice, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, HomeHeaderGlassButton, HomeHeaderGlassButton.ios, HomeNativeStackHeader, HomeStackHeader, IncomingShareBridge, McpAppViews, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileOutboxBridge, MobilePrimitives, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, OutsideTap, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PushNotificationsBridge, QuietSyncIndicator, RecentMessageHistories, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, ResidentHomeList, SessionPaneDivider, SheetModal, SheetSurface, SimpleStackHeader, StartupSplashOverlay, ThemeProvider, UsageWidgetBridge, [threadId], _layout, gestureHandler, nativeToolbarMenuItems, peerFileTransport, useLocale | apps/mobile/app/_layout.tsx, apps/mobile/app/companions/direct/[threadId].tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/HomeNativeStackHeader.tsx, apps/mobile/src/platform/chrome/HomeStackHeader.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/chrome/nativeToolbarMenuItems.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/HomeHeaderGlassButton.ios.tsx, apps/mobile/src/session/HomeHeaderGlassButton.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 226 | 76 | -| `mobile.companions.groups` | mobile | 伙伴群聊与分工 | app/companions/groups/[groupId].tsx | AdaptiveWindow, AgentErrorDetails, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, AuthorizationMessageCard, BlurBackdrop, BotGroupAvatars, BotGroupChatScreen, BotGroupComposer, BotGroupMenu, BotGroupMessageAttachments, BotGroupMessageText, BotGroupPlan, BotGroupProcess, BotGroupQuote, BotGroupRuntimeFailureNotice, BotGroupSettingsSheet, BotGroupSpeakerRow, BotGroupTaskCard, BotGroupTextAttachmentPreview, BotGroupThreadViews, BotGroupTopicImageShare, BotGroupWorkArea, CenteredScreen, ChatBackgroundBridge, ChatGroupSettingsSheet, ChatIdentityHeader, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, ChatPathChipSpan, ChatReplyIcon, ChatThreadSummary, CompanionCardButton, CompanionEntering, CompanionLearningFooter, CompanionMessageActions, CompanionMessageCard, CompanionNativeContent.ios, CompanionPresenceRing, CompanionSheet, CompanionSheet.ios, CompanionTaskResultCard, CompanionThreads, ComposerAttachmentTray, ComposerFrame, ComposerFrame.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, ContextSheetMediaViews, ConversationShareSvg, ConversationShareWebView, DeviceLinkContext, DirectLinkTransport, ErrorNotice, FileBrowserSegmentedControl, FileTypeIcon, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, GroupMaskControls, HomeHeaderGlassButton, HomeHeaderGlassButton.ios, HomeNativeStackHeader, HomeStackHeader, HtmlFileReader, ImageLightbox, IncomingShareBridge, InlineQuoteChip, InlineReferenceChip, InlineVideoPlayer, InputHandoffCard, InputHandoffDeviceCode, InputHandoffPictureStep, InputHandoffQrCode, InputHandoffRecordRow, InputHandoffSheet, InputHandoffSheet.ios, InputHandoffStepFields, InteractionPanel, MailLinkSpan, MarkdownBlockContent, MarkdownCodeCopyButton, MarkdownFileReader, MarkdownSelectableText, McpAppCard, McpAppViews, MessageActionSheet, MessageLoadingImage, MessageRenderer, MessageUsageCost, MobileBoundaryNotice, MobileComposerInputRow, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileMailHtmlPreview, MobileOutboxBridge, MobilePrimitives, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, NativeVideoPlayer, OutsideTap, PaperPlaneIcon, PendingSendBubble, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PluginCardActions, PluginCardRecipients, PluginInvocationHeader, PluginSetupNativeActions, PluginSetupNativeForm, PushNotificationsBridge, QuestionReferenceText, QuietSyncIndicator, QuoteCapsule, RecentMessageHistories, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, ResidentHomeList, RichAnswer, SelectionMenuText, SentInlineAtomBody, SessionHeaderNativeControls, SessionHeaderNativeControls.ios, SessionPaneDivider, SessionQuickSendContext, SettingsGroupRows, ShareImageNativeButton, ShareImageNativeButton.ios, ShareMessageCheckbox, ShareSelectAllButton, ShareSelectionBar, SheetModal, SheetSurface, SimpleStackHeader, StartupSplashOverlay, TaskTags, TeammateProcess, ThemeProvider, UsageCostDetails, UsageWidgetBridge, VideoDownloadButton, VideoSitePreview, [groupId], _layout, gestureHandler, listDisclosureTransition, mathWebView, mediaPlayerWebView, mermaidWebView, nativeToolbarMenuItems, peerFileTransport, useComposerPendingImagePreview, useLocale, voicePillWidthMotion | apps/mobile/app/_layout.tsx, apps/mobile/app/companions/groups/[groupId].tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ChatReplyIcon.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FileTypeIcon.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/PaperPlaneIcon.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/inputHandoff/InputHandoffCard.tsx, apps/mobile/src/inputHandoff/InputHandoffDeviceCode.tsx, apps/mobile/src/inputHandoff/InputHandoffPictureStep.tsx, apps/mobile/src/inputHandoff/InputHandoffQrCode.tsx, apps/mobile/src/inputHandoff/InputHandoffRecordRow.tsx, apps/mobile/src/inputHandoff/InputHandoffSheet.ios.tsx, apps/mobile/src/inputHandoff/InputHandoffSheet.tsx, apps/mobile/src/inputHandoff/InputHandoffStepFields.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/HomeNativeStackHeader.tsx, apps/mobile/src/platform/chrome/HomeStackHeader.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/chrome/nativeToolbarMenuItems.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/plugins/MobileMailHtmlPreview.tsx, apps/mobile/src/plugins/PluginCardActions.tsx, apps/mobile/src/plugins/PluginCardRecipients.tsx, apps/mobile/src/plugins/PluginSetupNativeActions.tsx, apps/mobile/src/plugins/PluginSetupNativeForm.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/AgentErrorDetails.tsx, apps/mobile/src/session/AuthorizationMessageCard.tsx, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/BotGroupAvatars.tsx, apps/mobile/src/session/BotGroupChatScreen.tsx, apps/mobile/src/session/BotGroupComposer.tsx, apps/mobile/src/session/BotGroupMenu.tsx, apps/mobile/src/session/BotGroupMessageAttachments.tsx, apps/mobile/src/session/BotGroupMessageText.tsx, apps/mobile/src/session/BotGroupPlan.tsx, apps/mobile/src/session/BotGroupProcess.tsx, apps/mobile/src/session/BotGroupQuote.tsx, apps/mobile/src/session/BotGroupRuntimeFailureNotice.tsx, apps/mobile/src/session/BotGroupSettingsSheet.tsx, apps/mobile/src/session/BotGroupSpeakerRow.tsx, apps/mobile/src/session/BotGroupTaskCard.tsx, apps/mobile/src/session/BotGroupTextAttachmentPreview.tsx, apps/mobile/src/session/BotGroupThreadViews.tsx, apps/mobile/src/session/BotGroupTopicImageShare.tsx, apps/mobile/src/session/BotGroupWorkArea.tsx, apps/mobile/src/session/ChatGroupSettingsSheet.tsx, apps/mobile/src/session/ChatIdentityHeader.tsx, apps/mobile/src/session/ChatPathChipSpan.tsx, apps/mobile/src/session/ChatThreadSummary.tsx, apps/mobile/src/session/CompanionCardButton.tsx, apps/mobile/src/session/CompanionEntering.tsx, apps/mobile/src/session/CompanionLearningFooter.tsx, apps/mobile/src/session/CompanionMessageActions.tsx, apps/mobile/src/session/CompanionMessageCard.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionPresenceRing.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/CompanionTaskResultCard.tsx, apps/mobile/src/session/CompanionThreads.tsx, apps/mobile/src/session/ComposerAttachmentTray.tsx, apps/mobile/src/session/ComposerFrame.ios.tsx, apps/mobile/src/session/ComposerFrame.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/ContextSheetMediaViews.tsx, apps/mobile/src/session/ConversationShareSvg.tsx, apps/mobile/src/session/ConversationShareWebView.tsx, apps/mobile/src/session/FileBrowserSegmentedControl.tsx, apps/mobile/src/session/GroupMaskControls.tsx, apps/mobile/src/session/HomeHeaderGlassButton.ios.tsx, apps/mobile/src/session/HomeHeaderGlassButton.tsx, apps/mobile/src/session/HtmlFileReader.tsx, apps/mobile/src/session/ImageLightbox.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/InlineQuoteChip.tsx, apps/mobile/src/session/InlineReferenceChip.tsx, apps/mobile/src/session/InlineVideoPlayer.tsx, apps/mobile/src/session/InteractionPanel.tsx, apps/mobile/src/session/MailLinkSpan.tsx, apps/mobile/src/session/MarkdownBlockContent.tsx, apps/mobile/src/session/MarkdownCodeCopyButton.tsx, apps/mobile/src/session/MarkdownFileReader.tsx, apps/mobile/src/session/MarkdownSelectableText.tsx, apps/mobile/src/session/McpAppCard.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MessageActionSheet.tsx, apps/mobile/src/session/MessageLoadingImage.tsx, apps/mobile/src/session/MessageRenderer.tsx, apps/mobile/src/session/MessageUsageCost.tsx, apps/mobile/src/session/MobileBoundaryNotice.tsx, apps/mobile/src/session/MobileComposerInputRow.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/NativeVideoPlayer.tsx, apps/mobile/src/session/PendingSendBubble.tsx, apps/mobile/src/session/PluginInvocationHeader.tsx, apps/mobile/src/session/QuestionReferenceText.tsx, apps/mobile/src/session/QuoteCapsule.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/RichAnswer.tsx, apps/mobile/src/session/SelectionMenuText.tsx, apps/mobile/src/session/SentInlineAtomBody.tsx, apps/mobile/src/session/SessionHeaderNativeControls.ios.tsx, apps/mobile/src/session/SessionHeaderNativeControls.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SessionQuickSendContext.tsx, apps/mobile/src/session/SettingsGroupRows.tsx, apps/mobile/src/session/ShareImageNativeButton.ios.tsx, apps/mobile/src/session/ShareImageNativeButton.tsx, apps/mobile/src/session/ShareMessageCheckbox.tsx, apps/mobile/src/session/ShareSelectAllButton.tsx, apps/mobile/src/session/ShareSelectionBar.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/session/TaskTags.tsx, apps/mobile/src/session/TeammateProcess.tsx, apps/mobile/src/session/UsageCostDetails.tsx, apps/mobile/src/session/VideoDownloadButton.tsx, apps/mobile/src/session/VideoSitePreview.tsx, apps/mobile/src/session/conversationShareWebViewHtml.ts, apps/mobile/src/session/listDisclosureTransition.tsx, apps/mobile/src/session/mathWebView.tsx, apps/mobile/src/session/mathWebViewHtml.ts, apps/mobile/src/session/mediaPlayerWebView.tsx, apps/mobile/src/session/mediaPlayerWebViewHtml.ts, apps/mobile/src/session/mermaidWebView.tsx, apps/mobile/src/session/mermaidWebViewHtml.ts, apps/mobile/src/session/selectableMarkdownHtml.ts, apps/mobile/src/session/useComposerPendingImagePreview.tsx, apps/mobile/src/session/voicePillWidthMotion.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 266 | 270 | +| `mobile.companions.groups` | mobile | 伙伴群聊与分工 | app/companions/groups/[groupId].tsx | AdaptiveWindow, AgentErrorDetails, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, AuthorizationMessageCard, BlurBackdrop, BotGroupAvatars, BotGroupChatScreen, BotGroupComposer, BotGroupMenu, BotGroupMessageAttachments, BotGroupMessageText, BotGroupPlan, BotGroupProcess, BotGroupQuote, BotGroupRuntimeFailureNotice, BotGroupSettingsSheet, BotGroupSpeakerRow, BotGroupTaskCard, BotGroupTextAttachmentPreview, BotGroupThreadViews, BotGroupTopicImageShare, BotGroupWorkArea, CenteredScreen, ChatBackgroundBridge, ChatGroupSettingsSheet, ChatIdentityHeader, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, ChatPathChipSpan, ChatReplyIcon, ChatThreadSummary, CompanionCardButton, CompanionEntering, CompanionLearningFooter, CompanionMessageActions, CompanionMessageCard, CompanionNativeContent.ios, CompanionPresenceRing, CompanionSheet, CompanionSheet.ios, CompanionTaskResultCard, CompanionThreads, ComposerAttachmentTray, ComposerFrame, ComposerFrame.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, ContextSheetMediaViews, ConversationShareSvg, ConversationShareWebView, DeviceLinkContext, DiagramDocumentPreview, DirectLinkTransport, ErrorNotice, FileBrowserSegmentedControl, FileTypeIcon, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, GroupMaskControls, HomeHeaderGlassButton, HomeHeaderGlassButton.ios, HomeNativeStackHeader, HomeStackHeader, HtmlFileReader, ImageLightbox, IncomingShareBridge, InlineQuoteChip, InlineReferenceChip, InlineVideoPlayer, InputHandoffCard, InputHandoffDeviceCode, InputHandoffPictureStep, InputHandoffQrCode, InputHandoffRecordRow, InputHandoffSheet, InputHandoffSheet.ios, InputHandoffStepFields, InteractionPanel, MailLinkSpan, MarkdownBlockContent, MarkdownCodeCopyButton, MarkdownFileReader, MarkdownSelectableText, McpAppCard, McpAppViews, MessageActionSheet, MessageLoadingImage, MessageRenderer, MessageUsageCost, MobileBoundaryNotice, MobileComposerInputRow, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileMailHtmlPreview, MobileOutboxBridge, MobilePrimitives, ModelDocumentPreview, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, NativeVideoPlayer, OutsideTap, PaperPlaneIcon, PdfDocumentPreview, PendingSendBubble, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PluginCardActions, PluginCardRecipients, PluginInvocationHeader, PluginSetupNativeActions, PluginSetupNativeForm, PreviewChrome, PushNotificationsBridge, QuestionReferenceText, QuietSyncIndicator, QuoteCapsule, RecentMessageHistories, RemoteAttachmentPreview, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, ResidentHomeList, RichAnswer, SelectionMenuText, SentInlineAtomBody, SessionHeaderNativeControls, SessionHeaderNativeControls.ios, SessionPaneDivider, SessionQuickSendContext, SettingsGroupRows, ShareImageNativeButton, ShareImageNativeButton.ios, ShareMessageCheckbox, ShareSelectAllButton, ShareSelectionBar, SheetModal, SheetSurface, SimpleStackHeader, StartupSplashOverlay, TaskTags, TeammateProcess, TextDocumentPreview, ThemeProvider, UsageCostDetails, UsageWidgetBridge, VideoDownloadButton, VideoSitePreview, [groupId], _layout, gestureHandler, listDisclosureTransition, mathWebView, mediaPlayerWebView, mermaidWebView, nativeToolbarMenuItems, peerFileTransport, useComposerPendingImagePreview, useLocale, voicePillWidthMotion | apps/mobile/app/_layout.tsx, apps/mobile/app/companions/groups/[groupId].tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ChatReplyIcon.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FileTypeIcon.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/PaperPlaneIcon.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/inputHandoff/InputHandoffCard.tsx, apps/mobile/src/inputHandoff/InputHandoffDeviceCode.tsx, apps/mobile/src/inputHandoff/InputHandoffPictureStep.tsx, apps/mobile/src/inputHandoff/InputHandoffQrCode.tsx, apps/mobile/src/inputHandoff/InputHandoffRecordRow.tsx, apps/mobile/src/inputHandoff/InputHandoffSheet.ios.tsx, apps/mobile/src/inputHandoff/InputHandoffSheet.tsx, apps/mobile/src/inputHandoff/InputHandoffStepFields.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/HomeNativeStackHeader.tsx, apps/mobile/src/platform/chrome/HomeStackHeader.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/chrome/nativeToolbarMenuItems.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/plugins/MobileMailHtmlPreview.tsx, apps/mobile/src/plugins/PluginCardActions.tsx, apps/mobile/src/plugins/PluginCardRecipients.tsx, apps/mobile/src/plugins/PluginSetupNativeActions.tsx, apps/mobile/src/plugins/PluginSetupNativeForm.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/AgentErrorDetails.tsx, apps/mobile/src/session/AuthorizationMessageCard.tsx, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/BotGroupAvatars.tsx, apps/mobile/src/session/BotGroupChatScreen.tsx, apps/mobile/src/session/BotGroupComposer.tsx, apps/mobile/src/session/BotGroupMenu.tsx, apps/mobile/src/session/BotGroupMessageAttachments.tsx, apps/mobile/src/session/BotGroupMessageText.tsx, apps/mobile/src/session/BotGroupPlan.tsx, apps/mobile/src/session/BotGroupProcess.tsx, apps/mobile/src/session/BotGroupQuote.tsx, apps/mobile/src/session/BotGroupRuntimeFailureNotice.tsx, apps/mobile/src/session/BotGroupSettingsSheet.tsx, apps/mobile/src/session/BotGroupSpeakerRow.tsx, apps/mobile/src/session/BotGroupTaskCard.tsx, apps/mobile/src/session/BotGroupTextAttachmentPreview.tsx, apps/mobile/src/session/BotGroupThreadViews.tsx, apps/mobile/src/session/BotGroupTopicImageShare.tsx, apps/mobile/src/session/BotGroupWorkArea.tsx, apps/mobile/src/session/ChatGroupSettingsSheet.tsx, apps/mobile/src/session/ChatIdentityHeader.tsx, apps/mobile/src/session/ChatPathChipSpan.tsx, apps/mobile/src/session/ChatThreadSummary.tsx, apps/mobile/src/session/CompanionCardButton.tsx, apps/mobile/src/session/CompanionEntering.tsx, apps/mobile/src/session/CompanionLearningFooter.tsx, apps/mobile/src/session/CompanionMessageActions.tsx, apps/mobile/src/session/CompanionMessageCard.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionPresenceRing.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/CompanionTaskResultCard.tsx, apps/mobile/src/session/CompanionThreads.tsx, apps/mobile/src/session/ComposerAttachmentTray.tsx, apps/mobile/src/session/ComposerFrame.ios.tsx, apps/mobile/src/session/ComposerFrame.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/ContextSheetMediaViews.tsx, apps/mobile/src/session/ConversationShareSvg.tsx, apps/mobile/src/session/ConversationShareWebView.tsx, apps/mobile/src/session/DiagramDocumentPreview.tsx, apps/mobile/src/session/FileBrowserSegmentedControl.tsx, apps/mobile/src/session/GroupMaskControls.tsx, apps/mobile/src/session/HomeHeaderGlassButton.ios.tsx, apps/mobile/src/session/HomeHeaderGlassButton.tsx, apps/mobile/src/session/HtmlFileReader.tsx, apps/mobile/src/session/ImageLightbox.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/InlineQuoteChip.tsx, apps/mobile/src/session/InlineReferenceChip.tsx, apps/mobile/src/session/InlineVideoPlayer.tsx, apps/mobile/src/session/InteractionPanel.tsx, apps/mobile/src/session/MailLinkSpan.tsx, apps/mobile/src/session/MarkdownBlockContent.tsx, apps/mobile/src/session/MarkdownCodeCopyButton.tsx, apps/mobile/src/session/MarkdownFileReader.tsx, apps/mobile/src/session/MarkdownSelectableText.tsx, apps/mobile/src/session/McpAppCard.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MessageActionSheet.tsx, apps/mobile/src/session/MessageLoadingImage.tsx, apps/mobile/src/session/MessageRenderer.tsx, apps/mobile/src/session/MessageUsageCost.tsx, apps/mobile/src/session/MobileBoundaryNotice.tsx, apps/mobile/src/session/MobileComposerInputRow.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/ModelDocumentPreview.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/NativeVideoPlayer.tsx, apps/mobile/src/session/PdfDocumentPreview.tsx, apps/mobile/src/session/PendingSendBubble.tsx, apps/mobile/src/session/PluginInvocationHeader.tsx, apps/mobile/src/session/PreviewChrome.tsx, apps/mobile/src/session/QuestionReferenceText.tsx, apps/mobile/src/session/QuoteCapsule.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteAttachmentPreview.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/RichAnswer.tsx, apps/mobile/src/session/SelectionMenuText.tsx, apps/mobile/src/session/SentInlineAtomBody.tsx, apps/mobile/src/session/SessionHeaderNativeControls.ios.tsx, apps/mobile/src/session/SessionHeaderNativeControls.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SessionQuickSendContext.tsx, apps/mobile/src/session/SettingsGroupRows.tsx, apps/mobile/src/session/ShareImageNativeButton.ios.tsx, apps/mobile/src/session/ShareImageNativeButton.tsx, apps/mobile/src/session/ShareMessageCheckbox.tsx, apps/mobile/src/session/ShareSelectAllButton.tsx, apps/mobile/src/session/ShareSelectionBar.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/session/TaskTags.tsx, apps/mobile/src/session/TeammateProcess.tsx, apps/mobile/src/session/TextDocumentPreview.tsx, apps/mobile/src/session/UsageCostDetails.tsx, apps/mobile/src/session/VideoDownloadButton.tsx, apps/mobile/src/session/VideoSitePreview.tsx, apps/mobile/src/session/conversationShareWebViewHtml.ts, apps/mobile/src/session/listDisclosureTransition.tsx, apps/mobile/src/session/mathWebView.tsx, apps/mobile/src/session/mathWebViewHtml.ts, apps/mobile/src/session/mediaPlayerWebView.tsx, apps/mobile/src/session/mediaPlayerWebViewHtml.ts, apps/mobile/src/session/mermaidWebView.tsx, apps/mobile/src/session/mermaidWebViewHtml.ts, apps/mobile/src/session/pdfWebViewHtml.ts, apps/mobile/src/session/selectableMarkdownHtml.ts, apps/mobile/src/session/useComposerPendingImagePreview.tsx, apps/mobile/src/session/voicePillWidthMotion.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 235 | 269 | | `mobile.device-management` | mobile | 设备管理 | app/devices/manage.tsx / app/devices/manage/[deviceId].tsx | AdaptiveWindow, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, BlurBackdrop, CenteredScreen, ChatBackgroundBridge, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, CompanionNativeContent.ios, CompanionSheet, CompanionSheet.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, DeviceInformationFields, DeviceInformationFields.ios, DeviceLinkContext, DeviceManagementDialogs, DeviceManagementList, DeviceManagementList.ios, DirectLinkTransport, ErrorNotice, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, HomeHeaderGlassButton, HomeHeaderGlassButton.ios, HomeNativeStackHeader, HomeStackHeader, IncomingShareBridge, McpAppViews, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileOutboxBridge, MobilePrimitives, ModalContentArea, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, OutsideTap, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PushNotificationsBridge, QuietSyncIndicator, RecentMessageHistories, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, RenameDeviceModal, ResidentHomeList, SessionPaneDivider, SettingsGroupRows, SheetModal, SheetSurface, SimpleStackHeader, StartupSplashOverlay, ThemeProvider, UsageWidgetBridge, [deviceId], _layout, gestureHandler, listDisclosureTransition, manage, nativeToolbarMenuItems, peerFileTransport, useLocale | apps/mobile/app/_layout.tsx, apps/mobile/app/devices/manage.tsx, apps/mobile/app/devices/manage/[deviceId].tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceInformationFields.ios.tsx, apps/mobile/src/device-link/DeviceInformationFields.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DeviceManagementDialogs.tsx, apps/mobile/src/device-link/DeviceManagementList.ios.tsx, apps/mobile/src/device-link/DeviceManagementList.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/ModalContentArea.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/HomeNativeStackHeader.tsx, apps/mobile/src/platform/chrome/HomeStackHeader.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/chrome/nativeToolbarMenuItems.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/HomeHeaderGlassButton.ios.tsx, apps/mobile/src/session/HomeHeaderGlassButton.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/RenameDeviceModal.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SettingsGroupRows.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/session/listDisclosureTransition.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 226 | 78 | | `mobile.devices` | mobile | 主机详情 | app/devices/[deviceId].tsx | AccountSwitcherSheet, AdaptiveWindow, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, AutomationTimerIcon, BlurBackdrop, BotGroupAvatars, BotGroupCreateSheet, BotGroupList, CenteredScreen, ChatBackgroundBridge, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, CompanionChannelTasks, CompanionChoice, CompanionCreateNativeView, CompanionCreateNativeView.ios, CompanionImportSheet, CompanionListRow, CompanionMemoryPage, CompanionMemoryPage.ios, CompanionModelChain, CompanionNativeContent.ios, CompanionPortraitPicker, CompanionPresenceRing, CompanionProfileArtifacts, CompanionProfileNativeView, CompanionProfileNativeView.ios, CompanionProfileSheet, CompanionSettingsRow, CompanionSettingsRow.ios, CompanionSheet, CompanionSheet.ios, ComposerMorphSource, ComposerMorphSource.ios, ComposerNativeRow, ComposerNativeRow.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, ConversationSearchFilterSheet, DeviceLinkContext, DirectLinkTransport, ErrorNotice, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, FormSheet, FormSheet.ios, HomeChromeDrawer, HomeChromeFrost, HomeDisplayMenu, HomeDisplayMenu.ios, HomeGlassMenuPanel, HomeHeaderGlassButton, HomeHeaderGlassButton.ios, HomeListVisuals, HomeNativeStackHeader, HomeNewTaskButton, HomeProjectMachineLabel, HomeRosterContext, HomeSearchBar, HomeSessionInfoMeta, HomeStackHeader, HomeSurface, HomeTeammatesSection, IncomingShareBridge, McpAppViews, MobileAgentMark, MobileAgentSwitcher, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileModelPickerList, MobileModelPickerList.ios, MobileOutboxBridge, MobilePermissionPickerList, MobilePermissionPickerList.ios, MobilePrimitives, MobileProviderMark, MobileUserAvatar, MobileVendorIcon, ModalContentArea, ModelOptionsSheetView, ModelOptionsSheetView.ios, ModelPickerNativeHeader, ModelPickerNativeHeader.ios, ModelPickerSheet, ModelQuotaBar, ModelSpeedIcon, MountOnFirstOpen, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, OutsideTap, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PluginMenuUnreadDot, PushNotificationsBridge, QuietSyncIndicator, RecentMessageHistories, RemoteAccessGuide, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, RemoteSourceMark, RemoteTaskSuggestions, RenameSessionModal, ResidentHomeList, SessionActionSheet, SessionActionSheetFrame, SessionActionSheetFrame.android, SessionImportSheet, SessionOptionsExpoSheet, SessionPaneDivider, SessionRightSpinner, SheetModal, SheetSurface, SimpleStackHeader, StartupSplashOverlay, SwipeableProjectHeader, SwipeableSessionRow, SwipeableSessionRow.ios, TaskFollowMark, TaskTags, TaskTagsNative, TaskTagsNative.ios, TeammateCreateController, TeammateGenerationLabel, TeammateList, TeammateStatusDot, ThemeProvider, UnifiedModelPickerSheet, UnifiedModelPickerView, UnifiedModelPickerView.ios, UsageWidgetBridge, WorkingStatusText, [deviceId], _layout, gestureHandler, listDisclosureTransition, nativeToolbarMenuItems, peerFileTransport, useLocale | apps/mobile/app/_layout.tsx, apps/mobile/app/devices/[deviceId].tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileAgentMark.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/MobileUserAvatar.tsx, apps/mobile/src/components/MobileVendorIcon.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteAccessGuide.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/ModalContentArea.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/HomeNativeStackHeader.tsx, apps/mobile/src/platform/chrome/HomeStackHeader.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/chrome/nativeToolbarMenuItems.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/plugins/PluginMenuUnreadDot.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/AccountSwitcherSheet.tsx, apps/mobile/src/session/AutomationTimerIcon.tsx, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/BotGroupAvatars.tsx, apps/mobile/src/session/BotGroupCreateSheet.tsx, apps/mobile/src/session/BotGroupList.tsx, apps/mobile/src/session/CompanionChannelTasks.tsx, apps/mobile/src/session/CompanionChoice.tsx, apps/mobile/src/session/CompanionCreateNativeView.ios.tsx, apps/mobile/src/session/CompanionCreateNativeView.tsx, apps/mobile/src/session/CompanionImportSheet.tsx, apps/mobile/src/session/CompanionListRow.tsx, apps/mobile/src/session/CompanionMemoryPage.ios.tsx, apps/mobile/src/session/CompanionMemoryPage.tsx, apps/mobile/src/session/CompanionModelChain.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionPortraitPicker.tsx, apps/mobile/src/session/CompanionPresenceRing.tsx, apps/mobile/src/session/CompanionProfileArtifacts.tsx, apps/mobile/src/session/CompanionProfileNativeView.ios.tsx, apps/mobile/src/session/CompanionProfileNativeView.tsx, apps/mobile/src/session/CompanionProfileSheet.tsx, apps/mobile/src/session/CompanionSettingsRow.ios.tsx, apps/mobile/src/session/CompanionSettingsRow.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/ComposerMorphSource.ios.tsx, apps/mobile/src/session/ComposerMorphSource.tsx, apps/mobile/src/session/ComposerNativeRow.ios.tsx, apps/mobile/src/session/ComposerNativeRow.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/ConversationSearchFilterSheet.tsx, apps/mobile/src/session/FormSheet.ios.tsx, apps/mobile/src/session/FormSheet.tsx, apps/mobile/src/session/HomeChromeDrawer.tsx, apps/mobile/src/session/HomeChromeFrost.tsx, apps/mobile/src/session/HomeDisplayMenu.ios.tsx, apps/mobile/src/session/HomeDisplayMenu.tsx, apps/mobile/src/session/HomeGlassMenuPanel.tsx, apps/mobile/src/session/HomeHeaderGlassButton.ios.tsx, apps/mobile/src/session/HomeHeaderGlassButton.tsx, apps/mobile/src/session/HomeListVisuals.tsx, apps/mobile/src/session/HomeNewTaskButton.tsx, apps/mobile/src/session/HomeProjectMachineLabel.tsx, apps/mobile/src/session/HomeRosterContext.tsx, apps/mobile/src/session/HomeSearchBar.tsx, apps/mobile/src/session/HomeSessionInfoMeta.tsx, apps/mobile/src/session/HomeSurface.tsx, apps/mobile/src/session/HomeTeammatesSection.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MobileAgentSwitcher.tsx, apps/mobile/src/session/MobileModelPickerList.ios.tsx, apps/mobile/src/session/MobileModelPickerList.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/MobilePermissionPickerList.ios.tsx, apps/mobile/src/session/MobilePermissionPickerList.tsx, apps/mobile/src/session/MobileProviderMark.tsx, apps/mobile/src/session/ModelOptionsSheetView.ios.tsx, apps/mobile/src/session/ModelOptionsSheetView.tsx, apps/mobile/src/session/ModelPickerNativeHeader.ios.tsx, apps/mobile/src/session/ModelPickerNativeHeader.tsx, apps/mobile/src/session/ModelPickerSheet.tsx, apps/mobile/src/session/ModelQuotaBar.tsx, apps/mobile/src/session/ModelSpeedIcon.tsx, apps/mobile/src/session/MountOnFirstOpen.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/RemoteSourceMark.tsx, apps/mobile/src/session/RemoteTaskSuggestions.tsx, apps/mobile/src/session/RenameSessionModal.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/SessionActionSheet.tsx, apps/mobile/src/session/SessionActionSheetFrame.android.tsx, apps/mobile/src/session/SessionActionSheetFrame.tsx, apps/mobile/src/session/SessionImportSheet.tsx, apps/mobile/src/session/SessionOptionsExpoSheet.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SessionRightSpinner.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/session/SwipeableProjectHeader.tsx, apps/mobile/src/session/SwipeableSessionRow.ios.tsx, apps/mobile/src/session/SwipeableSessionRow.tsx, apps/mobile/src/session/TaskFollowMark.tsx, apps/mobile/src/session/TaskTags.tsx, apps/mobile/src/session/TaskTagsNative.ios.tsx, apps/mobile/src/session/TaskTagsNative.tsx, apps/mobile/src/session/TeammateCreateController.tsx, apps/mobile/src/session/TeammateGenerationLabel.tsx, apps/mobile/src/session/TeammateList.tsx, apps/mobile/src/session/TeammateStatusDot.tsx, apps/mobile/src/session/UnifiedModelPickerSheet.tsx, apps/mobile/src/session/UnifiedModelPickerView.ios.tsx, apps/mobile/src/session/UnifiedModelPickerView.tsx, apps/mobile/src/session/WorkingStatusText.tsx, apps/mobile/src/session/listDisclosureTransition.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 226 | 168 | -| `mobile.files` | mobile | 任务文件与预览 | app/files/[sessionId].tsx / app/files/preview/[sessionId].tsx | AdaptiveWindow, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, BlurBackdrop, CenteredScreen, ChatBackgroundBridge, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, CindyMakeSessionGate, CompanionNativeContent.ios, CompanionSheet, CompanionSheet.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, DeviceLinkContext, DirectLinkTransport, ErrorNotice, FileBrowserSegmentedControl, FileTransferBusyNotice, FileTypeIcon, FileTypeTile, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, HtmlBrowserChrome, HtmlBrowserChrome.ios, HtmlFileReader, HtmlPreviewLoading, HtmlWebsiteBrowser, ImageLightbox, IncomingShareBridge, MarkdownFileReader, McpAppViews, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileOutboxBridge, MobilePrimitives, ModalContentArea, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, NativeVideoPlayer, OutsideTap, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PushNotificationsBridge, QuietSyncIndicator, RecentMessageHistories, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, ResidentHomeList, SessionEntryRecovery, SessionPaneDivider, SheetModal, SheetSurface, SimpleStackHeader, StartupSplashOverlay, SystemNavigationBack, ThemeProvider, UsageWidgetBridge, VideoDownloadButton, [sessionId], _layout, gestureHandler, mediaPlayerWebView, peerFileTransport, useLocale | apps/mobile/app/_layout.tsx, apps/mobile/app/files/[sessionId].tsx, apps/mobile/app/files/preview/[sessionId].tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FileTypeIcon.tsx, apps/mobile/src/components/FileTypeTile.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/ModalContentArea.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/chrome/SystemNavigationBack.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/CindyMakeSessionGate.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/FileBrowserSegmentedControl.tsx, apps/mobile/src/session/FileTransferBusyNotice.tsx, apps/mobile/src/session/HtmlBrowserChrome.ios.tsx, apps/mobile/src/session/HtmlBrowserChrome.tsx, apps/mobile/src/session/HtmlFileReader.tsx, apps/mobile/src/session/HtmlPreviewLoading.tsx, apps/mobile/src/session/HtmlWebsiteBrowser.tsx, apps/mobile/src/session/ImageLightbox.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/MarkdownFileReader.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/NativeVideoPlayer.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/SessionEntryRecovery.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/session/VideoDownloadButton.tsx, apps/mobile/src/session/mathWebViewHtml.ts, apps/mobile/src/session/mediaPlayerWebView.tsx, apps/mobile/src/session/mediaPlayerWebViewHtml.ts, apps/mobile/src/session/selectableMarkdownHtml.ts, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 265 | 102 | +| `mobile.files` | mobile | 任务文件与预览 | app/files/[sessionId].tsx / app/files/preview/[sessionId].tsx | AdaptiveWindow, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, BlurBackdrop, CenteredScreen, ChatBackgroundBridge, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, CindyMakeSessionGate, CompanionNativeContent.ios, CompanionSheet, CompanionSheet.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, DeviceLinkContext, DiagramDocumentPreview, DirectLinkTransport, DocumentPreviewChrome, ErrorNotice, FileBrowserSegmentedControl, FileTransferBusyNotice, FileTypeIcon, FileTypeTile, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, HtmlBrowserChrome, HtmlBrowserChrome.ios, HtmlFileReader, HtmlPreviewLoading, HtmlWebsiteBrowser, ImageLightbox, IncomingShareBridge, MarkdownFileReader, McpAppViews, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileOutboxBridge, MobilePrimitives, ModalContentArea, ModelDocumentPreview, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, NativeVideoPlayer, OutsideTap, PdfDocumentPreview, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PreviewChrome, PushNotificationsBridge, QuietSyncIndicator, RecentMessageHistories, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, ResidentHomeList, SessionEntryRecovery, SessionPaneDivider, SheetModal, SheetSurface, SimpleStackHeader, StartupSplashOverlay, SystemNavigationBack, TextDocumentPreview, ThemeProvider, UsageWidgetBridge, VideoDownloadButton, [sessionId], _layout, gestureHandler, mediaPlayerWebView, mermaidWebView, peerFileTransport, useLocale | apps/mobile/app/_layout.tsx, apps/mobile/app/files/[sessionId].tsx, apps/mobile/app/files/preview/[sessionId].tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FileTypeIcon.tsx, apps/mobile/src/components/FileTypeTile.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/ModalContentArea.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/chrome/SystemNavigationBack.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/CindyMakeSessionGate.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/DiagramDocumentPreview.tsx, apps/mobile/src/session/DocumentPreviewChrome.tsx, apps/mobile/src/session/FileBrowserSegmentedControl.tsx, apps/mobile/src/session/FileTransferBusyNotice.tsx, apps/mobile/src/session/HtmlBrowserChrome.ios.tsx, apps/mobile/src/session/HtmlBrowserChrome.tsx, apps/mobile/src/session/HtmlFileReader.tsx, apps/mobile/src/session/HtmlPreviewLoading.tsx, apps/mobile/src/session/HtmlWebsiteBrowser.tsx, apps/mobile/src/session/ImageLightbox.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/MarkdownFileReader.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/ModelDocumentPreview.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/NativeVideoPlayer.tsx, apps/mobile/src/session/PdfDocumentPreview.tsx, apps/mobile/src/session/PreviewChrome.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/SessionEntryRecovery.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/session/TextDocumentPreview.tsx, apps/mobile/src/session/VideoDownloadButton.tsx, apps/mobile/src/session/mathWebViewHtml.ts, apps/mobile/src/session/mediaPlayerWebView.tsx, apps/mobile/src/session/mediaPlayerWebViewHtml.ts, apps/mobile/src/session/mermaidWebView.tsx, apps/mobile/src/session/mermaidWebViewHtml.ts, apps/mobile/src/session/pdfWebViewHtml.ts, apps/mobile/src/session/selectableMarkdownHtml.ts, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 234 | 102 | | `mobile.home` | mobile | 主机与资源首页 | app/index.tsx / app/devices/index.tsx | AccountSwitcherSheet, AdaptiveWindow, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, AutomationTimerIcon, BlurBackdrop, BotGroupAvatars, BotGroupCreateSheet, BotGroupList, CenteredScreen, ChatBackgroundBridge, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, CompanionChannelTasks, CompanionChoice, CompanionCreateNativeView, CompanionCreateNativeView.ios, CompanionImportSheet, CompanionListRow, CompanionMemoryPage, CompanionMemoryPage.ios, CompanionModelChain, CompanionNativeContent.ios, CompanionPortraitPicker, CompanionPresenceRing, CompanionProfileArtifacts, CompanionProfileNativeView, CompanionProfileNativeView.ios, CompanionProfileSheet, CompanionSettingsRow, CompanionSettingsRow.ios, CompanionSheet, CompanionSheet.ios, ComposerMorphSource, ComposerMorphSource.ios, ComposerNativeRow, ComposerNativeRow.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, ConversationSearchFilterSheet, DeviceLinkContext, DirectLinkTransport, ErrorNotice, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, FormSheet, FormSheet.ios, HomeChromeDrawer, HomeChromeFrost, HomeDisplayMenu, HomeDisplayMenu.ios, HomeGlassMenuPanel, HomeHeaderGlassButton, HomeHeaderGlassButton.ios, HomeListVisuals, HomeNativeStackHeader, HomeNewTaskButton, HomeProjectMachineLabel, HomeRosterContext, HomeSearchBar, HomeSessionInfoMeta, HomeStackHeader, HomeSurface, HomeTeammatesSection, IncomingShareBridge, McpAppViews, MobileAgentMark, MobileAgentSwitcher, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileModelPickerList, MobileModelPickerList.ios, MobileOutboxBridge, MobilePermissionPickerList, MobilePermissionPickerList.ios, MobilePrimitives, MobileProviderMark, MobileUserAvatar, MobileVendorIcon, ModalContentArea, ModelOptionsSheetView, ModelOptionsSheetView.ios, ModelPickerNativeHeader, ModelPickerNativeHeader.ios, ModelPickerSheet, ModelQuotaBar, ModelSpeedIcon, MountOnFirstOpen, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, OutsideTap, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PluginMenuUnreadDot, PushNotificationsBridge, QuietSyncIndicator, RecentMessageHistories, RemoteAccessGuide, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, RemoteSourceMark, RemoteTaskSuggestions, RenameSessionModal, ResidentHomeList, SessionActionSheet, SessionActionSheetFrame, SessionActionSheetFrame.android, SessionHeaderNativeControls, SessionHeaderNativeControls.ios, SessionOptionsExpoSheet, SessionPaneDivider, SessionRightSpinner, SheetModal, SheetSurface, SimpleStackHeader, StartupSplashOverlay, SwipeableProjectHeader, SwipeableSessionRow, SwipeableSessionRow.ios, TaskFollowMark, TaskTags, TaskTagsNative, TaskTagsNative.ios, TeammateCreateController, TeammateGenerationLabel, TeammateList, TeammateStatusDot, ThemeProvider, UnifiedModelPickerSheet, UnifiedModelPickerView, UnifiedModelPickerView.ios, UsageWidgetBridge, WorkingStatusText, _layout, gestureHandler, index, listDisclosureTransition, nativeToolbarMenuItems, peerFileTransport, useLocale | apps/mobile/app/_layout.tsx, apps/mobile/app/devices/index.tsx, apps/mobile/app/index.tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileAgentMark.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/MobileUserAvatar.tsx, apps/mobile/src/components/MobileVendorIcon.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteAccessGuide.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/ModalContentArea.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/HomeNativeStackHeader.tsx, apps/mobile/src/platform/chrome/HomeStackHeader.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/chrome/nativeToolbarMenuItems.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/plugins/PluginMenuUnreadDot.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/AccountSwitcherSheet.tsx, apps/mobile/src/session/AutomationTimerIcon.tsx, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/BotGroupAvatars.tsx, apps/mobile/src/session/BotGroupCreateSheet.tsx, apps/mobile/src/session/BotGroupList.tsx, apps/mobile/src/session/CompanionChannelTasks.tsx, apps/mobile/src/session/CompanionChoice.tsx, apps/mobile/src/session/CompanionCreateNativeView.ios.tsx, apps/mobile/src/session/CompanionCreateNativeView.tsx, apps/mobile/src/session/CompanionImportSheet.tsx, apps/mobile/src/session/CompanionListRow.tsx, apps/mobile/src/session/CompanionMemoryPage.ios.tsx, apps/mobile/src/session/CompanionMemoryPage.tsx, apps/mobile/src/session/CompanionModelChain.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionPortraitPicker.tsx, apps/mobile/src/session/CompanionPresenceRing.tsx, apps/mobile/src/session/CompanionProfileArtifacts.tsx, apps/mobile/src/session/CompanionProfileNativeView.ios.tsx, apps/mobile/src/session/CompanionProfileNativeView.tsx, apps/mobile/src/session/CompanionProfileSheet.tsx, apps/mobile/src/session/CompanionSettingsRow.ios.tsx, apps/mobile/src/session/CompanionSettingsRow.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/ComposerMorphSource.ios.tsx, apps/mobile/src/session/ComposerMorphSource.tsx, apps/mobile/src/session/ComposerNativeRow.ios.tsx, apps/mobile/src/session/ComposerNativeRow.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/ConversationSearchFilterSheet.tsx, apps/mobile/src/session/FormSheet.ios.tsx, apps/mobile/src/session/FormSheet.tsx, apps/mobile/src/session/HomeChromeDrawer.tsx, apps/mobile/src/session/HomeChromeFrost.tsx, apps/mobile/src/session/HomeDisplayMenu.ios.tsx, apps/mobile/src/session/HomeDisplayMenu.tsx, apps/mobile/src/session/HomeGlassMenuPanel.tsx, apps/mobile/src/session/HomeHeaderGlassButton.ios.tsx, apps/mobile/src/session/HomeHeaderGlassButton.tsx, apps/mobile/src/session/HomeListVisuals.tsx, apps/mobile/src/session/HomeNewTaskButton.tsx, apps/mobile/src/session/HomeProjectMachineLabel.tsx, apps/mobile/src/session/HomeRosterContext.tsx, apps/mobile/src/session/HomeSearchBar.tsx, apps/mobile/src/session/HomeSessionInfoMeta.tsx, apps/mobile/src/session/HomeSurface.tsx, apps/mobile/src/session/HomeTeammatesSection.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MobileAgentSwitcher.tsx, apps/mobile/src/session/MobileModelPickerList.ios.tsx, apps/mobile/src/session/MobileModelPickerList.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/MobilePermissionPickerList.ios.tsx, apps/mobile/src/session/MobilePermissionPickerList.tsx, apps/mobile/src/session/MobileProviderMark.tsx, apps/mobile/src/session/ModelOptionsSheetView.ios.tsx, apps/mobile/src/session/ModelOptionsSheetView.tsx, apps/mobile/src/session/ModelPickerNativeHeader.ios.tsx, apps/mobile/src/session/ModelPickerNativeHeader.tsx, apps/mobile/src/session/ModelPickerSheet.tsx, apps/mobile/src/session/ModelQuotaBar.tsx, apps/mobile/src/session/ModelSpeedIcon.tsx, apps/mobile/src/session/MountOnFirstOpen.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/RemoteSourceMark.tsx, apps/mobile/src/session/RemoteTaskSuggestions.tsx, apps/mobile/src/session/RenameSessionModal.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/SessionActionSheet.tsx, apps/mobile/src/session/SessionActionSheetFrame.android.tsx, apps/mobile/src/session/SessionActionSheetFrame.tsx, apps/mobile/src/session/SessionHeaderNativeControls.ios.tsx, apps/mobile/src/session/SessionHeaderNativeControls.tsx, apps/mobile/src/session/SessionOptionsExpoSheet.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SessionRightSpinner.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/session/SwipeableProjectHeader.tsx, apps/mobile/src/session/SwipeableSessionRow.ios.tsx, apps/mobile/src/session/SwipeableSessionRow.tsx, apps/mobile/src/session/TaskFollowMark.tsx, apps/mobile/src/session/TaskTags.tsx, apps/mobile/src/session/TaskTagsNative.ios.tsx, apps/mobile/src/session/TaskTagsNative.tsx, apps/mobile/src/session/TeammateCreateController.tsx, apps/mobile/src/session/TeammateGenerationLabel.tsx, apps/mobile/src/session/TeammateList.tsx, apps/mobile/src/session/TeammateStatusDot.tsx, apps/mobile/src/session/UnifiedModelPickerSheet.tsx, apps/mobile/src/session/UnifiedModelPickerView.ios.tsx, apps/mobile/src/session/UnifiedModelPickerView.tsx, apps/mobile/src/session/WorkingStatusText.tsx, apps/mobile/src/session/listDisclosureTransition.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 226 | 162 | | `mobile.mail` | mobile | 邮件阅读与附件预览 | app/mail-preview.tsx | AdaptiveWindow, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, BlurBackdrop, CenteredScreen, ChatBackgroundBridge, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, CompanionNativeContent.ios, CompanionSheet, CompanionSheet.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, DeviceLinkContext, DirectLinkTransport, ErrorNotice, FileTypeIcon, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, HomeHeaderGlassButton, HomeHeaderGlassButton.ios, HomeNativeStackHeader, HomeStackHeader, IncomingShareBridge, McpAppViews, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileMailHtmlPreview, MobileOutboxBridge, MobilePrimitives, MobileUserAvatar, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, OutsideTap, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PluginCardActions, PluginCardRecipients, PushNotificationsBridge, QuietSyncIndicator, RecentMessageHistories, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, ResidentHomeList, SessionPaneDivider, SheetModal, SheetSurface, SimpleStackHeader, StartupSplashOverlay, ThemeProvider, UsageWidgetBridge, _layout, gestureHandler, mail-preview, nativeToolbarMenuItems, peerFileTransport, useLocale | apps/mobile/app/_layout.tsx, apps/mobile/app/mail-preview.tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FileTypeIcon.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/MobileUserAvatar.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/HomeNativeStackHeader.tsx, apps/mobile/src/platform/chrome/HomeStackHeader.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/chrome/nativeToolbarMenuItems.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/plugins/MobileMailHtmlPreview.tsx, apps/mobile/src/plugins/PluginCardActions.tsx, apps/mobile/src/plugins/PluginCardRecipients.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/HomeHeaderGlassButton.ios.tsx, apps/mobile/src/session/HomeHeaderGlassButton.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 226 | 86 | | `mobile.overlay.connection-startup` | mobile | 根布局挂载的启动与连接反馈 | app/_layout.tsx → StartupSplashOverlay / ConnectionNoticeProvider;各页面 ConnectionBanner | AdaptiveWindow, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, BlurBackdrop, CenteredScreen, ChatBackgroundBridge, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, CompanionNativeContent.ios, CompanionSheet, CompanionSheet.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ConnectionNoticeProvider, ContextSheet, ContextSheet.ios, DeviceLinkContext, DirectLinkTransport, ErrorNotice, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, IncomingShareBridge, McpAppViews, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileOutboxBridge, MobilePrimitives, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, OutsideTap, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PushNotificationsBridge, QuietSyncIndicator, RecentMessageHistories, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, ResidentHomeList, SessionPaneDivider, SheetModal, SheetSurface, StartupSplashOverlay, ThemeProvider, UsageWidgetBridge, _layout, gestureHandler, peerFileTransport, useLocale | apps/mobile/app/_layout.tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 226 | 73 | -| `mobile.plugins` | mobile | 插件目录、页面与原生交互 | app/plugins.tsx | AdaptiveWindow, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, BlurBackdrop, CenteredScreen, ChatBackgroundBridge, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, CompanionNativeContent.ios, CompanionSheet, CompanionSheet.ios, ComposerNativeRow, ComposerNativeRow.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, DeviceLinkContext, DirectLinkTransport, ErrorNotice, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, HomeHeaderGlassButton, HomeHeaderGlassButton.ios, HomeNativeStackHeader, HomeStackHeader, HtmlBrowserChrome, HtmlBrowserChrome.ios, HtmlFileReader, HtmlWebsiteBrowser, ImageLightbox, IncomingShareBridge, McpAppViews, MobileAgentMark, MobileAgentSwitcher, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileModelPickerList, MobileModelPickerList.ios, MobileOutboxBridge, MobilePermissionPickerList, MobilePermissionPickerList.ios, MobilePrimitives, MobileProviderMark, ModelOptionsSheetView, ModelOptionsSheetView.ios, ModelPickerNativeHeader, ModelPickerNativeHeader.ios, ModelPickerSheet, ModelQuotaBar, ModelSpeedIcon, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, NativeVideoPlayer, OutsideTap, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PluginDetailView, PluginDetailView.ios, PluginDirectoryPicker, PluginDirectoryView, PluginDirectoryView.ios, PluginManagementConfirmation, PluginManagementConfirmation.ios, PluginManagementMenu, PluginMediaViewer, PluginModelPicker, PluginNativeIntent, PluginNotice, PluginNotice.ios, PluginOriginalSettings, PluginPage, PluginPermissionCard, PluginScheduleDraft, PluginSettingsCard, PluginTaskPicker, PluginTaskPickerView, PluginTaskPickerView.ios, PluginToolChips, PluginsScreen, PushNotificationsBridge, QuietSyncIndicator, RecentMessageHistories, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, RemoteSourceMark, ResidentHomeList, SessionPaneDivider, SheetModal, SheetSurface, SimpleStackHeader, StartupSplashOverlay, SystemNavigationBack, ThemeProvider, UnifiedModelPickerSheet, UnifiedModelPickerView, UnifiedModelPickerView.ios, UsageWidgetBridge, VideoDownloadButton, _layout, gestureHandler, mediaPlayerWebView, nativeToolbarMenuItems, peerFileTransport, plugins, useLocale | apps/mobile/app/_layout.tsx, apps/mobile/app/plugins.tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileAgentMark.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/HomeNativeStackHeader.tsx, apps/mobile/src/platform/chrome/HomeStackHeader.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/chrome/SystemNavigationBack.tsx, apps/mobile/src/platform/chrome/nativeToolbarMenuItems.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/plugins/PluginDetailView.ios.tsx, apps/mobile/src/plugins/PluginDetailView.tsx, apps/mobile/src/plugins/PluginDirectoryPicker.tsx, apps/mobile/src/plugins/PluginDirectoryView.ios.tsx, apps/mobile/src/plugins/PluginDirectoryView.tsx, apps/mobile/src/plugins/PluginManagementConfirmation.ios.tsx, apps/mobile/src/plugins/PluginManagementConfirmation.tsx, apps/mobile/src/plugins/PluginManagementMenu.tsx, apps/mobile/src/plugins/PluginMediaViewer.tsx, apps/mobile/src/plugins/PluginModelPicker.tsx, apps/mobile/src/plugins/PluginNativeIntent.tsx, apps/mobile/src/plugins/PluginNotice.ios.tsx, apps/mobile/src/plugins/PluginNotice.tsx, apps/mobile/src/plugins/PluginOriginalSettings.tsx, apps/mobile/src/plugins/PluginPage.tsx, apps/mobile/src/plugins/PluginPermissionCard.tsx, apps/mobile/src/plugins/PluginScheduleDraft.tsx, apps/mobile/src/plugins/PluginSettingsCard.tsx, apps/mobile/src/plugins/PluginTaskPicker.tsx, apps/mobile/src/plugins/PluginTaskPickerView.ios.tsx, apps/mobile/src/plugins/PluginTaskPickerView.tsx, apps/mobile/src/plugins/PluginToolChips.tsx, apps/mobile/src/plugins/PluginsScreen.tsx, apps/mobile/src/plugins/pluginPageHtml.ts, apps/mobile/src/plugins/pluginSettingsHtml.ts, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/ComposerNativeRow.ios.tsx, apps/mobile/src/session/ComposerNativeRow.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/HomeHeaderGlassButton.ios.tsx, apps/mobile/src/session/HomeHeaderGlassButton.tsx, apps/mobile/src/session/HtmlBrowserChrome.ios.tsx, apps/mobile/src/session/HtmlBrowserChrome.tsx, apps/mobile/src/session/HtmlFileReader.tsx, apps/mobile/src/session/HtmlWebsiteBrowser.tsx, apps/mobile/src/session/ImageLightbox.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MobileAgentSwitcher.tsx, apps/mobile/src/session/MobileModelPickerList.ios.tsx, apps/mobile/src/session/MobileModelPickerList.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/MobilePermissionPickerList.ios.tsx, apps/mobile/src/session/MobilePermissionPickerList.tsx, apps/mobile/src/session/MobileProviderMark.tsx, apps/mobile/src/session/ModelOptionsSheetView.ios.tsx, apps/mobile/src/session/ModelOptionsSheetView.tsx, apps/mobile/src/session/ModelPickerNativeHeader.ios.tsx, apps/mobile/src/session/ModelPickerNativeHeader.tsx, apps/mobile/src/session/ModelPickerSheet.tsx, apps/mobile/src/session/ModelQuotaBar.tsx, apps/mobile/src/session/ModelSpeedIcon.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/NativeVideoPlayer.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/RemoteSourceMark.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/session/UnifiedModelPickerSheet.tsx, apps/mobile/src/session/UnifiedModelPickerView.ios.tsx, apps/mobile/src/session/UnifiedModelPickerView.tsx, apps/mobile/src/session/VideoDownloadButton.tsx, apps/mobile/src/session/mediaPlayerWebView.tsx, apps/mobile/src/session/mediaPlayerWebViewHtml.ts, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 4 | 265 | 112 | +| `mobile.plugins` | mobile | 插件目录、页面与原生交互 | app/plugins.tsx | AdaptiveWindow, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, BlurBackdrop, CenteredScreen, ChatBackgroundBridge, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, CompanionNativeContent.ios, CompanionSheet, CompanionSheet.ios, ComposerNativeRow, ComposerNativeRow.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, DeviceLinkContext, DirectLinkTransport, DocumentPreviewChrome, ErrorNotice, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, HomeHeaderGlassButton, HomeHeaderGlassButton.ios, HomeNativeStackHeader, HomeStackHeader, HtmlBrowserChrome, HtmlBrowserChrome.ios, HtmlFileReader, HtmlWebsiteBrowser, ImageLightbox, IncomingShareBridge, McpAppViews, MobileAgentMark, MobileAgentSwitcher, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileModelPickerList, MobileModelPickerList.ios, MobileOutboxBridge, MobilePermissionPickerList, MobilePermissionPickerList.ios, MobilePrimitives, MobileProviderMark, ModelOptionsSheetView, ModelOptionsSheetView.ios, ModelPickerNativeHeader, ModelPickerNativeHeader.ios, ModelPickerSheet, ModelQuotaBar, ModelSpeedIcon, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, NativeVideoPlayer, OutsideTap, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PluginDetailView, PluginDetailView.ios, PluginDirectoryPicker, PluginDirectoryView, PluginDirectoryView.ios, PluginManagementConfirmation, PluginManagementConfirmation.ios, PluginManagementMenu, PluginMediaViewer, PluginModelPicker, PluginNativeIntent, PluginNotice, PluginNotice.ios, PluginOriginalSettings, PluginPage, PluginPermissionCard, PluginScheduleDraft, PluginSettingsCard, PluginTaskPicker, PluginTaskPickerView, PluginTaskPickerView.ios, PluginToolChips, PluginsScreen, PreviewChrome, PushNotificationsBridge, QuietSyncIndicator, RecentMessageHistories, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, RemoteSourceMark, ResidentHomeList, SessionPaneDivider, SheetModal, SheetSurface, SimpleStackHeader, StartupSplashOverlay, SystemNavigationBack, ThemeProvider, UnifiedModelPickerSheet, UnifiedModelPickerView, UnifiedModelPickerView.ios, UsageWidgetBridge, VideoDownloadButton, _layout, gestureHandler, mediaPlayerWebView, nativeToolbarMenuItems, peerFileTransport, plugins, useLocale | apps/mobile/app/_layout.tsx, apps/mobile/app/plugins.tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileAgentMark.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/HomeNativeStackHeader.tsx, apps/mobile/src/platform/chrome/HomeStackHeader.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/chrome/SystemNavigationBack.tsx, apps/mobile/src/platform/chrome/nativeToolbarMenuItems.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/plugins/PluginDetailView.ios.tsx, apps/mobile/src/plugins/PluginDetailView.tsx, apps/mobile/src/plugins/PluginDirectoryPicker.tsx, apps/mobile/src/plugins/PluginDirectoryView.ios.tsx, apps/mobile/src/plugins/PluginDirectoryView.tsx, apps/mobile/src/plugins/PluginManagementConfirmation.ios.tsx, apps/mobile/src/plugins/PluginManagementConfirmation.tsx, apps/mobile/src/plugins/PluginManagementMenu.tsx, apps/mobile/src/plugins/PluginMediaViewer.tsx, apps/mobile/src/plugins/PluginModelPicker.tsx, apps/mobile/src/plugins/PluginNativeIntent.tsx, apps/mobile/src/plugins/PluginNotice.ios.tsx, apps/mobile/src/plugins/PluginNotice.tsx, apps/mobile/src/plugins/PluginOriginalSettings.tsx, apps/mobile/src/plugins/PluginPage.tsx, apps/mobile/src/plugins/PluginPermissionCard.tsx, apps/mobile/src/plugins/PluginScheduleDraft.tsx, apps/mobile/src/plugins/PluginSettingsCard.tsx, apps/mobile/src/plugins/PluginTaskPicker.tsx, apps/mobile/src/plugins/PluginTaskPickerView.ios.tsx, apps/mobile/src/plugins/PluginTaskPickerView.tsx, apps/mobile/src/plugins/PluginToolChips.tsx, apps/mobile/src/plugins/PluginsScreen.tsx, apps/mobile/src/plugins/pluginPageHtml.ts, apps/mobile/src/plugins/pluginSettingsHtml.ts, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/ComposerNativeRow.ios.tsx, apps/mobile/src/session/ComposerNativeRow.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/DocumentPreviewChrome.tsx, apps/mobile/src/session/HomeHeaderGlassButton.ios.tsx, apps/mobile/src/session/HomeHeaderGlassButton.tsx, apps/mobile/src/session/HtmlBrowserChrome.ios.tsx, apps/mobile/src/session/HtmlBrowserChrome.tsx, apps/mobile/src/session/HtmlFileReader.tsx, apps/mobile/src/session/HtmlWebsiteBrowser.tsx, apps/mobile/src/session/ImageLightbox.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MobileAgentSwitcher.tsx, apps/mobile/src/session/MobileModelPickerList.ios.tsx, apps/mobile/src/session/MobileModelPickerList.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/MobilePermissionPickerList.ios.tsx, apps/mobile/src/session/MobilePermissionPickerList.tsx, apps/mobile/src/session/MobileProviderMark.tsx, apps/mobile/src/session/ModelOptionsSheetView.ios.tsx, apps/mobile/src/session/ModelOptionsSheetView.tsx, apps/mobile/src/session/ModelPickerNativeHeader.ios.tsx, apps/mobile/src/session/ModelPickerNativeHeader.tsx, apps/mobile/src/session/ModelPickerSheet.tsx, apps/mobile/src/session/ModelQuotaBar.tsx, apps/mobile/src/session/ModelSpeedIcon.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/NativeVideoPlayer.tsx, apps/mobile/src/session/PreviewChrome.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/RemoteSourceMark.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/session/UnifiedModelPickerSheet.tsx, apps/mobile/src/session/UnifiedModelPickerView.ios.tsx, apps/mobile/src/session/UnifiedModelPickerView.tsx, apps/mobile/src/session/VideoDownloadButton.tsx, apps/mobile/src/session/mediaPlayerWebView.tsx, apps/mobile/src/session/mediaPlayerWebViewHtml.ts, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 4 | 232 | 111 | | `mobile.relay-join` | mobile | 扫码加入自建中转 | app/relay-join.tsx | AdaptiveWindow, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, BlurBackdrop, CenteredScreen, ChatBackgroundBridge, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, CompanionNativeContent.ios, CompanionSheet, CompanionSheet.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, DeviceLinkContext, DirectLinkTransport, ErrorNotice, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, IncomingShareBridge, McpAppViews, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileOutboxBridge, MobilePrimitives, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, OutsideTap, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PushNotificationsBridge, QuietSyncIndicator, RecentMessageHistories, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, ResidentHomeList, SessionPaneDivider, SheetModal, SheetSurface, SimpleStackHeader, StartupSplashOverlay, ThemeProvider, UsageWidgetBridge, _layout, gestureHandler, peerFileTransport, relay-join, useLocale | apps/mobile/app/_layout.tsx, apps/mobile/app/relay-join.tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 226 | 75 | | `mobile.remote-desktop` | mobile | 远程桌面 | app/devices/desktop/[deviceId].tsx | AdaptiveWindow, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, BlurBackdrop, CenteredScreen, ChatBackgroundBridge, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, CompanionNativeContent.ios, CompanionSheet, CompanionSheet.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, DeviceLinkContext, DirectLinkTransport, ErrorNotice, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, IncomingShareBridge, McpAppViews, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileOutboxBridge, MobilePrimitives, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, OutsideTap, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PushNotificationsBridge, QuietSyncIndicator, RecentMessageHistories, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, ResidentHomeList, SessionPaneDivider, SheetModal, SheetSurface, StartupSplashOverlay, ThemeProvider, UsageWidgetBridge, [deviceId], _layout, gestureHandler, peerFileTransport, useLocale | apps/mobile/app/_layout.tsx, apps/mobile/app/devices/desktop/[deviceId].tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 226 | 73 | | `mobile.resources` | mobile | 远程资源列表与详情 | app/resources/[collectionId].tsx / app/resources/[collectionId]/[resourceId].tsx | AdaptiveWindow, AnchoredPullDownMenu, AnchoredPullDownMenu.android, AndroidUpdateSheet, AppText, AuthContext, BlurBackdrop, CenteredScreen, ChatBackgroundBridge, ChatInviteAvatar, ChatInviteHost, ChatInviteSheet, ChatInviteView, ChatInviteView.ios, CompanionChannelTasks, CompanionChoice, CompanionCreateNativeView, CompanionCreateNativeView.ios, CompanionImportSheet, CompanionMemoryPage, CompanionMemoryPage.ios, CompanionModelChain, CompanionNativeContent.ios, CompanionPortraitPicker, CompanionProfileArtifacts, CompanionProfileNativeView, CompanionProfileNativeView.ios, CompanionProfileSheet, CompanionSettingsRow, CompanionSettingsRow.ios, CompanionSheet, CompanionSheet.ios, ComposerNativeRow, ComposerNativeRow.ios, ComposerNativeSection, ComposerNativeSection.ios, ComposerSheet, ComposerSheet.ios, ConnectionBanner, ConnectionNoticeOverlay, ContextSheet, ContextSheet.ios, DeviceLinkContext, DirectLinkTransport, ErrorNotice, FilterReminderDot, FoldDesktopSkin, FoldTouchpad, HomeHeaderGlassButton, HomeHeaderGlassButton.ios, HomeNativeStackHeader, HomeStackHeader, IncomingShareBridge, McpAppViews, MobileAgentMark, MobileAgentSwitcher, MobileLoginHandoffContext, MobileLoginHandoffStage, MobileModelPickerList, MobileModelPickerList.ios, MobileOutboxBridge, MobilePermissionPickerList, MobilePermissionPickerList.ios, MobilePrimitives, MobileProviderMark, ModelOptionsSheetView, ModelOptionsSheetView.ios, ModelPickerNativeHeader, ModelPickerNativeHeader.ios, ModelPickerSheet, ModelQuotaBar, ModelSpeedIcon, NativeBottomSheet, NativeBottomSheet.android, NativeChromeBackButton.ios, NativeChromeButton.ios, NativeErrorNotice.ios, NativeMcpAppSurface, NativePullDownMenu, NativeRemoteDesktopView, NativeResidentHistory, NativeSwitch, OutsideTap, PermissionGuide, PermissionGuideView, PermissionGuideView.ios, PushNotificationsBridge, QuietSyncIndicator, RecentMessageHistories, RemoteCompanionAvatar, RemoteDesktopActionButton, RemoteDesktopActionButton.ios, RemoteDesktopBackButton, RemoteDesktopBackButton.ios, RemoteDesktopChrome, RemoteDesktopChrome.ios, RemoteDesktopClipboardButton, RemoteDesktopControls, RemoteDesktopDisplaySettings, RemoteDesktopHost, RemoteDesktopIcons, RemoteDesktopMouseControls, RemoteDesktopMouseControls.ios, RemoteDesktopNetworkStatus, RemoteDesktopPanelButton, RemoteDesktopPanelButton.ios, RemoteDesktopScreen, RemoteDesktopSecuritySettings, RemoteDesktopWindows, RemoteHistoryPrefetchBridge, RemoteSourceMark, ResidentHomeList, SessionPaneDivider, SheetModal, SheetSurface, SimpleStackHeader, StartupSplashOverlay, ThemeProvider, UnifiedModelPickerSheet, UnifiedModelPickerView, UnifiedModelPickerView.ios, UsageWidgetBridge, [collectionId], [resourceId], _layout, gestureHandler, nativeToolbarMenuItems, peerFileTransport, useLocale | apps/mobile/app/_layout.tsx, apps/mobile/app/resources/[collectionId].tsx, apps/mobile/app/resources/[collectionId]/[resourceId].tsx, apps/mobile/src/auth/AuthContext.tsx, apps/mobile/src/auth/MobileLoginHandoffContext.tsx, apps/mobile/src/chat/ChatInviteAvatar.tsx, apps/mobile/src/chat/ChatInviteHost.tsx, apps/mobile/src/chat/ChatInviteSheet.tsx, apps/mobile/src/chat/ChatInviteView.ios.tsx, apps/mobile/src/chat/ChatInviteView.tsx, apps/mobile/src/components/AppText.tsx, apps/mobile/src/components/CenteredScreen.tsx, apps/mobile/src/components/ConnectionBanner.tsx, apps/mobile/src/components/ConnectionNoticeOverlay.tsx, apps/mobile/src/components/ErrorNotice.tsx, apps/mobile/src/components/FilterReminderDot.tsx, apps/mobile/src/components/MobileAgentMark.tsx, apps/mobile/src/components/MobileLoginHandoffStage.tsx, apps/mobile/src/components/MobilePrimitives.tsx, apps/mobile/src/components/NativeErrorNotice.ios.tsx, apps/mobile/src/components/QuietSyncIndicator.tsx, apps/mobile/src/components/RemoteCompanionAvatar.tsx, apps/mobile/src/components/StartupSplashOverlay.tsx, apps/mobile/src/device-link/DeviceLinkContext.tsx, apps/mobile/src/device-link/DirectLinkTransport.tsx, apps/mobile/src/device-link/peerFileTransport.tsx, apps/mobile/src/i18n/useLocale.tsx, apps/mobile/src/notifications/ChatBackgroundBridge.tsx, apps/mobile/src/notifications/PushNotificationsBridge.tsx, apps/mobile/src/platform/AdaptiveWindow.tsx, apps/mobile/src/platform/OutsideTap.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.android.tsx, apps/mobile/src/platform/chrome/AnchoredPullDownMenu.tsx, apps/mobile/src/platform/chrome/HomeNativeStackHeader.tsx, apps/mobile/src/platform/chrome/HomeStackHeader.tsx, apps/mobile/src/platform/chrome/NativeChromeBackButton.ios.tsx, apps/mobile/src/platform/chrome/NativeChromeButton.ios.tsx, apps/mobile/src/platform/chrome/NativePullDownMenu.tsx, apps/mobile/src/platform/chrome/NativeSwitch.tsx, apps/mobile/src/platform/chrome/SimpleStackHeader.tsx, apps/mobile/src/platform/chrome/nativeToolbarMenuItems.tsx, apps/mobile/src/platform/gestureHandler.tsx, apps/mobile/src/remote-desktop/FoldDesktopSkin.tsx, apps/mobile/src/remote-desktop/FoldTouchpad.tsx, apps/mobile/src/remote-desktop/NativeRemoteDesktopView.tsx, apps/mobile/src/remote-desktop/PermissionGuide.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.ios.tsx, apps/mobile/src/remote-desktop/PermissionGuideView.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopActionButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopBackButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopChrome.tsx, apps/mobile/src/remote-desktop/RemoteDesktopClipboardButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopDisplaySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopHost.tsx, apps/mobile/src/remote-desktop/RemoteDesktopIcons.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopMouseControls.tsx, apps/mobile/src/remote-desktop/RemoteDesktopNetworkStatus.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.ios.tsx, apps/mobile/src/remote-desktop/RemoteDesktopPanelButton.tsx, apps/mobile/src/remote-desktop/RemoteDesktopScreen.tsx, apps/mobile/src/remote-desktop/RemoteDesktopSecuritySettings.tsx, apps/mobile/src/remote-desktop/RemoteDesktopWindows.tsx, apps/mobile/src/remote-desktop/viewerHtml.ts, apps/mobile/src/session/BlurBackdrop.tsx, apps/mobile/src/session/CompanionChannelTasks.tsx, apps/mobile/src/session/CompanionChoice.tsx, apps/mobile/src/session/CompanionCreateNativeView.ios.tsx, apps/mobile/src/session/CompanionCreateNativeView.tsx, apps/mobile/src/session/CompanionImportSheet.tsx, apps/mobile/src/session/CompanionMemoryPage.ios.tsx, apps/mobile/src/session/CompanionMemoryPage.tsx, apps/mobile/src/session/CompanionModelChain.tsx, apps/mobile/src/session/CompanionNativeContent.ios.tsx, apps/mobile/src/session/CompanionPortraitPicker.tsx, apps/mobile/src/session/CompanionProfileArtifacts.tsx, apps/mobile/src/session/CompanionProfileNativeView.ios.tsx, apps/mobile/src/session/CompanionProfileNativeView.tsx, apps/mobile/src/session/CompanionProfileSheet.tsx, apps/mobile/src/session/CompanionSettingsRow.ios.tsx, apps/mobile/src/session/CompanionSettingsRow.tsx, apps/mobile/src/session/CompanionSheet.ios.tsx, apps/mobile/src/session/CompanionSheet.tsx, apps/mobile/src/session/ComposerNativeRow.ios.tsx, apps/mobile/src/session/ComposerNativeRow.tsx, apps/mobile/src/session/ComposerNativeSection.ios.tsx, apps/mobile/src/session/ComposerNativeSection.tsx, apps/mobile/src/session/ComposerSheet.ios.tsx, apps/mobile/src/session/ComposerSheet.tsx, apps/mobile/src/session/ContextSheet.ios.tsx, apps/mobile/src/session/ContextSheet.tsx, apps/mobile/src/session/HomeHeaderGlassButton.ios.tsx, apps/mobile/src/session/HomeHeaderGlassButton.tsx, apps/mobile/src/session/IncomingShareBridge.tsx, apps/mobile/src/session/McpAppViews.tsx, apps/mobile/src/session/MobileAgentSwitcher.tsx, apps/mobile/src/session/MobileModelPickerList.ios.tsx, apps/mobile/src/session/MobileModelPickerList.tsx, apps/mobile/src/session/MobileOutboxBridge.tsx, apps/mobile/src/session/MobilePermissionPickerList.ios.tsx, apps/mobile/src/session/MobilePermissionPickerList.tsx, apps/mobile/src/session/MobileProviderMark.tsx, apps/mobile/src/session/ModelOptionsSheetView.ios.tsx, apps/mobile/src/session/ModelOptionsSheetView.tsx, apps/mobile/src/session/ModelPickerNativeHeader.ios.tsx, apps/mobile/src/session/ModelPickerNativeHeader.tsx, apps/mobile/src/session/ModelPickerSheet.tsx, apps/mobile/src/session/ModelQuotaBar.tsx, apps/mobile/src/session/ModelSpeedIcon.tsx, apps/mobile/src/session/NativeBottomSheet.android.tsx, apps/mobile/src/session/NativeBottomSheet.tsx, apps/mobile/src/session/NativeMcpAppSurface.tsx, apps/mobile/src/session/NativeResidentHistory.tsx, apps/mobile/src/session/RecentMessageHistories.tsx, apps/mobile/src/session/RemoteHistoryPrefetchBridge.tsx, apps/mobile/src/session/RemoteSourceMark.tsx, apps/mobile/src/session/ResidentHomeList.tsx, apps/mobile/src/session/SessionPaneDivider.tsx, apps/mobile/src/session/SheetModal.tsx, apps/mobile/src/session/SheetSurface.tsx, apps/mobile/src/session/UnifiedModelPickerSheet.tsx, apps/mobile/src/session/UnifiedModelPickerView.ios.tsx, apps/mobile/src/session/UnifiedModelPickerView.tsx, apps/mobile/src/theme/ThemeProvider.tsx, apps/mobile/src/theme/navigationTheme.ts, apps/mobile/src/theme/tokens.ts, apps/mobile/src/update/AndroidUpdateSheet.tsx, apps/mobile/src/widgets/UsageWidgetBridge.tsx | 0 | 226 | 98 | diff --git a/docs/product-rules/attachment-preview.md b/docs/product-rules/attachment-preview.md new file mode 100644 index 00000000000..62a35e9cae7 --- /dev/null +++ b/docs/product-rules/attachment-preview.md @@ -0,0 +1,39 @@ +# 附件预览:三端共享行为合同 + +2026-11-01。适用于 Desktop、iOS、Android;任务、伙伴私聊、群与话题、输入区、文件浏览、成果和插件媒体。统一查看方式,读取授权仍由各来源负责。 + +## 打开与返回 + +所有查看器都有固定的文件名、关闭和文件操作入口。关闭在加载、失败、播放、放大时可达;关闭仅结束查看,不能删除附件、撤回消息或离开来源任务。桌面保留 Esc、焦点归还;手机保留系统返回、安全区和原生播放器。未保存标注由原标注流程询问保存、放弃或继续;不能通过切换附件绕过。 + +同一条消息、待发托盘或文件列表可提供明确集合。进入时固定其身份与顺序,不扫描整页 DOM、不将新到消息插入当前集合。上一项、下一项到边界禁用;计数可打开文件列表。只有一项时不显示无意义导航。纯图片消息原有图库和缩略图行为继续保留。 + +## 类型能力 + +| 内容 | 标准查看行为 | +| --- | --- | +| 图片 / GIF / SVG | 原图、缩放、平移、适应、原始尺寸、原件保存或分享;可用入口继续提供标注。图片始终保持比例,SVG 走既有图像安全路径。 | +| 视频 / 音频 | 应用内播放器、进度和平台支持的音量 / 全屏;宿主关闭与保存独立于播放器控件。切换或退出销毁/暂停旧播放器。 | +| PDF | 三端内嵌 PDF.js;页码、翻页、缩放、文字层与查找。手机离线引擎不依赖第三方网页;30 MB 上限保留保存原件的出口,密码仅交给本地阅读器。 | +| 文本 / 代码 / JSON | 原文选择复制、查找、换行、行号与引用位置;既有长文本编辑不变。读取上限与截断提示继续生效。 | +| Markdown | 阅读 / 源码切换、原有链接与引用能力;源码复用公共文本阅读器。 | +| HTML | 保留既有网页预览、导航与脚本/资源策略;更换外壳不增加网页权限。群附件沿既有受限阅读器。 | +| Mermaid / draw.io | 图形与源码、缩放、既有导出能力;手机使用离线渲染资源。 | +| GLB / glTF | 模型查看、旋转、缩放和复位;手机读取已授权本地副本(50 MB 上限)。外部资源需原传输路径明确提供,不从文件内容推导额外授权。 | +| 其他文件 / FBX | 显示文件名与类型,提供保存/分享或已有外部打开;不把无法解码的文件伪装成可预览。 | + +## 来源与生命周期 + +- 本机、另一台电脑、SSH、Chat Server、插件授权资源继续走现有 resolver。UI 不把远端绝对路径解释为手机本地文件,不公开签名 URL 或凭证。 +- 保存/分享交付原件,不能导出播放器截图冒充原件;手机复制至受控临时目录,系统分享完成后释放,查看期间保留缓存租约。 +- 关闭或切换中断读取;迟到的结果不再应用到已卸载查看器。账户隔离、插件 grant、服务端附件授权和危险文件下载策略不变。 +- 错误状态保留关闭;可重试的读取/解码错误提供重试。未知格式可直接分享,不要求先成功预览。 +- 过程图片仍留在展开的工作过程里,伙伴成果只展示已有成果,不把临时文件升级为成果。 + +## 实现入口 + +共享类型判定在 `packages/maker-shared/src/attachmentPreview.ts`。Desktop 使用 `PreviewChrome`、`AttachmentCollection`,经 `TextLightbox` / `FileLightbox` 分派既有内容渲染器。Mobile 使用 `PreviewChrome`、`RemoteAttachmentPreview`、`DocumentPreviewChrome` 和类型阅读器;任务、群附件、草稿、插件媒体与伙伴成果接入同一合同。 + +移动端离线资源由 `node apps/mobile/scripts/sync-document-preview-assets.mjs` 从现有依赖和 Desktop draw.io vendor 生成;更新依赖后重新生成,禁止手改生成文件。没有新增 native dependency、协议字段或授权记录迁移。 + +验收与尚未完成的环境补充检查见 [本次证据](../design-evidence/2026-11-01/attachment-preview/README.md)。本合同描述行为要求;组件复用或代码存在不能代替对应来源的实机验证。 diff --git a/packages/maker-shared/package.json b/packages/maker-shared/package.json index 1204a1fa3eb..7ee44f7cb8d 100644 --- a/packages/maker-shared/package.json +++ b/packages/maker-shared/package.json @@ -35,6 +35,7 @@ "./file-browser": "./src/fileBrowser.ts", "./file-browser-grid": "./src/fileBrowserGrid.ts", "./file-preview": "./src/filePreview.ts", + "./attachment-preview": "./src/attachmentPreview.ts", "./fixtures": "./src/fixtures.ts", "./ui-mock": "./src/uiMock.ts", "./ghost-command": "./src/ghostCommand.ts", diff --git a/packages/maker-shared/src/attachmentPreview.ts b/packages/maker-shared/src/attachmentPreview.ts new file mode 100644 index 00000000000..e02bc95b6c5 --- /dev/null +++ b/packages/maker-shared/src/attachmentPreview.ts @@ -0,0 +1,98 @@ +import { + basenameRemotePath, + extractRemoteFileExt, + isHtmlFilePreviewCandidate, + remoteFilePreviewKind, +} from "./filePreview.js"; + +/** Presentation only. A source is an opaque reference resolved by its existing host. */ +export type PreviewKind = + | "image" + | "video" + | "audio" + | "pdf" + | "text" + | "markdown" + | "html" + | "diagram" + | "model" + | "file"; +export interface PreviewItem { + id: string; + name: string; + source: string; + kind: PreviewKind; + mimeType?: string; + size?: number; +} +/** No wrapping: the final item remains final for keyboard, swipe and buttons alike. */ +export function previewAdjacentIndex( + index: number, + delta: number, + count: number, +): number { + return Math.max(0, Math.min(Math.max(0, count - 1), index + delta)); +} + +/** Takes a real filename, not a signed URL. Transport URLs cannot select executable HTML. */ +export function attachmentPreviewKind( + name: string, + mimeType?: string, +): PreviewKind { + const ext = extractRemoteFileExt(basenameRemotePath(name)); + if ( + remoteFilePreviewKind(name) === "drawio" || + ext === ".mmd" || + ext === ".mermaid" + ) + return "diagram"; + if ( + [ + ".png", + ".jpg", + ".jpeg", + ".gif", + ".webp", + ".svg", + ".avif", + ".bmp", + ".ico", + ".heic", + ".heif", + ].includes(ext) + ) + return "image"; + if ([".mp4", ".m4v", ".mov", ".webm", ".ogv", ".mkv"].includes(ext)) + return "video"; + if ( + [".mp3", ".m4a", ".wav", ".ogg", ".oga", ".aac", ".flac", ".opus"].includes( + ext, + ) + ) + return "audio"; + if (ext === ".pdf") return "pdf"; + if (ext === ".glb" || ext === ".gltf") return "model"; + if (isHtmlFilePreviewCandidate(name)) return "html"; + if (ext === ".md" || ext === ".markdown") return "markdown"; + if (remoteFilePreviewKind(name) === "text") return "text"; + // MIME supplements extensionless managed media; it never enables active documents. + if (!ext && mimeType?.startsWith("image/")) return "image"; + if (!ext && mimeType?.startsWith("video/")) return "video"; + if (!ext && mimeType?.startsWith("audio/")) return "audio"; + return "file"; +} + +/** Safe chrome label: do not display credentials, query strings, or inline data. */ +export function previewSourceName(source: string, fallback: string): string { + if (/^(data|blob):/i.test(source)) return fallback; + try { + const url = new URL(source); + const path = ["xdt-file:", "xdt-audio:"].includes(url.protocol) + ? url.searchParams.get("path") + : url.pathname; + const name = path ? basenameRemotePath(path) : ""; + return name ? decodeURIComponent(name) : fallback; + } catch { + return basenameRemotePath(source) || fallback; + } +} diff --git a/packages/maker-shared/src/filePreview.ts b/packages/maker-shared/src/filePreview.ts index b9db37cce40..2112751a225 100644 --- a/packages/maker-shared/src/filePreview.ts +++ b/packages/maker-shared/src/filePreview.ts @@ -22,6 +22,8 @@ const SUPPORTED_OFFICE_EXTS = new Set(['.doc', '.docx', '.xls', '.xlsx', '.ppt', // Mirrors desktop shared/textFileExts.ts for remote read-only previews. const SUPPORTED_TEXT_EXTS = new Set([ + ".mmd", + ".mermaid", '.js', '.ts', '.tsx', '.jsx', '.mjs', '.cjs', '.py', '.go', '.rs', '.java', '.c', '.cpp', '.cc', '.cxx', '.h', '.hpp', '.hh', '.cs', '.rb', '.php', '.swift', '.kt', '.kts', '.scala', '.groovy', '.coffee', diff --git a/scripts/__tests__/design-inventory.test.mjs b/scripts/__tests__/design-inventory.test.mjs index b6739eef866..5a333c1665d 100644 --- a/scripts/__tests__/design-inventory.test.mjs +++ b/scripts/__tests__/design-inventory.test.mjs @@ -985,28 +985,22 @@ test('语音浮窗纳入 globals.css 与直接渲染的 VoiceInputStatusNotice', assert.ok(generated.bareRadii > 10, `裸圆角应高于 10(实际 ${generated.bareRadii})`); }); -test('裸圆角统计覆盖 React style 对象的 camelCase borderRadius', () => { - // 与 BARE_RADIUS_RE 同口径的行为级断言:经 scanStyleStats 出来的统计必须数到 - // style 对象声明。用真实文件钉死:LoginCaptchaOverlay.tsx 的 borderRadius: 18。 - const { surfaces } = buildGeneratedSurfaces(ROOT, {}); - const login = surfaces.find((surface) => surface.id === 'desktop.auth.login'); - // 上一轮口径(只认 rounded* 与 border-radius:)登录页裸圆角为 6; - // LoginCaptchaOverlay 的 borderRadius: 18 等内联声明并入后必须更高。 - assert.ok(login.bareRadii > 6, `camelCase borderRadius 应计入裸圆角(实际 ${login.bareRadii})`); - // 会话工作台含 ImageLightbox 的 borderRadius: '9999px'。 - const session = surfaces.find((surface) => surface.id === 'desktop.chat.session'); - const lightboxPath = 'apps/desktop/src/renderer/components/chat/ImageLightbox.tsx'; - assert.ok(session.styleSources.includes(lightboxPath), 'ImageLightbox 必须可达'); - // 独立统计这个文件:3 处 rounded-full + 2 处 camelCase borderRadius。 - // 不能用整个工作台的圆角总数作下限,否则合法删除 B 版等组件也会误报扫描器退化。 - const isolated = buildGeneratedSurfaces(ROOT, { - catalog: [{ - ...catalogSurfaces().find((surface) => surface.id === session.id), - styleRoots: [lightboxPath], - extraStyleRoots: [], - }], - }).surfaces[0]; - assert.equal(isolated.bareRadii, 5, 'ImageLightbox 的两个内联圆角必须计入'); +test('裸圆角统计覆盖 React style 对象的 camelCase borderRadius', (t) => { + // CI #8356: scan the production generator with an isolated source fixture. + // UI refactors may legitimately remove radii; the scanner's contract stays fixed. + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cindy-radius-inventory-')); + t.after(() => fs.rmSync(root, { recursive: true, force: true })); + const file = 'apps/desktop/src/renderer/fixture.tsx'; + fs.mkdirSync(path.dirname(path.join(root, file)), { recursive: true }); + fs.writeFileSync(path.join(root, file), ` + export const Fixture = () =>
; + // borderRadius: 99 is a comment, not a style. + `); + const catalog = [{ ...catalogSurfaces().find(surface => surface.id === 'desktop.chat.session'), + styleRoots: [file], extraStyleRoots: [] }]; + const result = buildGeneratedSurfaces(root, { catalog }).surfaces[0]; + assert.ok(result.styleSources.includes(file)); + assert.equal(result.bareRadii, 2, 'class and camelCase style both count, comments do not'); }); test('renderer 模块图入口双向核对: index.tsx 的参数→入口模块映射必须与 catalog 一致', () => {