@openrails/contentkit-ui 0.68.0 → 0.69.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/README.md +55 -10
- package/dist/client/client.d.ts +4 -2
- package/dist/client/client.js +1 -0
- package/dist/client/client.js.map +1 -1
- package/dist/client/content/bans.js +7 -7
- package/dist/client/content/bans.js.map +1 -1
- package/dist/client/content/comments.js +16 -14
- package/dist/client/content/comments.js.map +1 -1
- package/dist/client/content/favorites.js +5 -5
- package/dist/client/content/favorites.js.map +1 -1
- package/dist/client/content/moderation.js +5 -5
- package/dist/client/content/moderation.js.map +1 -1
- package/dist/client/content/polls.js +34 -31
- package/dist/client/content/polls.js.map +1 -1
- package/dist/client/content/posts.js +21 -20
- package/dist/client/content/posts.js.map +1 -1
- package/dist/client/content/reactions.js +4 -4
- package/dist/client/content/reactions.js.map +1 -1
- package/dist/client/generated/error-codes.d.ts +1 -1
- package/dist/client/generated/error-codes.js +4 -0
- package/dist/client/generated/error-codes.js.map +1 -1
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/http.d.ts +6 -2
- package/dist/client/http.js +11 -3
- package/dist/client/http.js.map +1 -1
- package/dist/client/media/client.js +24 -24
- package/dist/client/media/client.js.map +1 -1
- package/dist/client/media/rules.d.ts +2 -0
- package/dist/client/media/rules.js +6 -2
- package/dist/client/media/rules.js.map +1 -1
- package/dist/client/taxonomy.js +2 -2
- package/dist/client/taxonomy.js.map +1 -1
- package/dist/components/media-folder-editor.d.ts +17 -3
- package/dist/components/media-folder-editor.js +146 -99
- package/dist/components/media-folder-editor.js.map +1 -1
- package/dist/components/slot-upload.js +102 -100
- package/dist/components/slot-upload.js.map +1 -1
- package/dist/locales/de.js +1 -0
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/en.d.ts +1 -0
- package/dist/locales/en.js +1 -0
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +1 -0
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/ja.js +1 -0
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +1 -0
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/zh.js +1 -0
- package/dist/locales/zh.js.map +1 -1
- package/dist/react/canonical.d.ts +2 -2
- package/dist/react/canonical.js +2 -12
- package/dist/react/canonical.js.map +1 -1
- package/dist/react/comments.js +35 -27
- package/dist/react/comments.js.map +1 -1
- package/dist/react/context.d.ts +7 -0
- package/dist/react/context.js +20 -15
- package/dist/react/context.js.map +1 -1
- package/dist/react/engagement.js +2 -2
- package/dist/react/engagement.js.map +1 -1
- package/dist/react/folder.d.ts +29 -9
- package/dist/react/folder.js +170 -113
- package/dist/react/folder.js.map +1 -1
- package/dist/react/polls.js +3 -3
- package/dist/react/polls.js.map +1 -1
- package/dist/react/posts.js +2 -2
- package/dist/react/posts.js.map +1 -1
- package/dist/react/provider.d.ts +7 -3
- package/dist/react/provider.js +34 -20
- package/dist/react/provider.js.map +1 -1
- package/dist/react/public.js +11 -11
- package/dist/react/public.js.map +1 -1
- package/dist/react/read.js +93 -92
- package/dist/react/read.js.map +1 -1
- package/dist/react/resources.d.ts +6 -3
- package/dist/react/resources.js +40 -40
- package/dist/react/resources.js.map +1 -1
- package/dist/react/slot.d.ts +4 -2
- package/dist/react/slot.js +96 -92
- package/dist/react/slot.js.map +1 -1
- package/dist/react/store.d.ts +1 -1
- package/dist/react/store.js +21 -20
- package/dist/react/store.js.map +1 -1
- package/dist/react/upload.d.ts +3 -2
- package/dist/react/upload.js +20 -15
- package/dist/react/upload.js.map +1 -1
- package/dist/react/use-resource.d.ts +1 -0
- package/dist/react/use-resource.js +38 -30
- package/dist/react/use-resource.js.map +1 -1
- package/dist/react.js +15 -15
- package/package.json +7 -5
package/dist/react/resources.js
CHANGED
|
@@ -6,8 +6,9 @@ var t = {
|
|
|
6
6
|
loaded: !1
|
|
7
7
|
}, n = (e, t) => !!e && !!t && e.kind === t.kind && e.id === t.id, r = class {
|
|
8
8
|
slots = /* @__PURE__ */ new Map();
|
|
9
|
+
version = 0;
|
|
9
10
|
constructor(e) {
|
|
10
|
-
e.subscribe((e) => this.apply(e));
|
|
11
|
+
e.subscribe((e, t) => this.apply(e, t));
|
|
11
12
|
}
|
|
12
13
|
subscribe(e, n, r) {
|
|
13
14
|
let i = this.slots.get(e);
|
|
@@ -15,7 +16,8 @@ var t = {
|
|
|
15
16
|
tag: n,
|
|
16
17
|
listeners: /* @__PURE__ */ new Set(),
|
|
17
18
|
entry: t,
|
|
18
|
-
version: 0
|
|
19
|
+
version: 0,
|
|
20
|
+
readVersion: ++this.version
|
|
19
21
|
});
|
|
20
22
|
let a = i;
|
|
21
23
|
return a.listeners.add(r), () => {
|
|
@@ -44,7 +46,7 @@ var t = {
|
|
|
44
46
|
loadingMore: !1,
|
|
45
47
|
error: void 0
|
|
46
48
|
}), r.fetcher(i.signal).then((e) => {
|
|
47
|
-
r.ctl === i && (r.ctl = void 0, this.update(r, {
|
|
49
|
+
r.ctl === i && (r.ctl = void 0, r.readVersion = ++this.version, this.update(r, {
|
|
48
50
|
data: e,
|
|
49
51
|
loading: !1,
|
|
50
52
|
loaded: !0,
|
|
@@ -88,7 +90,7 @@ var t = {
|
|
|
88
90
|
let n = this.slots.get(e);
|
|
89
91
|
if (!n) return;
|
|
90
92
|
let r = typeof t == "function" ? t(n.entry.data) : t;
|
|
91
|
-
this.update(n, {
|
|
93
|
+
n.readVersion = ++this.version, this.update(n, {
|
|
92
94
|
data: r,
|
|
93
95
|
loaded: !0,
|
|
94
96
|
error: void 0
|
|
@@ -96,7 +98,7 @@ var t = {
|
|
|
96
98
|
}
|
|
97
99
|
mark(e) {
|
|
98
100
|
let t = this.slots.get(e);
|
|
99
|
-
return t ?
|
|
101
|
+
return t ? t.version = ++this.version : 0;
|
|
100
102
|
}
|
|
101
103
|
rollback(e, t, n) {
|
|
102
104
|
let r = this.slots.get(e);
|
|
@@ -106,13 +108,15 @@ var t = {
|
|
|
106
108
|
});
|
|
107
109
|
}
|
|
108
110
|
patch(e, t) {
|
|
109
|
-
for (let n of this.slots.values()) n.entry.data !== void 0 && e(n.tag) && this.update(n, { data: t(n.entry.data) });
|
|
111
|
+
for (let n of this.slots.values()) n.entry.data !== void 0 && e(n.tag) && this.update(n, { data: t(n.entry.data, n.readVersion) });
|
|
110
112
|
}
|
|
111
113
|
invalidate(e) {
|
|
112
114
|
for (let [t, n] of this.slots) e(n.tag) && n.listeners.size && this.load(t, !0);
|
|
113
115
|
}
|
|
114
|
-
apply(e) {
|
|
115
|
-
let
|
|
116
|
+
apply(e, t) {
|
|
117
|
+
let r = (e, n) => {
|
|
118
|
+
this.patch((n) => e(n) && (n.readScope ?? "{}") === t, n), this.invalidate((n) => e(n) && (n.readScope ?? "{}") !== t);
|
|
119
|
+
}, i = (e, t) => r((t) => e.includes(t.type), (e) => {
|
|
116
120
|
let n = !1, r = [];
|
|
117
121
|
for (let i of e.items) {
|
|
118
122
|
let e = t(i);
|
|
@@ -122,7 +126,7 @@ var t = {
|
|
|
122
126
|
...e,
|
|
123
127
|
items: r
|
|
124
128
|
} : e;
|
|
125
|
-
}),
|
|
129
|
+
}), a = [
|
|
126
130
|
"comments",
|
|
127
131
|
"replies",
|
|
128
132
|
"latest",
|
|
@@ -130,42 +134,42 @@ var t = {
|
|
|
130
134
|
];
|
|
131
135
|
switch (e.type) {
|
|
132
136
|
case "comment.created": {
|
|
133
|
-
let
|
|
134
|
-
|
|
135
|
-
...
|
|
136
|
-
items:
|
|
137
|
-
}),
|
|
137
|
+
let t = e.comment.reply_to_id, a = !e.comment.moderation;
|
|
138
|
+
r((r) => t ? r.type === "replies" && r.id === t : r.type === "comments" && n(r.ref, e.ref), (n) => n.items.some((t) => t.id === e.comment.id) ? n : {
|
|
139
|
+
...n,
|
|
140
|
+
items: t ? [...n.items, e.comment] : [e.comment, ...n.items]
|
|
141
|
+
}), t && a && i(["comments"], (e) => e.id === t ? {
|
|
138
142
|
...e,
|
|
139
143
|
reply_count: e.reply_count + 1
|
|
140
144
|
} : e), this.invalidate((e) => e.type === "latest" || e.type === "admin-comments");
|
|
141
145
|
break;
|
|
142
146
|
}
|
|
143
147
|
case "comment.updated":
|
|
144
|
-
|
|
148
|
+
i(a, (t) => t.id === e.comment.id ? {
|
|
145
149
|
...t,
|
|
146
150
|
...e.comment,
|
|
147
151
|
author: e.comment.author ?? t.author
|
|
148
152
|
} : t);
|
|
149
153
|
break;
|
|
150
154
|
case "comment.reacted":
|
|
151
|
-
|
|
155
|
+
i(a, (t) => t.id === e.id ? {
|
|
152
156
|
...t,
|
|
153
157
|
...e.counts
|
|
154
158
|
} : t);
|
|
155
159
|
break;
|
|
156
160
|
case "comment.deleted": {
|
|
157
|
-
let
|
|
158
|
-
|
|
159
|
-
...
|
|
161
|
+
let t;
|
|
162
|
+
i(["comments", "replies"], (n) => n.id !== e.id || n.deleted ? n : (n.moderation || (t = n.reply_to_id), {
|
|
163
|
+
...n,
|
|
160
164
|
deleted: !0,
|
|
161
165
|
body: "[deleted]",
|
|
162
166
|
author: void 0,
|
|
163
167
|
user_id: void 0,
|
|
164
168
|
anon_name: void 0
|
|
165
|
-
})),
|
|
169
|
+
})), t && i(["comments"], (e) => e.id === t ? {
|
|
166
170
|
...e,
|
|
167
171
|
reply_count: Math.max(0, e.reply_count - 1)
|
|
168
|
-
} : e),
|
|
172
|
+
} : e), i(["latest"], (t) => t.id === e.id ? null : t), i(["admin-comments"], (t) => t.id === e.id ? {
|
|
169
173
|
...t,
|
|
170
174
|
deleted: !0,
|
|
171
175
|
deleted_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -173,13 +177,20 @@ var t = {
|
|
|
173
177
|
break;
|
|
174
178
|
}
|
|
175
179
|
case "comment.restored":
|
|
176
|
-
this.invalidate((e) =>
|
|
180
|
+
this.invalidate((e) => a.includes(e.type));
|
|
177
181
|
break;
|
|
178
182
|
case "reaction.changed":
|
|
179
|
-
|
|
183
|
+
if (r((t) => t.type === "reaction" && n(t.ref, e.ref), () => e.counts), e.ref.kind === "post") {
|
|
184
|
+
let t = (t) => t.id === e.ref.id ? {
|
|
185
|
+
...t,
|
|
186
|
+
total_likes: e.counts.likes,
|
|
187
|
+
total_dislikes: e.counts.dislikes
|
|
188
|
+
} : t;
|
|
189
|
+
r((t) => t.type === "post" && t.id === e.ref.id, t), i(["posts"], t);
|
|
190
|
+
}
|
|
180
191
|
break;
|
|
181
192
|
case "favorite.changed":
|
|
182
|
-
|
|
193
|
+
r((t) => t.type === "favorite" && n(t.ref, e.ref), () => ({
|
|
183
194
|
favorited: e.favorited,
|
|
184
195
|
count: e.count
|
|
185
196
|
})), this.invalidate((e) => e.type === "favorites");
|
|
@@ -188,13 +199,13 @@ var t = {
|
|
|
188
199
|
this.invalidate((e) => e.type === "posts");
|
|
189
200
|
break;
|
|
190
201
|
case "post.updated":
|
|
191
|
-
|
|
202
|
+
r((e) => e.type === "post", (t) => t?.id === e.post.id ? e.post : t), i(["posts"], (t) => t.id === e.post.id ? e.post : t);
|
|
192
203
|
break;
|
|
193
204
|
case "post.changed":
|
|
194
205
|
this.invalidate((t) => t.type === "post" && t.id === e.id || t.type === "posts");
|
|
195
206
|
break;
|
|
196
207
|
case "post.deleted":
|
|
197
|
-
|
|
208
|
+
i(["posts"], (t) => t.id === e.id ? null : t), this.invalidate((e) => e.type === "posts" && e.scope === "deleted");
|
|
198
209
|
break;
|
|
199
210
|
case "post.restored":
|
|
200
211
|
this.invalidate((t) => t.type === "post" && t.id === e.post.id || t.type === "posts");
|
|
@@ -203,32 +214,21 @@ var t = {
|
|
|
203
214
|
this.invalidate((e) => e.type === "polls" || e.type === "poll" && !e.id);
|
|
204
215
|
break;
|
|
205
216
|
case "poll.updated":
|
|
206
|
-
|
|
217
|
+
r((e) => e.type === "poll", (t) => t?.id === e.poll.id ? e.poll : t), i(["polls"], (t) => t.id === e.poll.id ? e.poll : t);
|
|
207
218
|
break;
|
|
208
219
|
case "poll.changed":
|
|
209
220
|
this.invalidate((t) => t.type === "poll" && (t.id === e.id || !t.id) || t.type === "polls");
|
|
210
221
|
break;
|
|
211
222
|
case "poll.deleted":
|
|
212
|
-
|
|
223
|
+
i(["polls"], (t) => t.id === e.id ? null : t), this.invalidate((e) => e.type === "poll" && !e.id);
|
|
213
224
|
break;
|
|
214
225
|
case "ban.saved":
|
|
215
226
|
case "ban.lifted":
|
|
216
227
|
this.invalidate((t) => t.type === "bans" && t.scope === e.scope || t.type === "standing");
|
|
217
228
|
break;
|
|
218
|
-
case "moderation.resolved":
|
|
229
|
+
case "moderation.resolved": i(["held"], (t) => t.id === e.id ? null : t), e.kind === "comment" ? this.invalidate((e) => a.includes(e.type)) : this.invalidate((t) => t.type === "post" && t.id === e.id || t.type === "posts");
|
|
219
230
|
}
|
|
220
231
|
}
|
|
221
|
-
postTotals(e, t, n) {
|
|
222
|
-
let r = (r) => r.id === e ? {
|
|
223
|
-
...r,
|
|
224
|
-
total_likes: t,
|
|
225
|
-
total_dislikes: n
|
|
226
|
-
} : r;
|
|
227
|
-
this.patch((t) => t.type === "post" && t.id === e, r), this.patch((e) => e.type === "posts", (e) => ({
|
|
228
|
-
...e,
|
|
229
|
-
items: e.items.map(r)
|
|
230
|
-
}));
|
|
231
|
-
}
|
|
232
232
|
update(e, t) {
|
|
233
233
|
e.entry = {
|
|
234
234
|
...e.entry,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resources.js","names":[],"sources":["../../src/react/resources.ts"],"sourcesContent":["import type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Comment, Poll, Post, RefBody } from \"../client/generated/wire.js\";\nimport type { ContentKitChange } from \"../client/http.js\";\n\n/** One shared read: its data, whether a fetch is in flight, and its last failure. */\nexport interface Resource<T> {\n data: T | undefined;\n loading: boolean;\n /** A fetch or set has completed. */\n loaded: boolean;\n /** A list's next page is in flight. */\n loadingMore?: boolean;\n error?: ContentKitError;\n}\n\n/** A page of a list and how to get the next one (null: no more). */\nexport interface Page<T> {\n items: T[];\n next: string | number | null;\n}\n\nexport type Fetcher<T> = (signal: AbortSignal) => Promise<T>;\n\n/** What a slot holds, for matching changes: its resource type and the ids it depends on. */\nexport interface Tag {\n type: string;\n ref?: RefBody;\n id?: string;\n scope?: string;\n}\n\ninterface Slot {\n tag: Tag;\n listeners: Set<() => void>;\n entry: Resource<unknown>;\n fetcher?: Fetcher<unknown>;\n more?: (cursor: string | number, signal: AbortSignal) => Promise<Page<unknown>>;\n ctl?: AbortController;\n moreCtl?: AbortController;\n /** Bumped by mark(): an optimistic write rolls back only if no newer one started. */\n version: number;\n}\n\nconst IDLE: Resource<never> = { data: undefined, loading: false, loaded: false };\n\nconst sameRef = (a?: RefBody, b?: RefBody) => !!a && !!b && a.kind === b.kind && a.id === b.id;\n\n/**\n * The content reads hooks share, per client: one request per key while any\n * hook shows it, optimistic writes with rollback, and every change the client\n * makes applied in place (or refetched) for every hook showing it.\n */\nexport class ResourceStore {\n private readonly slots = new Map<string, Slot>();\n\n constructor(client: ContentKitClient) {\n client.subscribe((c) => this.apply(c));\n }\n\n subscribe(key: string, tag: Tag, listener: () => void): () => void {\n let s = this.slots.get(key);\n if (!s) this.slots.set(key, (s = { tag, listeners: new Set(), entry: IDLE, version: 0 }));\n const slot = s;\n slot.listeners.add(listener);\n return () => {\n slot.listeners.delete(listener);\n // Kept across a StrictMode remount; dropped once no hook shows it.\n queueMicrotask(() => {\n if (slot.listeners.size || this.slots.get(key) !== slot) return;\n slot.ctl?.abort();\n slot.moreCtl?.abort();\n this.slots.delete(key);\n });\n };\n }\n\n snapshot<T>(key: string): Resource<T> {\n return (this.slots.get(key)?.entry ?? IDLE) as Resource<T>;\n }\n\n /** The latest fetchers of a key: its first read, and a list's next page. */\n bind(key: string, fetcher: Fetcher<unknown>, more?: Slot[\"more\"]): void {\n const s = this.slots.get(key);\n if (s) Object.assign(s, { fetcher, more });\n }\n\n /** Fetches unless loaded or in flight; force refetches (a list from its first page). */\n load(key: string, force = false): void {\n const s = this.slots.get(key);\n if (!s?.fetcher || (!force && (s.entry.loaded || s.ctl))) return;\n s.ctl?.abort();\n s.moreCtl?.abort();\n s.moreCtl = undefined;\n const ctl = (s.ctl = new AbortController());\n this.update(s, { loading: true, loadingMore: false, error: undefined });\n s.fetcher(ctl.signal).then(\n (data) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n this.update(s, { data, loading: false, loaded: true, error: undefined });\n },\n (e) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n const error = toContentKitError(e);\n if (error.code !== \"aborted\") this.update(s, { loading: false, loaded: true, error });\n },\n );\n }\n\n /** Appends a list's next page; resolves once it landed. */\n async loadMore(key: string): Promise<void> {\n const s = this.slots.get(key);\n const page = s?.entry.data as Page<{ id: string }> | undefined;\n if (!s?.more || !page || page.next === null || s.moreCtl || s.ctl) return;\n const ctl = (s.moreCtl = new AbortController());\n this.update(s, { loadingMore: true });\n try {\n const next = (await s.more(page.next, ctl.signal)) as Page<{ id: string }>;\n if (s.moreCtl !== ctl) return;\n const cur = s.entry.data as Page<{ id: string }>;\n const seen = new Set(cur.items.map((x) => x.id));\n this.update(s, { data: { items: [...cur.items, ...next.items.filter((x) => !seen.has(x.id))], next: next.next }, error: undefined });\n } catch (e) {\n const error = toContentKitError(e);\n if (s.moreCtl === ctl && error.code !== \"aborted\") this.update(s, { error });\n } finally {\n if (s.moreCtl === ctl) {\n s.moreCtl = undefined;\n this.update(s, { loadingMore: false });\n }\n }\n }\n\n /** Replaces a key's data for every hook showing it. */\n set<T>(key: string, data: T | ((prev: T | undefined) => T)): void {\n const s = this.slots.get(key);\n if (!s) return;\n const next = typeof data === \"function\" ? (data as (p: T | undefined) => T)(s.entry.data as T | undefined) : data;\n this.update(s, { data: next, loaded: true, error: undefined });\n }\n\n /** Starts an optimistic write on key: the token rollback() checks. */\n mark(key: string): number {\n const s = this.slots.get(key);\n return s ? ++s.version : 0;\n }\n\n /** Restores data unless a newer optimistic write started since mark(). */\n rollback<T>(key: string, token: number, data: T): void {\n const s = this.slots.get(key);\n if (s && s.version === token) this.update(s, { data, loaded: true });\n }\n\n /** Applies fn to the data of every slot whose tag matches. */\n patch<T>(match: (tag: Tag) => boolean, fn: (data: T) => T): void {\n for (const s of this.slots.values()) {\n if (s.entry.data !== undefined && match(s.tag)) this.update(s, { data: fn(s.entry.data as T) });\n }\n }\n\n /** Refetches every shown slot whose tag matches. */\n invalidate(match: (tag: Tag) => boolean): void {\n for (const [key, s] of this.slots) if (match(s.tag) && s.listeners.size) this.load(key, true);\n }\n\n private apply(c: ContentKitChange): void {\n const items = <T extends { id: string }>(types: string[], fn: (item: T) => T | null) =>\n this.patch<Page<T>>(\n (t) => types.includes(t.type),\n (page) => {\n let changed = false;\n const out: T[] = [];\n for (const it of page.items) {\n const next = fn(it);\n if (next !== it) changed = true;\n if (next) out.push(next);\n }\n return changed ? { ...page, items: out } : page;\n },\n );\n const comments = [\"comments\", \"replies\", \"latest\", \"admin-comments\"];\n switch (c.type) {\n case \"comment.created\": {\n const parent = c.comment.reply_to_id;\n const published = !c.comment.moderation;\n const add = (t: Tag) => (parent ? t.type === \"replies\" && t.id === parent : t.type === \"comments\" && sameRef(t.ref, c.ref));\n this.patch<Page<Comment>>(add, (p) => (p.items.some((x) => x.id === c.comment.id) ? p : { ...p, items: parent ? [...p.items, c.comment] : [c.comment, ...p.items] }));\n if (parent && published) items<Comment>([\"comments\"], (x) => (x.id === parent ? { ...x, reply_count: x.reply_count + 1 } : x));\n this.invalidate((t) => t.type === \"latest\" || t.type === \"admin-comments\");\n break;\n }\n case \"comment.updated\":\n items<Comment>(comments, (x) => (x.id === c.comment.id ? { ...x, ...c.comment, author: c.comment.author ?? x.author } : x));\n break;\n case \"comment.reacted\":\n items<Comment>(comments, (x) => (x.id === c.id ? { ...x, ...c.counts } : x));\n break;\n case \"comment.deleted\": {\n let parent: string | undefined;\n items<Comment>([\"comments\", \"replies\"], (x) => {\n if (x.id !== c.id || x.deleted) return x;\n if (!x.moderation) parent = x.reply_to_id;\n return { ...x, deleted: true, body: \"[deleted]\", author: undefined, user_id: undefined, anon_name: undefined };\n });\n if (parent) items<Comment>([\"comments\"], (x) => (x.id === parent ? { ...x, reply_count: Math.max(0, x.reply_count - 1) } : x));\n items<Comment>([\"latest\"], (x) => (x.id === c.id ? null : x));\n items<Comment & { deleted_at?: string }>([\"admin-comments\"], (x) => (x.id === c.id ? { ...x, deleted: true, deleted_at: new Date().toISOString() } : x));\n break;\n }\n case \"comment.restored\":\n this.invalidate((t) => comments.includes(t.type));\n break;\n case \"reaction.changed\":\n this.patch((t) => t.type === \"reaction\" && sameRef(t.ref, c.ref), () => c.counts);\n if (c.ref.kind === \"post\") this.postTotals(c.ref.id, c.counts.likes, c.counts.dislikes);\n break;\n case \"favorite.changed\":\n this.patch((t) => t.type === \"favorite\" && sameRef(t.ref, c.ref), () => ({ favorited: c.favorited, count: c.count }));\n this.invalidate((t) => t.type === \"favorites\");\n break;\n case \"post.created\":\n this.invalidate((t) => t.type === \"posts\");\n break;\n case \"post.updated\":\n this.patch<Post | null>((t) => t.type === \"post\", (p) => (p?.id === c.post.id ? c.post : p));\n items<Post>([\"posts\"], (x) => (x.id === c.post.id ? c.post : x));\n break;\n case \"post.changed\":\n this.invalidate((t) => (t.type === \"post\" && t.id === c.id) || t.type === \"posts\");\n break;\n case \"post.deleted\":\n items<Post>([\"posts\"], (x) => (x.id === c.id ? null : x));\n this.invalidate((t) => t.type === \"posts\" && t.scope === \"deleted\");\n break;\n case \"post.restored\":\n this.invalidate((t) => (t.type === \"post\" && t.id === c.post.id) || t.type === \"posts\");\n break;\n case \"poll.created\":\n this.invalidate((t) => t.type === \"polls\" || (t.type === \"poll\" && !t.id));\n break;\n case \"poll.updated\":\n // A latest-poll slot has no id in its tag: match by the poll it holds.\n this.patch<Poll | null>((t) => t.type === \"poll\", (p) => (p?.id === c.poll.id ? c.poll : p));\n items<Poll>([\"polls\"], (x) => (x.id === c.poll.id ? c.poll : x));\n break;\n case \"poll.changed\":\n this.invalidate((t) => (t.type === \"poll\" && (t.id === c.id || !t.id)) || t.type === \"polls\");\n break;\n case \"poll.deleted\":\n items<Poll>([\"polls\"], (x) => (x.id === c.id ? null : x));\n this.invalidate((t) => t.type === \"poll\" && !t.id);\n break;\n case \"ban.saved\":\n case \"ban.lifted\":\n this.invalidate((t) => (t.type === \"bans\" && t.scope === c.scope) || t.type === \"standing\");\n break;\n case \"moderation.resolved\":\n items<{ id: string }>([\"held\"], (x) => (x.id === c.id ? null : x));\n if (c.kind === \"comment\") this.invalidate((t) => comments.includes(t.type));\n else this.invalidate((t) => (t.type === \"post\" && t.id === c.id) || t.type === \"posts\");\n break;\n }\n }\n\n private postTotals(id: string, likes: number, dislikes: number): void {\n const fn = (p: Post): Post => (p.id === id ? { ...p, total_likes: likes, total_dislikes: dislikes } : p);\n this.patch<Post>((t) => t.type === \"post\" && t.id === id, fn);\n this.patch<Page<Post>>((t) => t.type === \"posts\", (page) => ({ ...page, items: page.items.map(fn) }));\n }\n\n // A new entry per change: useSyncExternalStore compares snapshots by identity.\n private update(s: Slot, patch: Partial<Resource<unknown>>): void {\n s.entry = { ...s.entry, ...patch };\n for (const l of s.listeners) l();\n }\n}\n\nconst stores = new WeakMap<ContentKitClient, ResourceStore>();\n\n/** The client's shared content store. */\nexport function resourcesFor(client: ContentKitClient): ResourceStore {\n let s = stores.get(client);\n if (!s) stores.set(client, (s = new ResourceStore(client)));\n return s;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resources.js","names":[],"sources":["../../src/react/resources.ts"],"sourcesContent":["import type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Comment, Poll, Post, RefBody } from \"../client/generated/wire.js\";\nimport type { ContentKitChange } from \"../client/http.js\";\n\n/** One shared read: its data, whether a fetch is in flight, and its last failure. */\nexport interface Resource<T> {\n data: T | undefined;\n loading: boolean;\n /** A fetch or set has completed. */\n loaded: boolean;\n /** A list's next page is in flight. */\n loadingMore?: boolean;\n error?: ContentKitError;\n}\n\n/** A page of a list and how to get the next one (null: no more). */\nexport interface Page<T> {\n items: T[];\n next: string | number | null;\n}\n\nexport type Fetcher<T> = (signal: AbortSignal) => Promise<T>;\n\n/** What a slot holds, for matching changes: its resource type and the ids it depends on. */\nexport interface Tag {\n type: string;\n readScope?: string;\n ref?: RefBody;\n id?: string;\n scope?: string;\n}\n\ninterface Slot {\n tag: Tag;\n listeners: Set<() => void>;\n entry: Resource<unknown>;\n fetcher?: Fetcher<unknown>;\n more?: (cursor: string | number, signal: AbortSignal) => Promise<Page<unknown>>;\n ctl?: AbortController;\n moreCtl?: AbortController;\n /** Bumped by mark(): an optimistic write rolls back only if no newer one started. */\n version: number;\n /** Identifies the full read; item patches and appended pages retain it. */\n readVersion: number;\n}\n\nconst IDLE: Resource<never> = { data: undefined, loading: false, loaded: false };\n\nconst sameRef = (a?: RefBody, b?: RefBody) => !!a && !!b && a.kind === b.kind && a.id === b.id;\n\n/**\n * The content reads hooks share, per client: one request per key while any\n * hook shows it, optimistic writes with rollback, and every change the client\n * makes applied in place (or refetched) for every hook showing it.\n */\nexport class ResourceStore {\n private readonly slots = new Map<string, Slot>();\n // Tokens must remain unique when a scope leaves and its slots are recreated.\n private version = 0;\n\n constructor(client: ContentKitClient) {\n client.subscribe((c, scope) => this.apply(c, scope));\n }\n\n subscribe(key: string, tag: Tag, listener: () => void): () => void {\n let s = this.slots.get(key);\n if (!s) this.slots.set(key, (s = { tag, listeners: new Set(), entry: IDLE, version: 0, readVersion: ++this.version }));\n const slot = s;\n slot.listeners.add(listener);\n return () => {\n slot.listeners.delete(listener);\n // Kept across a StrictMode remount; dropped once no hook shows it.\n queueMicrotask(() => {\n if (slot.listeners.size || this.slots.get(key) !== slot) return;\n slot.ctl?.abort();\n slot.moreCtl?.abort();\n this.slots.delete(key);\n });\n };\n }\n\n snapshot<T>(key: string): Resource<T> {\n return (this.slots.get(key)?.entry ?? IDLE) as Resource<T>;\n }\n\n /** The latest fetchers of a key: its first read, and a list's next page. */\n bind(key: string, fetcher: Fetcher<unknown>, more?: Slot[\"more\"]): void {\n const s = this.slots.get(key);\n if (s) Object.assign(s, { fetcher, more });\n }\n\n /** Fetches unless loaded or in flight; force refetches (a list from its first page). */\n load(key: string, force = false): void {\n const s = this.slots.get(key);\n if (!s?.fetcher || (!force && (s.entry.loaded || s.ctl))) return;\n s.ctl?.abort();\n s.moreCtl?.abort();\n s.moreCtl = undefined;\n const ctl = (s.ctl = new AbortController());\n this.update(s, { loading: true, loadingMore: false, error: undefined });\n s.fetcher(ctl.signal).then(\n (data) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n s.readVersion = ++this.version;\n this.update(s, { data, loading: false, loaded: true, error: undefined });\n },\n (e) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n const error = toContentKitError(e);\n if (error.code !== \"aborted\") this.update(s, { loading: false, loaded: true, error });\n },\n );\n }\n\n /** Appends a list's next page; resolves once it landed. */\n async loadMore(key: string): Promise<void> {\n const s = this.slots.get(key);\n const page = s?.entry.data as Page<{ id: string }> | undefined;\n if (!s?.more || !page || page.next === null || s.moreCtl || s.ctl) return;\n const ctl = (s.moreCtl = new AbortController());\n this.update(s, { loadingMore: true });\n try {\n const next = (await s.more(page.next, ctl.signal)) as Page<{ id: string }>;\n if (s.moreCtl !== ctl) return;\n const cur = s.entry.data as Page<{ id: string }>;\n const seen = new Set(cur.items.map((x) => x.id));\n this.update(s, { data: { items: [...cur.items, ...next.items.filter((x) => !seen.has(x.id))], next: next.next }, error: undefined });\n } catch (e) {\n const error = toContentKitError(e);\n if (s.moreCtl === ctl && error.code !== \"aborted\") this.update(s, { error });\n } finally {\n if (s.moreCtl === ctl) {\n s.moreCtl = undefined;\n this.update(s, { loadingMore: false });\n }\n }\n }\n\n /** Replaces a key's data for every hook showing it. */\n set<T>(key: string, data: T | ((prev: T | undefined) => T)): void {\n const s = this.slots.get(key);\n if (!s) return;\n const next = typeof data === \"function\" ? (data as (p: T | undefined) => T)(s.entry.data as T | undefined) : data;\n s.readVersion = ++this.version;\n this.update(s, { data: next, loaded: true, error: undefined });\n }\n\n /** Starts an optimistic write on key: the token rollback() checks. */\n mark(key: string): number {\n const s = this.slots.get(key);\n return s ? (s.version = ++this.version) : 0;\n }\n\n /** Restores data unless a newer optimistic write started since mark(). */\n rollback<T>(key: string, token: number, data: T): void {\n const s = this.slots.get(key);\n if (s && s.version === token) this.update(s, { data, loaded: true });\n }\n\n /** Applies fn to matching slots, with the read identity for scoped rollback. */\n patch<T>(match: (tag: Tag) => boolean, fn: (data: T, readVersion: number) => T): void {\n for (const s of this.slots.values()) {\n if (s.entry.data !== undefined && match(s.tag)) this.update(s, { data: fn(s.entry.data as T, s.readVersion) });\n }\n }\n\n /** Refetches every shown slot whose tag matches. */\n invalidate(match: (tag: Tag) => boolean): void {\n for (const [key, s] of this.slots) if (match(s.tag) && s.listeners.size) this.load(key, true);\n }\n\n private apply(c: ContentKitChange, scope: string | null): void {\n const patch = <T>(match: (tag: Tag) => boolean, fn: (data: T) => T) => {\n this.patch<T>((t) => match(t) && (t.readScope ?? \"{}\") === scope, fn);\n this.invalidate((t) => match(t) && (t.readScope ?? \"{}\") !== scope);\n };\n const items = <T extends { id: string }>(types: string[], fn: (item: T) => T | null) =>\n patch<Page<T>>(\n (t) => types.includes(t.type),\n (page) => {\n let changed = false;\n const out: T[] = [];\n for (const it of page.items) {\n const next = fn(it);\n if (next !== it) changed = true;\n if (next) out.push(next);\n }\n return changed ? { ...page, items: out } : page;\n },\n );\n const comments = [\"comments\", \"replies\", \"latest\", \"admin-comments\"];\n switch (c.type) {\n case \"comment.created\": {\n const parent = c.comment.reply_to_id;\n const published = !c.comment.moderation;\n const add = (t: Tag) => (parent ? t.type === \"replies\" && t.id === parent : t.type === \"comments\" && sameRef(t.ref, c.ref));\n patch<Page<Comment>>(add, (p) => (p.items.some((x) => x.id === c.comment.id) ? p : { ...p, items: parent ? [...p.items, c.comment] : [c.comment, ...p.items] }));\n if (parent && published) items<Comment>([\"comments\"], (x) => (x.id === parent ? { ...x, reply_count: x.reply_count + 1 } : x));\n this.invalidate((t) => t.type === \"latest\" || t.type === \"admin-comments\");\n break;\n }\n case \"comment.updated\":\n items<Comment>(comments, (x) => (x.id === c.comment.id ? { ...x, ...c.comment, author: c.comment.author ?? x.author } : x));\n break;\n case \"comment.reacted\":\n items<Comment>(comments, (x) => (x.id === c.id ? { ...x, ...c.counts } : x));\n break;\n case \"comment.deleted\": {\n let parent: string | undefined;\n items<Comment>([\"comments\", \"replies\"], (x) => {\n if (x.id !== c.id || x.deleted) return x;\n if (!x.moderation) parent = x.reply_to_id;\n return { ...x, deleted: true, body: \"[deleted]\", author: undefined, user_id: undefined, anon_name: undefined };\n });\n if (parent) items<Comment>([\"comments\"], (x) => (x.id === parent ? { ...x, reply_count: Math.max(0, x.reply_count - 1) } : x));\n items<Comment>([\"latest\"], (x) => (x.id === c.id ? null : x));\n items<Comment & { deleted_at?: string }>([\"admin-comments\"], (x) => (x.id === c.id ? { ...x, deleted: true, deleted_at: new Date().toISOString() } : x));\n break;\n }\n case \"comment.restored\":\n this.invalidate((t) => comments.includes(t.type));\n break;\n case \"reaction.changed\":\n patch((t) => t.type === \"reaction\" && sameRef(t.ref, c.ref), () => c.counts);\n if (c.ref.kind === \"post\") {\n const totals = (p: Post): Post => (p.id === c.ref.id ? { ...p, total_likes: c.counts.likes, total_dislikes: c.counts.dislikes } : p);\n patch<Post>((t) => t.type === \"post\" && t.id === c.ref.id, totals);\n items<Post>([\"posts\"], totals);\n }\n break;\n case \"favorite.changed\":\n patch((t) => t.type === \"favorite\" && sameRef(t.ref, c.ref), () => ({ favorited: c.favorited, count: c.count }));\n this.invalidate((t) => t.type === \"favorites\");\n break;\n case \"post.created\":\n this.invalidate((t) => t.type === \"posts\");\n break;\n case \"post.updated\":\n patch<Post | null>((t) => t.type === \"post\", (p) => (p?.id === c.post.id ? c.post : p));\n items<Post>([\"posts\"], (x) => (x.id === c.post.id ? c.post : x));\n break;\n case \"post.changed\":\n this.invalidate((t) => (t.type === \"post\" && t.id === c.id) || t.type === \"posts\");\n break;\n case \"post.deleted\":\n items<Post>([\"posts\"], (x) => (x.id === c.id ? null : x));\n this.invalidate((t) => t.type === \"posts\" && t.scope === \"deleted\");\n break;\n case \"post.restored\":\n this.invalidate((t) => (t.type === \"post\" && t.id === c.post.id) || t.type === \"posts\");\n break;\n case \"poll.created\":\n this.invalidate((t) => t.type === \"polls\" || (t.type === \"poll\" && !t.id));\n break;\n case \"poll.updated\":\n // A latest-poll slot has no id in its tag: match by the poll it holds.\n patch<Poll | null>((t) => t.type === \"poll\", (p) => (p?.id === c.poll.id ? c.poll : p));\n items<Poll>([\"polls\"], (x) => (x.id === c.poll.id ? c.poll : x));\n break;\n case \"poll.changed\":\n this.invalidate((t) => (t.type === \"poll\" && (t.id === c.id || !t.id)) || t.type === \"polls\");\n break;\n case \"poll.deleted\":\n items<Poll>([\"polls\"], (x) => (x.id === c.id ? null : x));\n this.invalidate((t) => t.type === \"poll\" && !t.id);\n break;\n case \"ban.saved\":\n case \"ban.lifted\":\n this.invalidate((t) => (t.type === \"bans\" && t.scope === c.scope) || t.type === \"standing\");\n break;\n case \"moderation.resolved\":\n items<{ id: string }>([\"held\"], (x) => (x.id === c.id ? null : x));\n if (c.kind === \"comment\") this.invalidate((t) => comments.includes(t.type));\n else this.invalidate((t) => (t.type === \"post\" && t.id === c.id) || t.type === \"posts\");\n break;\n }\n }\n\n // A new entry per change: useSyncExternalStore compares snapshots by identity.\n private update(s: Slot, patch: Partial<Resource<unknown>>): void {\n s.entry = { ...s.entry, ...patch };\n for (const l of s.listeners) l();\n }\n}\n\nconst stores = new WeakMap<ContentKitClient, ResourceStore>();\n\n/** The client's shared content store. */\nexport function resourcesFor(client: ContentKitClient): ResourceStore {\n let s = stores.get(client);\n if (!s) stores.set(client, (s = new ResourceStore(client)));\n return s;\n}\n"],"mappings":"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"}
|
package/dist/react/slot.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { AspectRatio } from '../client/aspect.js';
|
|
|
2
2
|
import { ContentKitClient } from '../client/client.js';
|
|
3
3
|
import { Size } from '../client/crop.js';
|
|
4
4
|
import { ContentKitError } from '../client/errors.js';
|
|
5
|
-
import { Edit, FileInfo, ReadResult, RefBody } from '../client/generated/wire.js';
|
|
5
|
+
import { Edit, FileInfo, PresetRule, ReadResult, RefBody } from '../client/generated/wire.js';
|
|
6
6
|
import { CropSource } from '../client/image.js';
|
|
7
7
|
import { Progress } from '../client/media/client.js';
|
|
8
8
|
import { PublicPreset } from '../client/public.js';
|
|
@@ -23,8 +23,10 @@ export interface UseSlotImage {
|
|
|
23
23
|
file: FileInfo | null;
|
|
24
24
|
/** The current published files; empty until the worker publishes them. */
|
|
25
25
|
renditions: Rendition[];
|
|
26
|
-
/** "W:H": the preset's, else "1:1". */
|
|
26
|
+
/** "W:H": the image's, else its public preset's (GET /media/presets), else "1:1". */
|
|
27
27
|
aspect: AspectRatio;
|
|
28
|
+
/** The path's public preset (aspect, widths, min_width) once the presets load. */
|
|
29
|
+
rule?: PresetRule;
|
|
28
30
|
loading: boolean;
|
|
29
31
|
error?: ContentKitError;
|
|
30
32
|
reload: () => void;
|
package/dist/react/slot.js
CHANGED
|
@@ -2,109 +2,106 @@ import { ContentKitError as e, toContentKitError as t } from "../client/errors.j
|
|
|
2
2
|
import { publicRenditions as n } from "../client/public.js";
|
|
3
3
|
import { ratio as r } from "../client/aspect.js";
|
|
4
4
|
import { samePath as i, stem as a } from "../client/media/client.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
5
|
+
import { presetFor as o } from "../client/media/rules.js";
|
|
6
|
+
import { centeredCrop as s, constrainCrop as c, editedSize as l, rotation as u, sameEdit as d } from "../client/crop.js";
|
|
7
|
+
import { useContentKitClient as f, useReadScope as p } from "./context.js";
|
|
8
|
+
import { decodeImage as m, isAnimatedImage as h } from "../client/image.js";
|
|
9
|
+
import { withUpload as g } from "./store.js";
|
|
10
|
+
import { useMediaRead as _ } from "./read.js";
|
|
11
|
+
import { usePresets as v } from "./public.js";
|
|
12
|
+
import { useCallback as y, useLayoutEffect as b, useMemo as x, useRef as S, useState as C } from "react";
|
|
11
13
|
//#region src/react/slot.ts
|
|
12
|
-
function
|
|
13
|
-
let t =
|
|
14
|
+
function w(e) {
|
|
15
|
+
let t = _(e.ref, {
|
|
14
16
|
editor: !0,
|
|
15
17
|
prefix: a(e.path),
|
|
16
18
|
read: e.read,
|
|
17
19
|
client: e.client
|
|
18
|
-
}), r = t.read,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
}), { presets: r } = v({ client: e.client }), s = o(r, e.ref.kind, e.path, e.image?.preset), c = t.read, l = c?.files.find((t) => t.upload && i(t.path, e.path)) ?? null, { path: u } = e, d = t.set, f = y((e) => d(g(c, u, e)), [
|
|
21
|
+
c,
|
|
22
|
+
u,
|
|
23
|
+
d
|
|
22
24
|
]);
|
|
23
25
|
return {
|
|
24
|
-
file:
|
|
25
|
-
renditions:
|
|
26
|
-
if (!
|
|
27
|
-
let t =
|
|
26
|
+
file: l,
|
|
27
|
+
renditions: x(() => {
|
|
28
|
+
if (!c) return n(e.image);
|
|
29
|
+
let t = c.public?.find((t) => i(t.from, u) && (!e.image || t.preset === e.image.preset));
|
|
28
30
|
return n(t);
|
|
29
31
|
}, [
|
|
30
|
-
|
|
32
|
+
c,
|
|
31
33
|
e.image,
|
|
32
|
-
|
|
34
|
+
u
|
|
33
35
|
]),
|
|
34
|
-
aspect: e.image?.aspect || "1:1",
|
|
36
|
+
aspect: e.image?.aspect || s?.aspect || "1:1",
|
|
37
|
+
rule: s,
|
|
35
38
|
loading: t.loading,
|
|
36
39
|
error: t.error,
|
|
37
40
|
reload: t.reload,
|
|
38
|
-
set:
|
|
41
|
+
set: f
|
|
39
42
|
};
|
|
40
43
|
}
|
|
41
|
-
function
|
|
42
|
-
let i = r(n), a =
|
|
43
|
-
return
|
|
44
|
-
width:
|
|
45
|
-
height:
|
|
44
|
+
function T(e, t, n) {
|
|
45
|
+
let i = r(n), a = u(t?.rotate ?? 0), o = t?.crop ? c(t.crop, e, i, a) : s(e, i, a);
|
|
46
|
+
return l({
|
|
47
|
+
width: o.w,
|
|
48
|
+
height: o.h
|
|
46
49
|
}, a);
|
|
47
50
|
}
|
|
48
|
-
function
|
|
49
|
-
let { media: r } =
|
|
50
|
-
|
|
51
|
-
let
|
|
52
|
-
|
|
53
|
-
let
|
|
54
|
-
let t =
|
|
55
|
-
n && n !== r && n.revoke?.(),
|
|
56
|
-
}, [])
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
"source" in e && e.source?.revoke?.();
|
|
61
|
-
}, []);
|
|
62
|
-
let x = g(async (t, n, r) => {
|
|
63
|
-
s.current?.abort(), s.current = null;
|
|
64
|
-
let i = ++c.current;
|
|
65
|
-
h({ status: "decoding" });
|
|
51
|
+
function E(n) {
|
|
52
|
+
let { media: r } = f(n.client), i = p(), [a, o] = C({ status: "idle" }), s = S(a);
|
|
53
|
+
s.current = a;
|
|
54
|
+
let c = S(null), l = S(0), u = S(n);
|
|
55
|
+
u.current = n;
|
|
56
|
+
let g = n.aspect ?? "1:1", _ = y((e) => {
|
|
57
|
+
let t = s.current, n = "source" in t ? t.source : void 0, r = "source" in e ? e.source : void 0;
|
|
58
|
+
n && n !== r && n.revoke?.(), s.current = e, o(e);
|
|
59
|
+
}, []), v = y(async (t, n, r) => {
|
|
60
|
+
c.current?.abort(), c.current = null;
|
|
61
|
+
let i = ++l.current;
|
|
62
|
+
_({ status: "decoding" });
|
|
66
63
|
try {
|
|
67
64
|
let e = await t();
|
|
68
|
-
if (i !==
|
|
69
|
-
|
|
65
|
+
if (i !== l.current) return e.revoke?.();
|
|
66
|
+
_({
|
|
70
67
|
status: "cropping",
|
|
71
68
|
source: e,
|
|
72
69
|
edit: r,
|
|
73
70
|
mode: n
|
|
74
71
|
});
|
|
75
72
|
} catch (t) {
|
|
76
|
-
if (i !==
|
|
73
|
+
if (i !== l.current) return;
|
|
77
74
|
let n = t instanceof e ? t : new e("decode", t instanceof Error ? t.message : String(t), { cause: t });
|
|
78
|
-
|
|
75
|
+
_({
|
|
79
76
|
status: "error",
|
|
80
77
|
error: n
|
|
81
|
-
}),
|
|
78
|
+
}), u.current.onError?.(n, "slot.decode");
|
|
82
79
|
}
|
|
83
|
-
}, [
|
|
84
|
-
if (
|
|
85
|
-
return (
|
|
86
|
-
}, "new", null), [
|
|
87
|
-
let e =
|
|
88
|
-
e && await
|
|
89
|
-
}, [r,
|
|
90
|
-
let t =
|
|
91
|
-
t.status === "cropping" ?
|
|
80
|
+
}, [_]), w = y((t) => v(async () => {
|
|
81
|
+
if (u.current.animation === "reject" && await h(t)) throw new e("animation_not_allowed", "animated images are not allowed here", { status: 422 });
|
|
82
|
+
return (u.current.decode ?? m)(t);
|
|
83
|
+
}, "new", null), [v]), E = !!n.file && !n.file.staged && (n.file.size ?? 0) > 0 && n.file.type.startsWith("image/"), D = y(async () => {
|
|
84
|
+
let e = u.current.file;
|
|
85
|
+
e && await v(() => r.editorView(u.current.ref, e.path), "recrop", e.edit ?? null);
|
|
86
|
+
}, [r, v]), O = y((e) => {
|
|
87
|
+
let t = s.current;
|
|
88
|
+
t.status === "cropping" ? d(t.edit, e) || _({
|
|
92
89
|
...t,
|
|
93
90
|
edit: e
|
|
94
|
-
}) : t.status === "error" && t.source && t.mode &&
|
|
91
|
+
}) : t.status === "error" && t.source && t.mode && _({
|
|
95
92
|
status: "cropping",
|
|
96
93
|
source: t.source,
|
|
97
94
|
edit: e,
|
|
98
95
|
mode: t.mode
|
|
99
96
|
});
|
|
100
|
-
}, [
|
|
101
|
-
e !== void 0 &&
|
|
102
|
-
let n =
|
|
97
|
+
}, [_]), k = y(async (e) => {
|
|
98
|
+
e !== void 0 && O(e);
|
|
99
|
+
let n = s.current;
|
|
103
100
|
if (!(n.status === "cropping" || n.status === "error" && n.source && n.mode)) return;
|
|
104
|
-
let { source: i, mode: a } = n,
|
|
101
|
+
let { source: i, mode: a } = n, o = n.edit ?? null, { ref: l, path: d, renderTimeout: f } = u.current, p = c.current = new AbortController(), m = (e) => p === c.current && _({
|
|
105
102
|
status: "saving",
|
|
106
103
|
source: i,
|
|
107
|
-
edit:
|
|
104
|
+
edit: o,
|
|
108
105
|
mode: a,
|
|
109
106
|
progress: e,
|
|
110
107
|
rendering: e?.phase === "processing"
|
|
@@ -113,69 +110,76 @@ function C(n) {
|
|
|
113
110
|
try {
|
|
114
111
|
let e;
|
|
115
112
|
if (a === "new") e = await r.put(i.file, {
|
|
116
|
-
ref:
|
|
113
|
+
ref: l,
|
|
117
114
|
path: d,
|
|
118
|
-
edit:
|
|
115
|
+
edit: o,
|
|
119
116
|
signal: p.signal,
|
|
120
117
|
timeout: f,
|
|
121
118
|
onProgress: m
|
|
122
119
|
});
|
|
123
120
|
else {
|
|
124
|
-
let t =
|
|
125
|
-
await r.commit(
|
|
121
|
+
let t = u.current.file?.path ?? d;
|
|
122
|
+
if (await r.commit(l, [{
|
|
126
123
|
op: "edit",
|
|
127
124
|
path: t,
|
|
128
|
-
...
|
|
129
|
-
}], { signal: p.signal }),
|
|
125
|
+
...o ? { edit: o } : {}
|
|
126
|
+
}], { signal: p.signal }), p !== c.current) return;
|
|
127
|
+
m({
|
|
130
128
|
phase: "processing",
|
|
131
129
|
loaded: 0,
|
|
132
130
|
total: 0
|
|
133
|
-
}), e = await r.waitFor(
|
|
131
|
+
}), e = await r.waitFor(l, t, {
|
|
134
132
|
signal: p.signal,
|
|
135
133
|
timeout: f
|
|
136
134
|
});
|
|
137
135
|
}
|
|
138
|
-
return p ===
|
|
136
|
+
return p === c.current ? (_({
|
|
139
137
|
status: "done",
|
|
140
138
|
file: e
|
|
141
|
-
}),
|
|
139
|
+
}), u.current.onSaved?.(e), e) : void 0;
|
|
142
140
|
} catch (e) {
|
|
143
|
-
if (p !==
|
|
141
|
+
if (p !== c.current) return;
|
|
144
142
|
let n = t(e);
|
|
145
|
-
|
|
143
|
+
_(n.code === "aborted" ? {
|
|
146
144
|
status: "cropping",
|
|
147
145
|
source: i,
|
|
148
|
-
edit:
|
|
146
|
+
edit: o,
|
|
149
147
|
mode: a
|
|
150
148
|
} : {
|
|
151
149
|
status: "error",
|
|
152
150
|
error: n,
|
|
153
151
|
source: i,
|
|
154
|
-
edit:
|
|
152
|
+
edit: o,
|
|
155
153
|
mode: a
|
|
156
|
-
}), n.code !== "aborted" &&
|
|
154
|
+
}), n.code !== "aborted" && u.current.onError?.(n, "slot.save");
|
|
157
155
|
return;
|
|
158
156
|
}
|
|
159
157
|
}, [
|
|
160
158
|
r,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
]),
|
|
164
|
-
|
|
165
|
-
}, [
|
|
159
|
+
_,
|
|
160
|
+
O
|
|
161
|
+
]), A = y(() => {
|
|
162
|
+
l.current++, c.current?.abort(), c.current = null, _({ status: "idle" });
|
|
163
|
+
}, [_]);
|
|
164
|
+
b(() => A, [
|
|
165
|
+
i,
|
|
166
|
+
r,
|
|
167
|
+
A
|
|
168
|
+
]);
|
|
169
|
+
let j = x(() => "source" in a && a.source ? T(a.source, a.edit, g) : void 0, [a, g]);
|
|
166
170
|
return {
|
|
167
|
-
...
|
|
168
|
-
aspect:
|
|
169
|
-
cropped:
|
|
170
|
-
pick:
|
|
171
|
-
canRecrop:
|
|
172
|
-
recrop:
|
|
173
|
-
setEdit:
|
|
174
|
-
save:
|
|
175
|
-
cancel:
|
|
171
|
+
...a,
|
|
172
|
+
aspect: g,
|
|
173
|
+
cropped: j,
|
|
174
|
+
pick: w,
|
|
175
|
+
canRecrop: E,
|
|
176
|
+
recrop: D,
|
|
177
|
+
setEdit: O,
|
|
178
|
+
save: k,
|
|
179
|
+
cancel: A
|
|
176
180
|
};
|
|
177
181
|
}
|
|
178
182
|
//#endregion
|
|
179
|
-
export {
|
|
183
|
+
export { T as editOutput, E as useSlotCrop, w as useSlotImage };
|
|
180
184
|
|
|
181
185
|
//# sourceMappingURL=slot.js.map
|