@ingcreators/annot-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/LICENSE +201 -0
- package/README.md +56 -0
- package/dist/auto-capture-options.d.ts +78 -0
- package/dist/editor/arrow-markers.d.ts +142 -0
- package/dist/editor/bake-translate.d.ts +192 -0
- package/dist/editor/font-registry.d.ts +58 -0
- package/dist/editor/gradient-utils.d.ts +23 -0
- package/dist/editor/history-core.d.ts +48 -0
- package/dist/editor/icons/brand-icons.d.ts +53 -0
- package/dist/editor/icons/material-symbols.d.ts +105 -0
- package/dist/editor/icons/registry.d.ts +179 -0
- package/dist/editor/icons/render.d.ts +22 -0
- package/dist/editor/icons/sanitize.d.ts +60 -0
- package/dist/editor/index.d.ts +13 -0
- package/dist/editor/path-utils.d.ts +32 -0
- package/dist/editor/property-schema.d.ts +263 -0
- package/dist/editor/rich-text-mapper.d.ts +8 -0
- package/dist/editor/selection-geometry.d.ts +66 -0
- package/dist/editor/shape-utils.d.ts +27 -0
- package/dist/editor/svg-format.d.ts +68 -0
- package/dist/editor/svg-id-utils.d.ts +37 -0
- package/dist/editor/svg-to-annotation-shapes.d.ts +34 -0
- package/dist/editor/text-utils.d.ts +212 -0
- package/dist/editor/tool-lifecycle.d.ts +56 -0
- package/dist/editor/tool-options.d.ts +126 -0
- package/dist/editor/tool-panel-adapter.d.ts +105 -0
- package/dist/editor/tool-preset-serde.d.ts +43 -0
- package/dist/editor/tool-registry.d.ts +320 -0
- package/dist/editor/tool-style-reader.d.ts +36 -0
- package/dist/editor/tool-style-writer.d.ts +33 -0
- package/dist/editor/toolbar-icons.d.ts +84 -0
- package/dist/editor/transform-utils.d.ts +127 -0
- package/dist/editor/viewport-math.d.ts +69 -0
- package/dist/encode/index.d.ts +4 -0
- package/dist/encode/options.d.ts +79 -0
- package/dist/encode/png8.d.ts +10 -0
- package/dist/headless.d.ts +29 -0
- package/dist/icons/index.d.ts +15 -0
- package/dist/icons/types.d.ts +108 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3164 -0
- package/dist/storage/errors.d.ts +59 -0
- package/dist/storage/index.d.ts +6 -0
- package/dist/storage/metadata-cache.d.ts +231 -0
- package/dist/storage/path.d.ts +49 -0
- package/dist/storage/thumbnail-cache.d.ts +110 -0
- package/dist/storage/thumbnail.d.ts +31 -0
- package/dist/storage/types.d.ts +677 -0
- package/dist/utils/assert.d.ts +31 -0
- package/dist/utils/constants.d.ts +10 -0
- package/dist/utils/dash-utils.d.ts +6 -0
- package/dist/utils/desktop-bridge.d.ts +349 -0
- package/dist/utils/filename.d.ts +48 -0
- package/dist/utils/id.d.ts +21 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/types.d.ts +20 -0
- package/dist/xmp/xmp-browser.d.ts +39 -0
- package/dist/zip/zip-builder.d.ts +8 -0
- package/dist/zip/zip-bytes.d.ts +22 -0
- package/package.json +58 -0
- package/styles/editor.css +1912 -0
- package/styles/fonts.css +46 -0
- package/styles/property-panel.css +779 -0
- package/styles/toolbar.css +673 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable discriminator for cross-backend storage error handling.
|
|
3
|
+
* Mirrors the `StorageError` subclass hierarchy below; callers
|
|
4
|
+
* that prefer a `switch` over `instanceof` can use `e.code`.
|
|
5
|
+
*/
|
|
6
|
+
export type StorageErrorCode = "conflict" | "not-found" | "permission" | "quota";
|
|
7
|
+
/**
|
|
8
|
+
* Base for all `StorageProvider`-thrown errors that callers may
|
|
9
|
+
* want to discriminate. Generic IO / network / parse errors keep
|
|
10
|
+
* throwing plain `Error` and are NOT captured by `instanceof
|
|
11
|
+
* StorageError`.
|
|
12
|
+
*
|
|
13
|
+
* Subclasses set `name` / `code` / `path` themselves. Construct via
|
|
14
|
+
* the subclasses (`StorageConflictError`, `StorageNotFoundError`,
|
|
15
|
+
* `StoragePermissionError`, `StorageQuotaError`) — callers and
|
|
16
|
+
* backends never instantiate `StorageError` directly.
|
|
17
|
+
*/
|
|
18
|
+
export declare class StorageError extends Error {
|
|
19
|
+
/** Discriminator. Set by each subclass to its matching code. */
|
|
20
|
+
readonly code: StorageErrorCode;
|
|
21
|
+
/** Path the failed operation was attempted on (image or folder). */
|
|
22
|
+
readonly path: string;
|
|
23
|
+
constructor(code: StorageErrorCode, path: string, message: string);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Destination path collides with an existing image or folder, and
|
|
27
|
+
* the operation does NOT auto-uniquify. Thrown by `createFolder`
|
|
28
|
+
* (duplicate name), `renameImage` / `renameFolder` / `moveFolder`
|
|
29
|
+
* (caller-picked name collides). `saveImage` and `moveImage`
|
|
30
|
+
* auto-uniquify and never throw this.
|
|
31
|
+
*/
|
|
32
|
+
export declare class StorageConflictError extends StorageError {
|
|
33
|
+
constructor(path: string, message?: string);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Source path for a `rename*` / `move*` no longer exists. Read
|
|
37
|
+
* methods (`getImage`, `getFolder`, `listImages`, `listFolders`,
|
|
38
|
+
* `getBreadcrumb`) and idempotent mutations (`updateImage`,
|
|
39
|
+
* `deleteImage`, `deleteFolder`) return `undefined` / `[]` /
|
|
40
|
+
* silently instead of throwing this.
|
|
41
|
+
*/
|
|
42
|
+
export declare class StorageNotFoundError extends StorageError {
|
|
43
|
+
constructor(path: string, message?: string);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Backend rejected the operation for auth / ACL reasons — expired
|
|
47
|
+
* GitHub token, revoked Drive scope, FSA permission lapse, etc.
|
|
48
|
+
* Any mutating `StorageProvider` method may throw this.
|
|
49
|
+
*/
|
|
50
|
+
export declare class StoragePermissionError extends StorageError {
|
|
51
|
+
constructor(path: string, message?: string);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Backend reports out-of-space or out-of-quota. Any mutating
|
|
55
|
+
* `StorageProvider` method may throw this.
|
|
56
|
+
*/
|
|
57
|
+
export declare class StorageQuotaError extends StorageError {
|
|
58
|
+
constructor(path: string, message?: string);
|
|
59
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { DocumentRecord, ImageRecord, StorageProvider } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* What kind of leaf entity a `ListingEntry` represents. `folder`
|
|
4
|
+
* means "this child path is a subfolder of the listing's
|
|
5
|
+
* `folderPath`"; consumers walk it for the folder tree, not for
|
|
6
|
+
* `getImage` / `getDocument` lookups.
|
|
7
|
+
*/
|
|
8
|
+
export type ListingEntryKind = "image" | "document" | "folder";
|
|
9
|
+
/**
|
|
10
|
+
* One entry in a folder's listing. `path` is the full path (not the
|
|
11
|
+
* leaf name); `version` is the opaque per-backend version string the
|
|
12
|
+
* cache uses to detect external mutation (`mtime` for Device/Desktop,
|
|
13
|
+
* blob SHA for GitHub, `modifiedTime` for Drive).
|
|
14
|
+
*/
|
|
15
|
+
export interface ListingEntry {
|
|
16
|
+
path: string;
|
|
17
|
+
version: string;
|
|
18
|
+
kind: ListingEntryKind;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Path → cached record + listing + per-namespace metadata store with
|
|
22
|
+
* version-gated hits. Lifecycle is shared across every opt-in
|
|
23
|
+
* `StorageProvider`; the providers themselves talk to this directly
|
|
24
|
+
* (unlike thumbnails, where a manager sits between the provider and
|
|
25
|
+
* the cache — metadata is intrinsic to `listImages` rather than a
|
|
26
|
+
* side-channel).
|
|
27
|
+
*
|
|
28
|
+
* Methods are organized by concern:
|
|
29
|
+
*
|
|
30
|
+
* - **Records**: per-path `ImageRecord` / `DocumentRecord` caches,
|
|
31
|
+
* version-gated so a stale entry is invisible to readers.
|
|
32
|
+
* - **Listing**: per-folder `ListingEntry[]` cache, used by
|
|
33
|
+
* `listImages` / `listDocuments` / `listFolders` so the store
|
|
34
|
+
* can return "what we know" before deciding whether to revalidate
|
|
35
|
+
* against the backend.
|
|
36
|
+
* - **Namespace meta**: per-namespace single-value KV store. Used
|
|
37
|
+
* for GitHub `branchHead` SHA tracking and Drive
|
|
38
|
+
* `changesPageToken` integration.
|
|
39
|
+
* - **Backend ID**: per-namespace bidirectional path ↔
|
|
40
|
+
* backend-internal-id map. Used by ID-based backends (Drive)
|
|
41
|
+
* where the change-tracking API speaks fileIds, not paths.
|
|
42
|
+
* - **Bulk operations**: cross-namespace migrations (rename,
|
|
43
|
+
* move-folder prefix rewrite) consolidated in one place rather
|
|
44
|
+
* than reimplemented per backend.
|
|
45
|
+
* - **Invalidation**: precise (`invalidatePath`) and broad
|
|
46
|
+
* (`invalidatePrefix`) eviction, used on `resync()` /
|
|
47
|
+
* `forceRefresh()` paths.
|
|
48
|
+
*/
|
|
49
|
+
export interface MetadataCache {
|
|
50
|
+
/**
|
|
51
|
+
* Returns the cached image record IFF its stored version matches
|
|
52
|
+
* `version`. Version mismatch returns `undefined` without
|
|
53
|
+
* disturbing the stored row — in a multi-tab world a peer may
|
|
54
|
+
* have written a NEWER version we don't yet know about, and
|
|
55
|
+
* silently evicting it here would discard valid data. Stale
|
|
56
|
+
* entries age out via LRU or are overwritten by the next `put`.
|
|
57
|
+
* `lastAccessedAt` is updated on hit so LRU eviction respects
|
|
58
|
+
* recent reads.
|
|
59
|
+
*/
|
|
60
|
+
getImage(ns: string, path: string, version: string): Promise<ImageRecord | undefined>;
|
|
61
|
+
/**
|
|
62
|
+
* Write-or-overwrite at `(ns, path)`. The `version` field becomes
|
|
63
|
+
* the new gate for future `getImage` calls. Implementations
|
|
64
|
+
* should handle quota exhaustion internally (one self-eviction
|
|
65
|
+
* sweep + retry, `clearAll` as last resort) and only throw
|
|
66
|
+
* `MetadataCacheQuotaError` when even that fails.
|
|
67
|
+
*/
|
|
68
|
+
putImage(ns: string, path: string, version: string, rec: ImageRecord): Promise<void>;
|
|
69
|
+
/** Document equivalent of {@link getImage}. */
|
|
70
|
+
getDocument(ns: string, path: string, version: string): Promise<DocumentRecord | undefined>;
|
|
71
|
+
/** Document equivalent of {@link putImage}. */
|
|
72
|
+
putDocument(ns: string, path: string, version: string, rec: DocumentRecord): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* Returns the cached listing for `folderPath` under `ns`, or
|
|
75
|
+
* `undefined` if no listing has been recorded. Listings are
|
|
76
|
+
* **not** version-gated at this layer — callers compare individual
|
|
77
|
+
* entry versions to decide whether to revalidate per-entry.
|
|
78
|
+
*/
|
|
79
|
+
getListing(ns: string, folderPath: string): Promise<ListingEntry[] | undefined>;
|
|
80
|
+
/**
|
|
81
|
+
* Replace the cached listing at `(ns, folderPath)` with `entries`.
|
|
82
|
+
* Used on cold-fetch or invalidate-all paths. Incremental updates
|
|
83
|
+
* (single add / version bump / remove) should prefer the
|
|
84
|
+
* targeted helpers below.
|
|
85
|
+
*/
|
|
86
|
+
putListing(ns: string, folderPath: string, entries: ListingEntry[]): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Add `entry` to the listing for `(ns, folderPath)`, or replace
|
|
89
|
+
* the existing entry with the same `path`. No-op if no listing
|
|
90
|
+
* has been recorded for the folder.
|
|
91
|
+
*/
|
|
92
|
+
upsertListingEntry(ns: string, folderPath: string, entry: ListingEntry): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* Remove the entry matching `path` from the listing for
|
|
95
|
+
* `(ns, folderPath)`. No-op if no listing or no matching entry.
|
|
96
|
+
*/
|
|
97
|
+
removeListingEntry(ns: string, folderPath: string, path: string): Promise<void>;
|
|
98
|
+
/**
|
|
99
|
+
* Read a single per-namespace value. Returns `undefined` when not
|
|
100
|
+
* set. Used today for:
|
|
101
|
+
*
|
|
102
|
+
* - GitHubStore's `branchHead` (current commit SHA of the
|
|
103
|
+
* tracked branch). On `init()` the store compares the live
|
|
104
|
+
* HEAD against this value; on match it skips the recursive
|
|
105
|
+
* tree fetch entirely.
|
|
106
|
+
* - GoogleDriveStore's `changesPageToken` (Drive Changes API
|
|
107
|
+
* resume token). On `init()` / `resync()` the store reads
|
|
108
|
+
* this token, applies changes since, advances the token.
|
|
109
|
+
*
|
|
110
|
+
* Treated as opaque strings by the cache.
|
|
111
|
+
*/
|
|
112
|
+
getNamespaceMeta(ns: string, key: string): Promise<string | undefined>;
|
|
113
|
+
/** Write-or-overwrite a per-namespace value. */
|
|
114
|
+
putNamespaceMeta(ns: string, key: string, value: string): Promise<void>;
|
|
115
|
+
/** Remove a per-namespace value. */
|
|
116
|
+
deleteNamespaceMeta(ns: string, key: string): Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* Record the path ↔ backend-internal-id mapping. Used by ID-based
|
|
119
|
+
* backends (Drive's fileId, future S3 / API-keyed stores) where
|
|
120
|
+
* the change-tracking API speaks IDs, not paths. The cache
|
|
121
|
+
* maintains both directions so `getBackendIdByPath` and
|
|
122
|
+
* `getPathByBackendId` are constant-time.
|
|
123
|
+
*/
|
|
124
|
+
setBackendId(ns: string, path: string, backendId: string): Promise<void>;
|
|
125
|
+
/** Forward lookup: path → backend ID. */
|
|
126
|
+
getBackendIdByPath(ns: string, path: string): Promise<string | undefined>;
|
|
127
|
+
/** Reverse lookup: backend ID → path. */
|
|
128
|
+
getPathByBackendId(ns: string, backendId: string): Promise<string | undefined>;
|
|
129
|
+
/**
|
|
130
|
+
* Move every cached artefact (record + listing entry + backend
|
|
131
|
+
* ID) from `oldPath` to `newPath` within the namespace.
|
|
132
|
+
* Generalizes the per-store rename/move handling that
|
|
133
|
+
* `GitHubBlobCache.migrateEntry` did for GitHubStore alone.
|
|
134
|
+
* Listing entries are updated in BOTH the source and destination
|
|
135
|
+
* folder's listings (remove from old, upsert into new).
|
|
136
|
+
*/
|
|
137
|
+
migrateEntry(ns: string, oldPath: string, newPath: string): Promise<void>;
|
|
138
|
+
/**
|
|
139
|
+
* Bulk-rewrite every cached artefact whose path starts with
|
|
140
|
+
* `oldPrefix` to start with `newPrefix` instead. Generalizes
|
|
141
|
+
* `GitHubBlobCache.rewriteEntriesForPrefix`. Used by folder
|
|
142
|
+
* rename / folder move operations.
|
|
143
|
+
*/
|
|
144
|
+
rewriteEntriesForPrefix(ns: string, oldPrefix: string, newPrefix: string): Promise<void>;
|
|
145
|
+
/** Targeted eviction: drop the record at `(ns, path)`. */
|
|
146
|
+
invalidatePath(ns: string, path: string): Promise<void>;
|
|
147
|
+
/**
|
|
148
|
+
* Drop every cached artefact whose key starts with `prefix`.
|
|
149
|
+
* Used by:
|
|
150
|
+
* - `StorageProvider.forceRefresh()` to invalidate a whole
|
|
151
|
+
* instance's namespace.
|
|
152
|
+
* - GitHubStore's branch-HEAD-mismatch handling (v1: drop the
|
|
153
|
+
* whole namespace, re-fetch from scratch).
|
|
154
|
+
* - Plugin uninstall — the host fires
|
|
155
|
+
* `invalidatePrefix("plugin:<pluginId>:")`.
|
|
156
|
+
*/
|
|
157
|
+
invalidatePrefix(prefix: string): Promise<void>;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Base error type implementations may throw out of mutating methods
|
|
161
|
+
* when their underlying transport fails irrecoverably. Stores that
|
|
162
|
+
* catch this should fall back to in-memory-only operation —
|
|
163
|
+
* functionality stays correct for the current session, just not
|
|
164
|
+
* across reload.
|
|
165
|
+
*/
|
|
166
|
+
export declare class MetadataCacheError extends Error {
|
|
167
|
+
constructor(message: string, options?: {
|
|
168
|
+
cause?: unknown;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Specific `MetadataCacheError` for quota-exhaustion paths.
|
|
173
|
+
* Implementations that distinguish quota from other I/O errors
|
|
174
|
+
* should throw this so the host can render quota-specific UI
|
|
175
|
+
* ("free up space") rather than the generic fallback.
|
|
176
|
+
*/
|
|
177
|
+
export declare class MetadataCacheQuotaError extends MetadataCacheError {
|
|
178
|
+
constructor(message?: string, options?: {
|
|
179
|
+
cause?: unknown;
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Capability interface — opt into the shared metadata cache by
|
|
184
|
+
* implementing both methods. Stores that don't implement this
|
|
185
|
+
* continue managing their own metadata internally
|
|
186
|
+
* (BrowserStore / IDBStore — already IDB-native and don't benefit
|
|
187
|
+
* from a second cache layer).
|
|
188
|
+
*
|
|
189
|
+
* The host wires the cache in at construction time via
|
|
190
|
+
* `attachMetadataCache(cache)`; the store reads its own namespace
|
|
191
|
+
* via `metadataNamespace()` so consumers (test helpers, `forceRefresh`
|
|
192
|
+
* implementations) can derive prefix-invalidation keys.
|
|
193
|
+
*/
|
|
194
|
+
export interface StorageWithMetadataCache {
|
|
195
|
+
/**
|
|
196
|
+
* Return the per-instance namespace prefix used to scope this
|
|
197
|
+
* store's cache entries. Examples:
|
|
198
|
+
*
|
|
199
|
+
* - DeviceStore: `"device:my-screenshots"` (root folder name)
|
|
200
|
+
* - DesktopStore: `"desktop:default-library"`
|
|
201
|
+
* - GitHubStore: `"github:owner/repo:main"` (owner / repo / branch)
|
|
202
|
+
* - GoogleDriveStore: `"googledrive:<rootFolderId>"`
|
|
203
|
+
* - Plugin-registered: `"plugin:<pluginId>:..."` — the rest of
|
|
204
|
+
* the suffix is the plugin's choice; the `plugin:<pluginId>:`
|
|
205
|
+
* prefix is enforced by the host at registration time.
|
|
206
|
+
*
|
|
207
|
+
* Stable across resync. Implementations MUST NOT bake mutable
|
|
208
|
+
* state into the namespace (e.g. don't append a "session ID" or
|
|
209
|
+
* "build hash") — invalidation relies on prefix stability across
|
|
210
|
+
* sessions.
|
|
211
|
+
*/
|
|
212
|
+
metadataNamespace(): string;
|
|
213
|
+
/**
|
|
214
|
+
* Receive the `MetadataCache` instance from the host at
|
|
215
|
+
* construction time. After this call, the store may issue cache
|
|
216
|
+
* operations from any of its public methods.
|
|
217
|
+
*
|
|
218
|
+
* Called exactly once per store instance, before any
|
|
219
|
+
* `StorageProvider` method. Stores that need to perform initial
|
|
220
|
+
* cache reads (e.g. seeding their in-memory shortcuts from
|
|
221
|
+
* `getNamespaceMeta`) should do so in `init()`, not here.
|
|
222
|
+
*/
|
|
223
|
+
attachMetadataCache(cache: MetadataCache): void;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Capability predicate — narrow a `StorageProvider` to
|
|
227
|
+
* `StorageProvider & StorageWithMetadataCache` before invoking
|
|
228
|
+
* the optional methods. Mirrors the `supports*` helpers next to
|
|
229
|
+
* the other `StorageWith*` interfaces in `./types.ts`.
|
|
230
|
+
*/
|
|
231
|
+
export declare function supportsMetadataCache(store: StorageProvider): store is StorageProvider & StorageWithMetadataCache;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path utilities for filesystem-style storage identifiers.
|
|
3
|
+
*
|
|
4
|
+
* Paths are POSIX-style, separated by "/". Never leading or trailing "/".
|
|
5
|
+
* Root path is "" (empty string). Reserved segments "." and ".." are invalid.
|
|
6
|
+
*/
|
|
7
|
+
/** Empty string represents the root folder. */
|
|
8
|
+
export declare const ROOT_PATH = "";
|
|
9
|
+
/**
|
|
10
|
+
* Validate a single path segment (folder or filename).
|
|
11
|
+
* Throws if the name is empty, is "." / "..", or contains reserved characters.
|
|
12
|
+
*/
|
|
13
|
+
export declare function validateName(name: string): void;
|
|
14
|
+
/**
|
|
15
|
+
* Join a parent path with a single name.
|
|
16
|
+
* Throws if `name` contains "/".
|
|
17
|
+
*/
|
|
18
|
+
export declare function joinPath(parent: string, name: string): string;
|
|
19
|
+
/** Return the parent folder's path. Root path's parent is "" (root). */
|
|
20
|
+
export declare function getParentPath(path: string): string;
|
|
21
|
+
/** Return the last segment (folder name or filename) from a path. */
|
|
22
|
+
export declare function getFilename(path: string): string;
|
|
23
|
+
/** Split a path into its segments. Returns [] for the root path. */
|
|
24
|
+
export declare function splitPath(path: string): string[];
|
|
25
|
+
/**
|
|
26
|
+
* Return all ancestor paths from root down to (but not including) `path`.
|
|
27
|
+
* Example: `ancestorPaths("A/B/C") = ["A", "A/B"]`.
|
|
28
|
+
*/
|
|
29
|
+
export declare function ancestorPaths(path: string): string[];
|
|
30
|
+
/**
|
|
31
|
+
* Return `true` if `path` is equal to `ancestor` or is nested inside it.
|
|
32
|
+
* Example: `isDescendantOrSame("A/B/C", "A") === true`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function isDescendantOrSame(path: string, ancestor: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Split a filename into base + extension. Extension includes the leading ".".
|
|
37
|
+
* Example: `splitExt("image.tar.gz") = ["image.tar", ".gz"]`.
|
|
38
|
+
* Files without extension return `[name, ""]`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function splitExt(filename: string): [string, string];
|
|
41
|
+
/**
|
|
42
|
+
* Return a unique filename within its folder by appending " (2)", " (3)", ...
|
|
43
|
+
* before the extension. If `desired` is already free, returns it unchanged.
|
|
44
|
+
*/
|
|
45
|
+
export declare function uniquifyFilename(desired: string, exists: (candidate: string) => boolean): string;
|
|
46
|
+
/** Async variant of `uniquifyFilename` for stores that need async existence checks. */
|
|
47
|
+
export declare function uniquifyFilenameAsync(desired: string, exists: (candidate: string) => Promise<boolean>): Promise<string>;
|
|
48
|
+
/** Rewrite a child path when its ancestor folder is renamed/moved. */
|
|
49
|
+
export declare function rewritePathPrefix(path: string, oldPrefix: string, newPrefix: string): string;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persistent thumbnail cache contract — Tier A, DOM-free.
|
|
3
|
+
*
|
|
4
|
+
* Owns a key-versioned key/value store of pre-rendered gallery
|
|
5
|
+
* thumbnails (`dataUrl` + natural dimensions). Implementations
|
|
6
|
+
* decide where the bytes live: the first-party
|
|
7
|
+
* `IndexedDBThumbnailCache` in `@ingcreators/annot-web/storage/
|
|
8
|
+
* idb-thumbnail-cache` is the canonical browser-side option;
|
|
9
|
+
* tests / Node hosts can swap in an in-memory mock with the
|
|
10
|
+
* same surface.
|
|
11
|
+
*
|
|
12
|
+
* The unified-thumbnail-cache plan
|
|
13
|
+
* ([`docs/plans/_done/unified-thumbnail-cache.md`](../../../../docs/plans/_done/unified-thumbnail-cache.md))
|
|
14
|
+
* lifts the per-store `#ensureThumbnail` lifecycle into a single
|
|
15
|
+
* host-side `ThumbnailManager` that talks to one `ThumbnailCache`
|
|
16
|
+
* instance shared across every backend; this file is the Tier A
|
|
17
|
+
* piece that contract.
|
|
18
|
+
*
|
|
19
|
+
* Keys carry a host-reserved namespace prefix matching the
|
|
20
|
+
* `StorageMode` strings used elsewhere in the app (`browser:` /
|
|
21
|
+
* `device:` / `github:` / `googledrive:`); plugin-registered
|
|
22
|
+
* providers must use the `plugin:<pluginId>:` prefix the host
|
|
23
|
+
* enforces at registration time. See the plan doc for the full
|
|
24
|
+
* convention.
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* One cached thumbnail. JPEG-encoded `dataUrl` (already base64
|
|
28
|
+
* via the standard `image-thumbnail.ts` helper) plus the source
|
|
29
|
+
* image's natural dimensions — needed by the gallery card's
|
|
30
|
+
* `WxH • date` line so it doesn't have to wait on a separate
|
|
31
|
+
* decode round-trip after a cache hit.
|
|
32
|
+
*/
|
|
33
|
+
export interface CachedThumbnail {
|
|
34
|
+
dataUrl: string;
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
}
|
|
38
|
+
/** One bulk-`get` request — the key plus the version the caller
|
|
39
|
+
* considers fresh. Mismatches are evicted, not returned. */
|
|
40
|
+
export interface ThumbnailCacheGetRequest {
|
|
41
|
+
key: string;
|
|
42
|
+
expectedVersion: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Key/value store of `CachedThumbnail` entries with version-gated
|
|
46
|
+
* cache hits. Lifecycle owned by the host-side `ThumbnailManager`;
|
|
47
|
+
* `StorageProvider` implementations don't talk to this directly,
|
|
48
|
+
* they expose `thumbnailKey` / `thumbnailVersion` /
|
|
49
|
+
* `fetchThumbnailSource` and let the manager drive the cache.
|
|
50
|
+
*/
|
|
51
|
+
export interface ThumbnailCache {
|
|
52
|
+
/**
|
|
53
|
+
* Returns the cached entry IFF its stored version matches
|
|
54
|
+
* `expectedVersion`. Stale entries (version mismatch) are
|
|
55
|
+
* evicted as a side effect of `get` — the next caller sees a
|
|
56
|
+
* cold miss. `lastAccessedAt` is updated on hit so LRU
|
|
57
|
+
* eviction respects recent reads.
|
|
58
|
+
*/
|
|
59
|
+
get(key: string, expectedVersion: string): Promise<CachedThumbnail | undefined>;
|
|
60
|
+
/**
|
|
61
|
+
* Bulk get — single transaction instead of one per record.
|
|
62
|
+
* Used by `ThumbnailManager.attach` against a `listImages`
|
|
63
|
+
* result so cold listings of a populated gallery don't fan
|
|
64
|
+
* out into N IDB round-trips.
|
|
65
|
+
*
|
|
66
|
+
* The returned `Map` keys are the request keys (so callers
|
|
67
|
+
* can correlate). Misses are simply absent.
|
|
68
|
+
*/
|
|
69
|
+
getMany(requests: ThumbnailCacheGetRequest[]): Promise<Map<string, CachedThumbnail>>;
|
|
70
|
+
/**
|
|
71
|
+
* Write or overwrite at `key`. Updates the entry's
|
|
72
|
+
* `lastAccessedAt`. Implementations should handle quota
|
|
73
|
+
* exhaustion internally (one self-eviction sweep + retry,
|
|
74
|
+
* `clearAll` as last resort) and only throw
|
|
75
|
+
* `ThumbnailCacheQuotaError` when even that fails.
|
|
76
|
+
*/
|
|
77
|
+
set(key: string, version: string, value: CachedThumbnail): Promise<void>;
|
|
78
|
+
/** Evict by exact key. */
|
|
79
|
+
delete(key: string): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Drop every entry whose key starts with `prefix`. Used by:
|
|
82
|
+
* - `StorageProvider.resync()` to invalidate a whole instance's
|
|
83
|
+
* namespace without enumerating individual keys.
|
|
84
|
+
* - Plugin uninstall — the host fires
|
|
85
|
+
* `deletePrefix("plugin:<pluginId>:")`.
|
|
86
|
+
*/
|
|
87
|
+
deletePrefix(prefix: string): Promise<void>;
|
|
88
|
+
/** Quota-recovery hatch. Drops everything. */
|
|
89
|
+
clearAll(): Promise<void>;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Base error type implementations may throw out of `set` once
|
|
93
|
+
* eviction + retry both fail. `ThumbnailManager` swallows this
|
|
94
|
+
* and falls back to in-memory only — the gallery still gets the
|
|
95
|
+
* thumbnail for the current session, just not across reload.
|
|
96
|
+
*/
|
|
97
|
+
export declare class ThumbnailCacheError extends Error {
|
|
98
|
+
constructor(message: string, options?: {
|
|
99
|
+
cause?: unknown;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/** Specific `ThumbnailCacheError` for quota-exhaustion paths.
|
|
103
|
+
* Implementations that distinguish quota from other I/O errors
|
|
104
|
+
* should throw this so callers can apply quota-specific UI
|
|
105
|
+
* ("free up space") rather than the generic fallback. */
|
|
106
|
+
export declare class ThumbnailCacheQuotaError extends ThumbnailCacheError {
|
|
107
|
+
constructor(message?: string, options?: {
|
|
108
|
+
cause?: unknown;
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared thumbnail rendering helper.
|
|
3
|
+
*
|
|
4
|
+
* The gallery card is a fixed 16:9 tile. Scaling a short/wide source image
|
|
5
|
+
* up to fill that tile (as `object-fit: cover` would do) makes the image
|
|
6
|
+
* blurry. Instead, every storage provider's `generateThumbnail()` draws
|
|
7
|
+
* onto a fixed-aspect (16:9) canvas with the following rules:
|
|
8
|
+
*
|
|
9
|
+
* - Never upscale. If the source is smaller than the canvas, it stays
|
|
10
|
+
* at natural size and the gaps are filled with the `bgColor`.
|
|
11
|
+
* - For source wider than 16:9 (or exactly 16:9): fit width,
|
|
12
|
+
* letterbox vertically when shorter than the canvas height.
|
|
13
|
+
* - For source taller than 16:9 (portrait screenshots, scrollshots):
|
|
14
|
+
* fit width, take only the top portion so the canvas shows the
|
|
15
|
+
* "head" of the page — matching typical screenshot gallery UX.
|
|
16
|
+
*
|
|
17
|
+
* Works with both `HTMLCanvasElement` (main-thread web) and
|
|
18
|
+
* `OffscreenCanvas` (service worker / fs-store). Caller passes in the
|
|
19
|
+
* source's natural width/height so we don't assume an `HTMLImageElement`.
|
|
20
|
+
*/
|
|
21
|
+
export interface ThumbCanvasLike {
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
}
|
|
25
|
+
export type ThumbImageSource = HTMLImageElement | CanvasImageSource;
|
|
26
|
+
/**
|
|
27
|
+
* Configure `canvas` size, clear the background, and draw `source` onto it
|
|
28
|
+
* following the rules above. The canvas ends up exactly `targetW × targetH`
|
|
29
|
+
* where targetW = maxWidth and targetH = round(targetW * 9/16).
|
|
30
|
+
*/
|
|
31
|
+
export declare function drawToThumbCanvas(ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, canvas: ThumbCanvasLike, source: ThumbImageSource, srcW: number, srcH: number, maxWidth?: number, bgColor?: string): void;
|