sanity-plugin-r2-video 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/studio/index.js +397 -243
- package/package.json +1 -1
package/dist/studio/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { resolveRenditionPath } from '../chunk-D5VS4ED7.js';
|
|
2
2
|
export { resolveRenditionPath } from '../chunk-D5VS4ED7.js';
|
|
3
|
-
import {
|
|
4
|
-
import { defineType, defineField, definePlugin,
|
|
5
|
-
import { createContext, useState, useCallback, useEffect, useRef, useMemo, useContext, useId } from 'react';
|
|
3
|
+
import { createContext, useState, useCallback, useEffect, useRef, useMemo, useId, useContext } from 'react';
|
|
4
|
+
import { defineType, defineField, definePlugin, set, useClient } from 'sanity';
|
|
6
5
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
|
+
import { PlayIcon } from '@sanity/icons/Play';
|
|
7
7
|
import { UploadIcon } from '@sanity/icons/Upload';
|
|
8
|
-
import { Stack, Flex,
|
|
8
|
+
import { Box, Stack, Flex, Button, TextInput, Card, Text, Select, Grid, Checkbox, Dialog, Spinner, Switch } from '@sanity/ui';
|
|
9
9
|
import { AccessDeniedIcon } from '@sanity/icons/AccessDenied';
|
|
10
10
|
import { canEncodeVideo } from 'mediabunny';
|
|
11
11
|
import { WarningOutlineIcon } from '@sanity/icons/WarningOutline';
|
|
@@ -14,6 +14,7 @@ import { ChevronRightIcon } from '@sanity/icons/ChevronRight';
|
|
|
14
14
|
import { SearchIcon } from '@sanity/icons/Search';
|
|
15
15
|
import { SyncIcon } from '@sanity/icons/Sync';
|
|
16
16
|
import { TrashIcon } from '@sanity/icons/Trash';
|
|
17
|
+
import styled from 'styled-components';
|
|
17
18
|
import { FolderIcon } from '@sanity/icons/Folder';
|
|
18
19
|
|
|
19
20
|
// studio/client.ts
|
|
@@ -107,6 +108,24 @@ var deleteVideoAsset = async (client, config, asset) => {
|
|
|
107
108
|
renditions.map((rendition) => rendition.key)
|
|
108
109
|
);
|
|
109
110
|
};
|
|
111
|
+
var R2VideoConfigContext = createContext(null);
|
|
112
|
+
var R2VideoConfigProvider = ({ config, children }) => {
|
|
113
|
+
return /* @__PURE__ */ jsx(R2VideoConfigContext.Provider, { value: config, children });
|
|
114
|
+
};
|
|
115
|
+
var useR2VideoConfig = () => {
|
|
116
|
+
const config = useContext(R2VideoConfigContext);
|
|
117
|
+
if (!config) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
"Missing R2 video config. Add `r2Video` to the Studio's plugin list."
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
return config;
|
|
123
|
+
};
|
|
124
|
+
var useR2VideoClient = () => {
|
|
125
|
+
const config = useR2VideoConfig();
|
|
126
|
+
const client = useClient({ apiVersion: config.apiVersion });
|
|
127
|
+
return { config, client };
|
|
128
|
+
};
|
|
110
129
|
|
|
111
130
|
// studio/folders.ts
|
|
112
131
|
var QUERY_FOLDERS = `*[_type == $folderType]{
|
|
@@ -150,18 +169,13 @@ var resolvePosterFolder = async (client, folderType, name) => {
|
|
|
150
169
|
const created = await client.create({ _type: folderType, name });
|
|
151
170
|
return created._id;
|
|
152
171
|
};
|
|
153
|
-
var
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
throw new Error(
|
|
161
|
-
"Missing R2 video config. Add `r2Video` to the Studio's plugin list."
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
return config;
|
|
172
|
+
var useFolders = () => {
|
|
173
|
+
const { config, client } = useR2VideoClient();
|
|
174
|
+
const [folders, setFolders] = useState([]);
|
|
175
|
+
useEffect(() => {
|
|
176
|
+
fetchFolders(client, config.folders.type).then(setFolders);
|
|
177
|
+
}, [client, config.folders.type]);
|
|
178
|
+
return { folders, paths: resolveFolderPaths(folders) };
|
|
165
179
|
};
|
|
166
180
|
|
|
167
181
|
// studio/defaults.ts
|
|
@@ -204,6 +218,15 @@ var formatSize = (bytes) => {
|
|
|
204
218
|
var formatDuration = (seconds) => {
|
|
205
219
|
return `${seconds.toFixed(1)}s`;
|
|
206
220
|
};
|
|
221
|
+
var totalSize = (renditions) => {
|
|
222
|
+
return renditions.reduce((total, rendition) => total + rendition.size, 0);
|
|
223
|
+
};
|
|
224
|
+
var pluralize = (count, noun) => {
|
|
225
|
+
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
226
|
+
};
|
|
227
|
+
var toMessage = (error) => {
|
|
228
|
+
return error instanceof Error ? error.message : String(error);
|
|
229
|
+
};
|
|
207
230
|
var formatBitrate = (bytes, seconds) => {
|
|
208
231
|
if (!seconds) {
|
|
209
232
|
return "-";
|
|
@@ -333,6 +356,57 @@ var transcodeVideo = (request, progressed) => {
|
|
|
333
356
|
worker.postMessage(request);
|
|
334
357
|
});
|
|
335
358
|
};
|
|
359
|
+
var Notice = ({ tone, title, icon, children }) => {
|
|
360
|
+
return /* @__PURE__ */ jsx(Card, { padding: 4, radius: 2, tone, children: /* @__PURE__ */ jsxs(Flex, { align: "flex-start", gap: 3, children: [
|
|
361
|
+
icon && /* @__PURE__ */ jsx(Text, { size: 2, children: icon }),
|
|
362
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
363
|
+
title && /* @__PURE__ */ jsx(Text, { size: 1, weight: "semibold", children: title }),
|
|
364
|
+
typeof children === "string" ? /* @__PURE__ */ jsx(Text, { muted: Boolean(title), size: 1, children }) : children
|
|
365
|
+
] })
|
|
366
|
+
] }) });
|
|
367
|
+
};
|
|
368
|
+
var Field = ({ label, id, description, children }) => {
|
|
369
|
+
return /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
370
|
+
/* @__PURE__ */ jsx(Text, { as: id ? "label" : void 0, htmlFor: id, size: 1, weight: "medium", children: label }),
|
|
371
|
+
children,
|
|
372
|
+
description && /* @__PURE__ */ jsx(Text, { muted: true, size: 0, children: description })
|
|
373
|
+
] });
|
|
374
|
+
};
|
|
375
|
+
var DialogActions = ({
|
|
376
|
+
aside,
|
|
377
|
+
cancel,
|
|
378
|
+
confirm
|
|
379
|
+
}) => {
|
|
380
|
+
return /* @__PURE__ */ jsx(Card, { borderTop: true, padding: 2, children: /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
|
|
381
|
+
aside,
|
|
382
|
+
/* @__PURE__ */ jsx(Box, { flex: 1, children: /* @__PURE__ */ jsx(
|
|
383
|
+
Button,
|
|
384
|
+
{
|
|
385
|
+
disabled: cancel.disabled,
|
|
386
|
+
mode: "ghost",
|
|
387
|
+
text: cancel.text,
|
|
388
|
+
width: "fill",
|
|
389
|
+
onClick: cancel.onClick
|
|
390
|
+
}
|
|
391
|
+
) }),
|
|
392
|
+
confirm && /* @__PURE__ */ jsx(Box, { flex: 1, children: /* @__PURE__ */ jsx(
|
|
393
|
+
Button,
|
|
394
|
+
{
|
|
395
|
+
disabled: confirm.disabled,
|
|
396
|
+
text: confirm.text,
|
|
397
|
+
tone: confirm.tone,
|
|
398
|
+
width: "fill",
|
|
399
|
+
onClick: confirm.onClick
|
|
400
|
+
}
|
|
401
|
+
) })
|
|
402
|
+
] }) });
|
|
403
|
+
};
|
|
404
|
+
var Loading = ({ children }) => {
|
|
405
|
+
return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 3, children: [
|
|
406
|
+
/* @__PURE__ */ jsx(Spinner, { muted: true }),
|
|
407
|
+
/* @__PURE__ */ jsx(Text, { muted: true, size: 1, children })
|
|
408
|
+
] });
|
|
409
|
+
};
|
|
336
410
|
var PreviewEncode = ({ file, keepAudio, encoding }) => {
|
|
337
411
|
const [preview, setPreview] = useState(null);
|
|
338
412
|
const [isEncoding, setIsEncoding] = useState(false);
|
|
@@ -374,7 +448,7 @@ var PreviewEncode = ({ file, keepAudio, encoding }) => {
|
|
|
374
448
|
duration: result.duration
|
|
375
449
|
});
|
|
376
450
|
} catch (caught) {
|
|
377
|
-
setError(
|
|
451
|
+
setError(toMessage(caught));
|
|
378
452
|
}
|
|
379
453
|
setIsEncoding(false);
|
|
380
454
|
};
|
|
@@ -394,15 +468,12 @@ var PreviewEncode = ({ file, keepAudio, encoding }) => {
|
|
|
394
468
|
}
|
|
395
469
|
)
|
|
396
470
|
] }),
|
|
397
|
-
isEncoding && /* @__PURE__ */ jsxs(
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
Math.round(progress * 100),
|
|
402
|
-
"%"
|
|
403
|
-
] })
|
|
471
|
+
isEncoding && /* @__PURE__ */ jsxs(Loading, { children: [
|
|
472
|
+
"Encoding top tier - ",
|
|
473
|
+
Math.round(progress * 100),
|
|
474
|
+
"%"
|
|
404
475
|
] }),
|
|
405
|
-
error && /* @__PURE__ */ jsx(
|
|
476
|
+
error && /* @__PURE__ */ jsx(Notice, { tone: "critical", children: error }),
|
|
406
477
|
preview && !isEncoding && /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
407
478
|
/* @__PURE__ */ jsx(
|
|
408
479
|
"video",
|
|
@@ -439,20 +510,15 @@ var PreviewEncode = ({ file, keepAudio, encoding }) => {
|
|
|
439
510
|
] }) });
|
|
440
511
|
};
|
|
441
512
|
var UnsupportedBrowser = () => {
|
|
442
|
-
return /* @__PURE__ */ jsx(
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
/* @__PURE__ */ jsx(
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
return /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
452
|
-
/* @__PURE__ */ jsx(Text, { as: "label", htmlFor: id, size: 1, weight: "medium", children: label }),
|
|
453
|
-
children,
|
|
454
|
-
/* @__PURE__ */ jsx(Text, { muted: true, size: 0, children: description })
|
|
455
|
-
] });
|
|
513
|
+
return /* @__PURE__ */ jsx(
|
|
514
|
+
Notice,
|
|
515
|
+
{
|
|
516
|
+
icon: /* @__PURE__ */ jsx(WarningOutlineIcon, {}),
|
|
517
|
+
title: "This browser can't encode video",
|
|
518
|
+
tone: "caution",
|
|
519
|
+
children: "Uploading needs WebCodecs h264 encoding, which this browser doesn't provide. Open the Studio in Chrome to add videos. Everything already in the library still works here."
|
|
520
|
+
}
|
|
521
|
+
);
|
|
456
522
|
};
|
|
457
523
|
var ToggleRow = ({ id, label, description, children }) => {
|
|
458
524
|
return /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 3, children: [
|
|
@@ -702,9 +768,6 @@ var describe = (item) => {
|
|
|
702
768
|
const { stage, progress, label } = item.progress;
|
|
703
769
|
return `${STAGE_LABELS[stage]} ${label} \xB7 ${Math.round(progress * 100)}%`;
|
|
704
770
|
};
|
|
705
|
-
var countLabel = (count) => {
|
|
706
|
-
return count === 1 ? "1 video" : `${count} video(s)`;
|
|
707
|
-
};
|
|
708
771
|
var toItem = (file) => ({
|
|
709
772
|
id: crypto.randomUUID(),
|
|
710
773
|
file,
|
|
@@ -718,10 +781,9 @@ var DialogUpload = ({
|
|
|
718
781
|
onUploaded,
|
|
719
782
|
onClose
|
|
720
783
|
}) => {
|
|
721
|
-
const config =
|
|
722
|
-
const
|
|
784
|
+
const { config, client } = useR2VideoClient();
|
|
785
|
+
const { paths } = useFolders();
|
|
723
786
|
const [canEncode, setCanEncode] = useState(null);
|
|
724
|
-
const [folders, setFolders] = useState([]);
|
|
725
787
|
const [targetFolder, setTargetFolder] = useState(folderId);
|
|
726
788
|
const [keepAudio, setKeepAudio] = useState(false);
|
|
727
789
|
const [quality, setQuality] = useState(config.encoding.quality);
|
|
@@ -736,13 +798,9 @@ var DialogUpload = ({
|
|
|
736
798
|
useEffect(() => {
|
|
737
799
|
canEncodeLadder(config.encoding.videoCodec).then(setCanEncode);
|
|
738
800
|
}, [config.encoding.videoCodec]);
|
|
739
|
-
useEffect(() => {
|
|
740
|
-
fetchFolders(client, config.folders.type).then(setFolders);
|
|
741
|
-
}, [client, config.folders.type]);
|
|
742
801
|
const encoding = useMemo(() => {
|
|
743
802
|
return { ...config.encoding, quality, preferBitrate };
|
|
744
803
|
}, [config.encoding, quality, preferBitrate]);
|
|
745
|
-
const paths = resolveFolderPaths(folders);
|
|
746
804
|
const pending = items.filter((item) => item.status === "pending");
|
|
747
805
|
const isBusy = items.some((item) => item.status === "working");
|
|
748
806
|
const canStart = pending.length > 0 && !isBusy && canEncode !== false;
|
|
@@ -776,7 +834,7 @@ var DialogUpload = ({
|
|
|
776
834
|
update(item.id, {
|
|
777
835
|
status: "failed",
|
|
778
836
|
progress: null,
|
|
779
|
-
error:
|
|
837
|
+
error: toMessage(error)
|
|
780
838
|
});
|
|
781
839
|
}
|
|
782
840
|
}
|
|
@@ -817,28 +875,22 @@ var DialogUpload = ({
|
|
|
817
875
|
id: "r2-video-upload",
|
|
818
876
|
width: 1,
|
|
819
877
|
onClose: isBusy ? void 0 : onClose,
|
|
820
|
-
footer: /* @__PURE__ */ jsx(
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
{
|
|
824
|
-
disabled: isBusy,
|
|
825
|
-
mode: "ghost",
|
|
878
|
+
footer: /* @__PURE__ */ jsx(
|
|
879
|
+
DialogActions,
|
|
880
|
+
{
|
|
881
|
+
cancel: {
|
|
826
882
|
text: items.length > 0 && !canStart ? "Done" : "Cancel",
|
|
827
|
-
|
|
883
|
+
disabled: isBusy,
|
|
828
884
|
onClick: onClose
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
Button,
|
|
833
|
-
{
|
|
834
|
-
disabled: !canStart,
|
|
835
|
-
text: isBusy ? "Encoding\u2026" : `Upload ${countLabel(pending.length)}`,
|
|
885
|
+
},
|
|
886
|
+
confirm: {
|
|
887
|
+
text: isBusy ? "Encoding\u2026" : `Upload ${pluralize(pending.length, "video")}`,
|
|
836
888
|
tone: "primary",
|
|
837
|
-
|
|
889
|
+
disabled: !canStart,
|
|
838
890
|
onClick: confirmed
|
|
839
891
|
}
|
|
840
|
-
|
|
841
|
-
|
|
892
|
+
}
|
|
893
|
+
),
|
|
842
894
|
children: /* @__PURE__ */ jsx(Card, { padding: 4, children: /* @__PURE__ */ jsxs(Stack, { gap: 5, children: [
|
|
843
895
|
canEncode === false && /* @__PURE__ */ jsx(UnsupportedBrowser, {}),
|
|
844
896
|
/* @__PURE__ */ jsx(
|
|
@@ -883,7 +935,7 @@ var DialogUpload = ({
|
|
|
883
935
|
}
|
|
884
936
|
),
|
|
885
937
|
items.length > 0 && /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
886
|
-
/* @__PURE__ */ jsx(Text, { muted: true, size: 1, weight: "medium", children:
|
|
938
|
+
/* @__PURE__ */ jsx(Text, { muted: true, size: 1, weight: "medium", children: pluralize(items.length, "video") }),
|
|
887
939
|
/* @__PURE__ */ jsx(Stack, { gap: 2, children: items.map((item) => /* @__PURE__ */ jsx(
|
|
888
940
|
Card,
|
|
889
941
|
{
|
|
@@ -976,7 +1028,7 @@ var InputVideo = (props) => {
|
|
|
976
1028
|
};
|
|
977
1029
|
return /* @__PURE__ */ jsxs(Stack, { gap: 3, style: { position: "relative" }, ...dropProps, children: [
|
|
978
1030
|
isDragging && /* @__PURE__ */ jsx(DropToUpload, { isRejected }),
|
|
979
|
-
/* @__PURE__ */ jsxs(Flex, { gap:
|
|
1031
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 1, children: [
|
|
980
1032
|
/* @__PURE__ */ jsx(Box, { flex: 1, children: renderDefault({
|
|
981
1033
|
...props,
|
|
982
1034
|
renderField: (field) => field.children
|
|
@@ -1015,7 +1067,7 @@ var VideoPreview = ({ renditions, posterUrl }) => {
|
|
|
1015
1067
|
const config = useR2VideoConfig();
|
|
1016
1068
|
const rendition = resolvePreviewRendition(renditions);
|
|
1017
1069
|
if (!rendition) {
|
|
1018
|
-
return /* @__PURE__ */ jsx(
|
|
1070
|
+
return /* @__PURE__ */ jsx(Notice, { tone: "caution", children: "This video has no renditions to play." });
|
|
1019
1071
|
}
|
|
1020
1072
|
return /* @__PURE__ */ jsx(
|
|
1021
1073
|
"video",
|
|
@@ -1054,9 +1106,6 @@ var VideoSummary = ({
|
|
|
1054
1106
|
hasAudio,
|
|
1055
1107
|
uploadedAt
|
|
1056
1108
|
}) => {
|
|
1057
|
-
const totalBytes = renditions.reduce((total, rendition) => {
|
|
1058
|
-
return total + rendition.size;
|
|
1059
|
-
}, 0);
|
|
1060
1109
|
const ordered = [...renditions].sort((a, b) => b.height - a.height);
|
|
1061
1110
|
const largest = ordered[0];
|
|
1062
1111
|
return /* @__PURE__ */ jsxs(Stack, { gap: 5, children: [
|
|
@@ -1070,7 +1119,7 @@ var VideoSummary = ({
|
|
|
1070
1119
|
),
|
|
1071
1120
|
/* @__PURE__ */ jsx(Fact, { label: "Duration", value: formatDuration(duration) }),
|
|
1072
1121
|
/* @__PURE__ */ jsx(Fact, { label: "Audio", value: hasAudio ? "Kept" : "Stripped" }),
|
|
1073
|
-
/* @__PURE__ */ jsx(Fact, { label: "Total", value: formatSize(
|
|
1122
|
+
/* @__PURE__ */ jsx(Fact, { label: "Total", value: formatSize(totalSize(renditions)) })
|
|
1074
1123
|
] }) }),
|
|
1075
1124
|
/* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
1076
1125
|
/* @__PURE__ */ jsx(Text, { muted: true, size: 1, weight: "medium", children: "Renditions" }),
|
|
@@ -1276,116 +1325,109 @@ var SCHEMA_R2_VIDEO = defineType({
|
|
|
1276
1325
|
}
|
|
1277
1326
|
}
|
|
1278
1327
|
});
|
|
1279
|
-
var DialogDelete = ({
|
|
1280
|
-
const config =
|
|
1281
|
-
const
|
|
1282
|
-
const [blockers, setBlockers] = useState(null);
|
|
1328
|
+
var DialogDelete = ({ assets, onDeleted, onClose }) => {
|
|
1329
|
+
const { config, client } = useR2VideoClient();
|
|
1330
|
+
const [blocked, setBlocked] = useState(null);
|
|
1283
1331
|
const [isDeleting, setIsDeleting] = useState(false);
|
|
1284
1332
|
const [error, setError] = useState(null);
|
|
1285
1333
|
useEffect(() => {
|
|
1286
|
-
|
|
1287
|
-
|
|
1334
|
+
Promise.all(
|
|
1335
|
+
assets.map(async (asset) => ({
|
|
1336
|
+
asset,
|
|
1337
|
+
documents: await findReferencingDocuments(client, asset._id)
|
|
1338
|
+
}))
|
|
1339
|
+
).then((checked) => {
|
|
1340
|
+
setBlocked(checked.filter((entry) => entry.documents.length > 0));
|
|
1341
|
+
}).catch(() => {
|
|
1342
|
+
setError("Could not check which documents use these videos.");
|
|
1288
1343
|
});
|
|
1289
|
-
}, [client,
|
|
1344
|
+
}, [client, assets]);
|
|
1290
1345
|
const confirmed = async () => {
|
|
1291
1346
|
setIsDeleting(true);
|
|
1292
1347
|
setError(null);
|
|
1293
1348
|
try {
|
|
1294
|
-
|
|
1349
|
+
for (const asset of assets) {
|
|
1350
|
+
await deleteVideoAsset(client, config, asset);
|
|
1351
|
+
}
|
|
1295
1352
|
onDeleted();
|
|
1296
1353
|
} catch (caught) {
|
|
1297
|
-
setError(
|
|
1354
|
+
setError(toMessage(caught));
|
|
1298
1355
|
setIsDeleting(false);
|
|
1299
1356
|
}
|
|
1300
1357
|
};
|
|
1301
|
-
const
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
const isBlocked = blockers !== null && blockers.length > 0;
|
|
1358
|
+
const isBlocked = blocked !== null && blocked.length > 0;
|
|
1359
|
+
const renditions = assets.flatMap((asset) => asset.renditions);
|
|
1360
|
+
const isOne = assets.length === 1;
|
|
1305
1361
|
return /* @__PURE__ */ jsx(
|
|
1306
1362
|
Dialog,
|
|
1307
1363
|
{
|
|
1308
|
-
header: "Delete video"
|
|
1364
|
+
header: isOne ? "Delete video" : `Delete ${assets.length} videos`,
|
|
1309
1365
|
id: "r2-video-delete",
|
|
1310
1366
|
width: 1,
|
|
1311
1367
|
onClose: isDeleting ? void 0 : onClose,
|
|
1312
|
-
footer: /* @__PURE__ */ jsx(
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
{
|
|
1316
|
-
|
|
1317
|
-
mode: "ghost",
|
|
1318
|
-
text: "Cancel",
|
|
1319
|
-
width: "fill",
|
|
1320
|
-
onClick: onClose
|
|
1321
|
-
}
|
|
1322
|
-
) }),
|
|
1323
|
-
/* @__PURE__ */ jsx(Box, { flex: 1, children: /* @__PURE__ */ jsx(
|
|
1324
|
-
Button,
|
|
1325
|
-
{
|
|
1326
|
-
disabled: isDeleting || isBlocked || blockers === null,
|
|
1368
|
+
footer: /* @__PURE__ */ jsx(
|
|
1369
|
+
DialogActions,
|
|
1370
|
+
{
|
|
1371
|
+
cancel: { text: "Cancel", disabled: isDeleting, onClick: onClose },
|
|
1372
|
+
confirm: {
|
|
1327
1373
|
text: isDeleting ? "Deleting\u2026" : "Delete",
|
|
1328
1374
|
tone: "critical",
|
|
1329
|
-
|
|
1375
|
+
disabled: isDeleting || isBlocked || blocked === null,
|
|
1330
1376
|
onClick: confirmed
|
|
1331
1377
|
}
|
|
1332
|
-
|
|
1333
|
-
|
|
1378
|
+
}
|
|
1379
|
+
),
|
|
1334
1380
|
children: /* @__PURE__ */ jsx(Card, { padding: 4, children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
|
|
1335
1381
|
/* @__PURE__ */ jsxs(Text, { size: 1, children: [
|
|
1336
|
-
|
|
1337
|
-
" -
|
|
1338
|
-
asset.renditions.length,
|
|
1339
|
-
" renditions,",
|
|
1382
|
+
isOne ? assets[0].filename : pluralize(assets.length, "video"),
|
|
1383
|
+
" -",
|
|
1340
1384
|
" ",
|
|
1341
|
-
|
|
1342
|
-
",
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1385
|
+
pluralize(renditions.length, "rendition"),
|
|
1386
|
+
",",
|
|
1387
|
+
" ",
|
|
1388
|
+
formatSize(totalSize(renditions)),
|
|
1389
|
+
", plus",
|
|
1390
|
+
" ",
|
|
1391
|
+
isOne ? "its poster" : "their posters",
|
|
1392
|
+
"."
|
|
1347
1393
|
] }),
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
/* @__PURE__ */
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
"
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1394
|
+
blocked === null && !error && /* @__PURE__ */ jsx(Loading, { children: "Checking where they're used\u2026" }),
|
|
1395
|
+
isBlocked && /* @__PURE__ */ jsx(Notice, { title: "Still in use", tone: "caution", children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
1396
|
+
/* @__PURE__ */ jsxs(Text, { muted: true, size: 1, children: [
|
|
1397
|
+
"Remove ",
|
|
1398
|
+
isOne ? "it" : "these",
|
|
1399
|
+
" from these documents first:"
|
|
1400
|
+
] }),
|
|
1401
|
+
blocked.map((entry) => /* @__PURE__ */ jsxs(Text, { muted: true, size: 1, children: [
|
|
1402
|
+
!isOne && `${entry.asset.filename}: `,
|
|
1403
|
+
entry.documents.map((document) => document.title || document._id).join(", ")
|
|
1404
|
+
] }, entry.asset._id))
|
|
1357
1405
|
] }) }),
|
|
1358
|
-
|
|
1359
|
-
|
|
1406
|
+
blocked !== null && blocked.length === 0 && /* @__PURE__ */ jsxs(Text, { muted: true, size: 1, children: [
|
|
1407
|
+
"Nothing references ",
|
|
1408
|
+
isOne ? "it" : "them",
|
|
1409
|
+
". This can't be undone."
|
|
1410
|
+
] }),
|
|
1411
|
+
error && /* @__PURE__ */ jsx(Notice, { tone: "critical", children: error })
|
|
1360
1412
|
] }) })
|
|
1361
1413
|
}
|
|
1362
1414
|
);
|
|
1363
1415
|
};
|
|
1364
|
-
var Field2 = ({ label, children }) => {
|
|
1365
|
-
return /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
1366
|
-
/* @__PURE__ */ jsx(Text, { muted: true, size: 1, weight: "medium", children: label }),
|
|
1367
|
-
children
|
|
1368
|
-
] });
|
|
1369
|
-
};
|
|
1370
1416
|
var DialogDetails = ({
|
|
1371
1417
|
asset,
|
|
1372
1418
|
onChanged,
|
|
1373
1419
|
onDelete,
|
|
1374
1420
|
onClose
|
|
1375
1421
|
}) => {
|
|
1376
|
-
const
|
|
1377
|
-
const
|
|
1378
|
-
const [folders, setFolders] = useState([]);
|
|
1422
|
+
const { client } = useR2VideoClient();
|
|
1423
|
+
const { paths } = useFolders();
|
|
1379
1424
|
const [folderId, setFolderId] = useState(
|
|
1380
1425
|
asset.folder ? asset.folder._ref : ""
|
|
1381
1426
|
);
|
|
1382
1427
|
const [filename, setFilename] = useState(asset.filename);
|
|
1383
1428
|
const [isSaving, setIsSaving] = useState(false);
|
|
1429
|
+
const [isFlagged, setIsFlagged] = useState(false);
|
|
1384
1430
|
const [error, setError] = useState(null);
|
|
1385
|
-
useEffect(() => {
|
|
1386
|
-
fetchFolders(client, config.folders.type).then(setFolders);
|
|
1387
|
-
}, [client, config.folders.type]);
|
|
1388
|
-
const paths = resolveFolderPaths(folders);
|
|
1389
1431
|
const isBroken = asset.renditions.length === 0;
|
|
1390
1432
|
const trimmed = filename.trim();
|
|
1391
1433
|
const savedFolderId = asset.folder ? asset.folder._ref : "";
|
|
@@ -1395,6 +1437,17 @@ var DialogDetails = ({
|
|
|
1395
1437
|
const reset = () => {
|
|
1396
1438
|
setFilename(asset.filename);
|
|
1397
1439
|
setFolderId(savedFolderId);
|
|
1440
|
+
setIsFlagged(false);
|
|
1441
|
+
};
|
|
1442
|
+
const attemptClose = () => {
|
|
1443
|
+
if (isSaving) {
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
if (isDirty) {
|
|
1447
|
+
setIsFlagged(true);
|
|
1448
|
+
return;
|
|
1449
|
+
}
|
|
1450
|
+
onClose();
|
|
1398
1451
|
};
|
|
1399
1452
|
const save = async () => {
|
|
1400
1453
|
if (!isDirty) {
|
|
@@ -1423,10 +1476,11 @@ var DialogDetails = ({
|
|
|
1423
1476
|
mutation = mutation.unset(removals);
|
|
1424
1477
|
}
|
|
1425
1478
|
await mutation.commit();
|
|
1479
|
+
setIsFlagged(false);
|
|
1426
1480
|
onChanged();
|
|
1427
1481
|
} catch (caught) {
|
|
1428
1482
|
reset();
|
|
1429
|
-
setError(
|
|
1483
|
+
setError(toMessage(caught));
|
|
1430
1484
|
}
|
|
1431
1485
|
setIsSaving(false);
|
|
1432
1486
|
};
|
|
@@ -1436,41 +1490,36 @@ var DialogDetails = ({
|
|
|
1436
1490
|
header: asset.filename || "Untitled video",
|
|
1437
1491
|
id: "r2-video-details",
|
|
1438
1492
|
width: 1,
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
mode: "ghost",
|
|
1493
|
+
onClickOutside: attemptClose,
|
|
1494
|
+
onClose: isSaving ? void 0 : attemptClose,
|
|
1495
|
+
footer: /* @__PURE__ */ jsx(
|
|
1496
|
+
DialogActions,
|
|
1497
|
+
{
|
|
1498
|
+
aside: /* @__PURE__ */ jsx(
|
|
1499
|
+
Button,
|
|
1500
|
+
{
|
|
1501
|
+
"aria-label": "Delete video",
|
|
1502
|
+
disabled: isSaving,
|
|
1503
|
+
icon: TrashIcon,
|
|
1504
|
+
mode: "ghost",
|
|
1505
|
+
tone: "critical",
|
|
1506
|
+
onClick: onDelete
|
|
1507
|
+
}
|
|
1508
|
+
),
|
|
1509
|
+
cancel: {
|
|
1457
1510
|
text: isDirty ? "Discard" : "Close",
|
|
1458
|
-
|
|
1511
|
+
disabled: isSaving,
|
|
1459
1512
|
onClick: isDirty ? reset : onClose
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
|
-
!isBroken && /* @__PURE__ */ jsx(Box, { flex: 1, children: /* @__PURE__ */ jsx(
|
|
1463
|
-
Button,
|
|
1464
|
-
{
|
|
1465
|
-
disabled: isSaving || !isDirty,
|
|
1513
|
+
},
|
|
1514
|
+
confirm: isBroken ? void 0 : {
|
|
1466
1515
|
text: isSaving ? "Saving\u2026" : "Save changes",
|
|
1467
1516
|
tone: "primary",
|
|
1468
|
-
|
|
1517
|
+
disabled: isSaving || !isDirty,
|
|
1469
1518
|
onClick: save
|
|
1470
1519
|
}
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
children: /* @__PURE__ */ jsx(Card, { padding: 4, children: isBroken ? /* @__PURE__ */ jsx(
|
|
1520
|
+
}
|
|
1521
|
+
),
|
|
1522
|
+
children: /* @__PURE__ */ jsx(Card, { padding: 4, children: isBroken ? /* @__PURE__ */ jsx(Notice, { tone: "caution", children: "This video was never finished uploading, so it has no renditions and nothing stored behind it. Delete it." }) : /* @__PURE__ */ jsxs(Stack, { gap: 5, children: [
|
|
1474
1523
|
/* @__PURE__ */ jsx(
|
|
1475
1524
|
VideoPreview,
|
|
1476
1525
|
{
|
|
@@ -1479,7 +1528,7 @@ var DialogDetails = ({
|
|
|
1479
1528
|
}
|
|
1480
1529
|
),
|
|
1481
1530
|
/* @__PURE__ */ jsxs(Flex, { gap: 3, children: [
|
|
1482
|
-
/* @__PURE__ */ jsx(Box, { flex: 1, children: /* @__PURE__ */ jsx(
|
|
1531
|
+
/* @__PURE__ */ jsx(Box, { flex: 1, children: /* @__PURE__ */ jsx(Field, { label: "Filename", children: /* @__PURE__ */ jsx(
|
|
1483
1532
|
TextInput,
|
|
1484
1533
|
{
|
|
1485
1534
|
"aria-label": "Filename",
|
|
@@ -1493,7 +1542,7 @@ var DialogDetails = ({
|
|
|
1493
1542
|
}
|
|
1494
1543
|
}
|
|
1495
1544
|
) }) }),
|
|
1496
|
-
/* @__PURE__ */ jsx(Box, { flex: 1, children: /* @__PURE__ */ jsx(
|
|
1545
|
+
/* @__PURE__ */ jsx(Box, { flex: 1, children: /* @__PURE__ */ jsx(Field, { label: "Folder", children: /* @__PURE__ */ jsxs(
|
|
1497
1546
|
Select,
|
|
1498
1547
|
{
|
|
1499
1548
|
"aria-label": "Folder",
|
|
@@ -1507,7 +1556,8 @@ var DialogDetails = ({
|
|
|
1507
1556
|
}
|
|
1508
1557
|
) }) })
|
|
1509
1558
|
] }),
|
|
1510
|
-
|
|
1559
|
+
isFlagged && /* @__PURE__ */ jsx(Notice, { tone: "caution", children: "Unsaved changes. Save them, or discard to close." }),
|
|
1560
|
+
error && /* @__PURE__ */ jsx(Notice, { tone: "critical", children: error }),
|
|
1511
1561
|
/* @__PURE__ */ jsx(
|
|
1512
1562
|
VideoSummary,
|
|
1513
1563
|
{
|
|
@@ -1556,19 +1606,15 @@ var removeOrphans = async (client, config, orphans) => {
|
|
|
1556
1606
|
}
|
|
1557
1607
|
}
|
|
1558
1608
|
};
|
|
1559
|
-
var countLabel2 = (count, noun) => {
|
|
1560
|
-
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
1561
|
-
};
|
|
1562
1609
|
var DialogOrphans = ({ onCleaned, onClose }) => {
|
|
1563
|
-
const config =
|
|
1564
|
-
const client = useClient({ apiVersion: config.apiVersion });
|
|
1610
|
+
const { config, client } = useR2VideoClient();
|
|
1565
1611
|
const [orphans, setOrphans] = useState(null);
|
|
1566
1612
|
const [isRemoving, setIsRemoving] = useState(false);
|
|
1567
1613
|
const [isDone, setIsDone] = useState(false);
|
|
1568
1614
|
const [error, setError] = useState(null);
|
|
1569
1615
|
useEffect(() => {
|
|
1570
1616
|
findOrphans(client, config).then(setOrphans).catch((caught) => {
|
|
1571
|
-
setError(
|
|
1617
|
+
setError(toMessage(caught));
|
|
1572
1618
|
});
|
|
1573
1619
|
}, [client, config]);
|
|
1574
1620
|
const confirmed = async () => {
|
|
@@ -1582,7 +1628,7 @@ var DialogOrphans = ({ onCleaned, onClose }) => {
|
|
|
1582
1628
|
setIsDone(true);
|
|
1583
1629
|
onCleaned();
|
|
1584
1630
|
} catch (caught) {
|
|
1585
|
-
setError(
|
|
1631
|
+
setError(toMessage(caught));
|
|
1586
1632
|
}
|
|
1587
1633
|
setIsRemoving(false);
|
|
1588
1634
|
};
|
|
@@ -1594,46 +1640,37 @@ var DialogOrphans = ({ onCleaned, onClose }) => {
|
|
|
1594
1640
|
id: "r2-video-orphans",
|
|
1595
1641
|
width: 1,
|
|
1596
1642
|
onClose: isRemoving ? void 0 : onClose,
|
|
1597
|
-
footer: /* @__PURE__ */ jsx(
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
{
|
|
1601
|
-
disabled: isRemoving,
|
|
1602
|
-
mode: "ghost",
|
|
1643
|
+
footer: /* @__PURE__ */ jsx(
|
|
1644
|
+
DialogActions,
|
|
1645
|
+
{
|
|
1646
|
+
cancel: {
|
|
1603
1647
|
text: isDone ? "Done" : "Cancel",
|
|
1604
|
-
|
|
1648
|
+
disabled: isRemoving,
|
|
1605
1649
|
onClick: onClose
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
/* @__PURE__ */ jsx(Box, { flex: 1, children: /* @__PURE__ */ jsx(
|
|
1609
|
-
Button,
|
|
1610
|
-
{
|
|
1611
|
-
disabled: isRemoving || isDone || total === 0,
|
|
1650
|
+
},
|
|
1651
|
+
confirm: {
|
|
1612
1652
|
text: isRemoving ? "Removing\u2026" : "Remove",
|
|
1613
1653
|
tone: "critical",
|
|
1614
|
-
|
|
1654
|
+
disabled: isRemoving || isDone || total === 0,
|
|
1615
1655
|
onClick: confirmed
|
|
1616
1656
|
}
|
|
1617
|
-
|
|
1618
|
-
|
|
1657
|
+
}
|
|
1658
|
+
),
|
|
1619
1659
|
children: /* @__PURE__ */ jsx(Card, { padding: 4, children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
|
|
1620
|
-
!orphans && !error && /* @__PURE__ */
|
|
1621
|
-
|
|
1622
|
-
/* @__PURE__ */ jsx(Text, { muted: true, size: 1, children: "Comparing storage against the library\u2026" })
|
|
1623
|
-
] }),
|
|
1624
|
-
isDone && /* @__PURE__ */ jsx(Card, { padding: 3, radius: 2, tone: "positive", children: /* @__PURE__ */ jsx(Text, { size: 1, children: "Removed." }) }),
|
|
1660
|
+
!orphans && !error && /* @__PURE__ */ jsx(Loading, { children: "Comparing storage against the library\u2026" }),
|
|
1661
|
+
isDone && /* @__PURE__ */ jsx(Notice, { tone: "positive", children: "Removed." }),
|
|
1625
1662
|
orphans && !isDone && total === 0 && /* @__PURE__ */ jsx(Text, { muted: true, size: 1, children: "Nothing to clean up - every stored file belongs to a video." }),
|
|
1626
1663
|
orphans && !isDone && total > 0 && /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
1627
1664
|
/* @__PURE__ */ jsxs(Text, { size: 1, children: [
|
|
1628
1665
|
"Found ",
|
|
1629
|
-
|
|
1666
|
+
pluralize(orphans.keys.length, "orphaned file"),
|
|
1630
1667
|
" in R2 and ",
|
|
1631
|
-
|
|
1668
|
+
pluralize(orphans.posterIds.length, "unused poster"),
|
|
1632
1669
|
" in Sanity."
|
|
1633
1670
|
] }),
|
|
1634
1671
|
/* @__PURE__ */ jsx(Text, { muted: true, size: 1, children: "Nothing references any of them, so removing them can't affect the site. This can't be undone." })
|
|
1635
1672
|
] }),
|
|
1636
|
-
error && /* @__PURE__ */ jsx(
|
|
1673
|
+
error && /* @__PURE__ */ jsx(Notice, { tone: "critical", children: error })
|
|
1637
1674
|
] }) })
|
|
1638
1675
|
}
|
|
1639
1676
|
);
|
|
@@ -1716,6 +1753,7 @@ var QUERY_ASSETS = `*[_type == "r2Video.asset"] | order(uploadedAt desc){
|
|
|
1716
1753
|
"hasAudio": coalesce(hasAudio, false),
|
|
1717
1754
|
"renditions": coalesce(renditions, []),
|
|
1718
1755
|
uploadedAt,
|
|
1756
|
+
"isUsed": count(*[references(^._id)]) > 0,
|
|
1719
1757
|
"posterUrl": poster.asset->url,
|
|
1720
1758
|
"folderName": folder->name
|
|
1721
1759
|
}`;
|
|
@@ -1737,9 +1775,22 @@ var matches = (asset, search) => {
|
|
|
1737
1775
|
const folderName = asset.folderName ?? "";
|
|
1738
1776
|
return asset.filename.toLowerCase().includes(term) || folderName.toLowerCase().includes(term);
|
|
1739
1777
|
};
|
|
1778
|
+
var SelectableCard = styled(Box)`
|
|
1779
|
+
position: relative;
|
|
1780
|
+
|
|
1781
|
+
& [data-checkbox] {
|
|
1782
|
+
opacity: 0;
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
&:hover [data-checkbox],
|
|
1786
|
+
& [data-checkbox]:focus-within,
|
|
1787
|
+
& [data-checkbox][data-selected="true"] {
|
|
1788
|
+
opacity: 1;
|
|
1789
|
+
}
|
|
1790
|
+
`;
|
|
1740
1791
|
var ToolVideoLibrary = () => {
|
|
1741
|
-
const config =
|
|
1742
|
-
const
|
|
1792
|
+
const { config, client } = useR2VideoClient();
|
|
1793
|
+
const { paths: allPaths } = useFolders();
|
|
1743
1794
|
const [assets, setAssets] = useState(null);
|
|
1744
1795
|
const [folders, setFolders] = useState([]);
|
|
1745
1796
|
const [search, setSearch] = useState("");
|
|
@@ -1749,6 +1800,9 @@ var ToolVideoLibrary = () => {
|
|
|
1749
1800
|
const [droppedFiles, setDroppedFiles] = useState([]);
|
|
1750
1801
|
const [detailing, setDetailing] = useState(null);
|
|
1751
1802
|
const [deleting, setDeleting] = useState(null);
|
|
1803
|
+
const [isUnusedOnly, setIsUnusedOnly] = useState(false);
|
|
1804
|
+
const [selectedIds, setSelectedIds] = useState([]);
|
|
1805
|
+
const [isMoving, setIsMoving] = useState(false);
|
|
1752
1806
|
const load = useCallback(() => {
|
|
1753
1807
|
client.fetch(QUERY_ASSETS).then(setAssets);
|
|
1754
1808
|
fetchFolders(client, config.folders.type).then(setFolders);
|
|
@@ -1778,8 +1832,29 @@ var ToolVideoLibrary = () => {
|
|
|
1778
1832
|
};
|
|
1779
1833
|
const visible = (assets ?? []).filter((asset) => {
|
|
1780
1834
|
const inFolder = !folderId || asset.folder && asset.folder._ref === folderId;
|
|
1781
|
-
|
|
1835
|
+
const isShown = !isUnusedOnly || !asset.isUsed;
|
|
1836
|
+
return matches(asset, search) && inFolder && isShown;
|
|
1782
1837
|
});
|
|
1838
|
+
const selected = visible.filter((asset) => selectedIds.includes(asset._id));
|
|
1839
|
+
const toggleSelected = (id) => {
|
|
1840
|
+
setSelectedIds((current) => {
|
|
1841
|
+
return current.includes(id) ? current.filter((entry) => entry !== id) : [...current, id];
|
|
1842
|
+
});
|
|
1843
|
+
};
|
|
1844
|
+
const moveSelected = async (targetId) => {
|
|
1845
|
+
setIsMoving(true);
|
|
1846
|
+
let transaction = client.transaction();
|
|
1847
|
+
for (const asset of selected) {
|
|
1848
|
+
transaction = targetId ? transaction.patch(
|
|
1849
|
+
asset._id,
|
|
1850
|
+
(patch) => patch.set({ folder: { _type: "reference", _ref: targetId } })
|
|
1851
|
+
) : transaction.patch(asset._id, (patch) => patch.unset(["folder"]));
|
|
1852
|
+
}
|
|
1853
|
+
await transaction.commit();
|
|
1854
|
+
setIsMoving(false);
|
|
1855
|
+
setSelectedIds([]);
|
|
1856
|
+
load();
|
|
1857
|
+
};
|
|
1783
1858
|
return /* @__PURE__ */ jsxs(
|
|
1784
1859
|
Flex,
|
|
1785
1860
|
{
|
|
@@ -1807,6 +1882,15 @@ var ToolVideoLibrary = () => {
|
|
|
1807
1882
|
onChange: (event) => setSearch(event.currentTarget.value)
|
|
1808
1883
|
}
|
|
1809
1884
|
) }),
|
|
1885
|
+
/* @__PURE__ */ jsx(
|
|
1886
|
+
Button,
|
|
1887
|
+
{
|
|
1888
|
+
mode: isUnusedOnly ? "default" : "ghost",
|
|
1889
|
+
text: "Unused",
|
|
1890
|
+
tone: isUnusedOnly ? "primary" : "default",
|
|
1891
|
+
onClick: () => setIsUnusedOnly(!isUnusedOnly)
|
|
1892
|
+
}
|
|
1893
|
+
),
|
|
1810
1894
|
/* @__PURE__ */ jsx(
|
|
1811
1895
|
Button,
|
|
1812
1896
|
{
|
|
@@ -1826,45 +1910,115 @@ var ToolVideoLibrary = () => {
|
|
|
1826
1910
|
}
|
|
1827
1911
|
)
|
|
1828
1912
|
] }),
|
|
1829
|
-
|
|
1830
|
-
/* @__PURE__ */ jsx(
|
|
1831
|
-
|
|
1832
|
-
|
|
1913
|
+
selected.length > 0 && /* @__PURE__ */ jsx(Card, { border: true, padding: 2, radius: 2, tone: "primary", children: /* @__PURE__ */ jsxs(Flex, { align: "center", gap: 2, children: [
|
|
1914
|
+
/* @__PURE__ */ jsx(Box, { paddingX: 2, children: /* @__PURE__ */ jsxs(Text, { size: 1, weight: "medium", children: [
|
|
1915
|
+
pluralize(selected.length, "video"),
|
|
1916
|
+
" selected"
|
|
1917
|
+
] }) }),
|
|
1918
|
+
/* @__PURE__ */ jsx(Box, { flex: 1, children: /* @__PURE__ */ jsxs(
|
|
1919
|
+
Select,
|
|
1920
|
+
{
|
|
1921
|
+
"aria-label": "Move to folder",
|
|
1922
|
+
disabled: isMoving,
|
|
1923
|
+
value: "",
|
|
1924
|
+
onChange: (event) => moveSelected(event.currentTarget.value),
|
|
1925
|
+
children: [
|
|
1926
|
+
/* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "Move to\u2026" }),
|
|
1927
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "No folder" }),
|
|
1928
|
+
allPaths.map((entry) => /* @__PURE__ */ jsx("option", { value: entry.id, children: entry.path }, entry.id))
|
|
1929
|
+
]
|
|
1930
|
+
}
|
|
1931
|
+
) }),
|
|
1932
|
+
/* @__PURE__ */ jsx(
|
|
1933
|
+
Button,
|
|
1934
|
+
{
|
|
1935
|
+
disabled: isMoving,
|
|
1936
|
+
mode: "ghost",
|
|
1937
|
+
text: "Clear",
|
|
1938
|
+
onClick: () => setSelectedIds([])
|
|
1939
|
+
}
|
|
1940
|
+
),
|
|
1941
|
+
/* @__PURE__ */ jsx(
|
|
1942
|
+
Button,
|
|
1943
|
+
{
|
|
1944
|
+
disabled: isMoving,
|
|
1945
|
+
icon: TrashIcon,
|
|
1946
|
+
mode: "ghost",
|
|
1947
|
+
text: "Delete",
|
|
1948
|
+
tone: "critical",
|
|
1949
|
+
onClick: () => setDeleting(selected)
|
|
1950
|
+
}
|
|
1951
|
+
)
|
|
1952
|
+
] }) }),
|
|
1953
|
+
assets === null && /* @__PURE__ */ jsx(Box, { padding: 4, children: /* @__PURE__ */ jsx(Loading, { children: "Loading library\u2026" }) }),
|
|
1833
1954
|
assets !== null && visible.length === 0 && /* @__PURE__ */ jsx(Card, { padding: 5, radius: 2, tone: "transparent", children: /* @__PURE__ */ jsx(Text, { align: "center", muted: true, size: 1, children: "No videos here yet." }) }),
|
|
1834
|
-
/* @__PURE__ */ jsx(Grid, { gridTemplateColumns: [1, 2, 3, 4], gap: 3, children: visible.map((asset) => /* @__PURE__ */
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1955
|
+
/* @__PURE__ */ jsx(Grid, { gridTemplateColumns: [1, 2, 3, 4], gap: 3, children: visible.map((asset) => /* @__PURE__ */ jsxs(SelectableCard, { children: [
|
|
1956
|
+
/* @__PURE__ */ jsx(
|
|
1957
|
+
Box,
|
|
1958
|
+
{
|
|
1959
|
+
"data-checkbox": true,
|
|
1960
|
+
"data-selected": selectedIds.includes(asset._id),
|
|
1961
|
+
style: {
|
|
1962
|
+
position: "absolute",
|
|
1963
|
+
top: 14,
|
|
1964
|
+
left: 14,
|
|
1965
|
+
zIndex: 1
|
|
1966
|
+
},
|
|
1967
|
+
children: /* @__PURE__ */ jsx(
|
|
1968
|
+
Checkbox,
|
|
1846
1969
|
{
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1970
|
+
"aria-label": `Select ${asset.filename}`,
|
|
1971
|
+
checked: selectedIds.includes(asset._id),
|
|
1972
|
+
onChange: () => toggleSelected(asset._id)
|
|
1973
|
+
}
|
|
1974
|
+
)
|
|
1975
|
+
}
|
|
1976
|
+
),
|
|
1977
|
+
/* @__PURE__ */ jsx(
|
|
1978
|
+
Card,
|
|
1979
|
+
{
|
|
1980
|
+
as: "button",
|
|
1981
|
+
border: true,
|
|
1982
|
+
padding: 2,
|
|
1983
|
+
radius: 2,
|
|
1984
|
+
style: {
|
|
1985
|
+
cursor: "pointer",
|
|
1986
|
+
textAlign: "left",
|
|
1987
|
+
width: "100%"
|
|
1988
|
+
},
|
|
1989
|
+
onClick: (event) => {
|
|
1990
|
+
if (event.shiftKey) {
|
|
1991
|
+
toggleSelected(asset._id);
|
|
1992
|
+
return;
|
|
1854
1993
|
}
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
/* @__PURE__ */
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1994
|
+
setDetailing(asset);
|
|
1995
|
+
},
|
|
1996
|
+
children: /* @__PURE__ */ jsxs(Stack, { gap: 3, children: [
|
|
1997
|
+
/* @__PURE__ */ jsx(
|
|
1998
|
+
Box,
|
|
1999
|
+
{
|
|
2000
|
+
style: {
|
|
2001
|
+
aspectRatio: "16 / 9",
|
|
2002
|
+
backgroundImage: asset.posterUrl ? `url(${asset.posterUrl}?w=480&fit=crop&auto=format)` : void 0,
|
|
2003
|
+
backgroundPosition: "center",
|
|
2004
|
+
backgroundSize: "cover",
|
|
2005
|
+
borderRadius: 2
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
),
|
|
2009
|
+
/* @__PURE__ */ jsxs(Stack, { gap: 2, children: [
|
|
2010
|
+
/* @__PURE__ */ jsx(Text, { size: 1, textOverflow: "ellipsis", weight: "medium", children: toTitle(asset, Boolean(folderId)) }),
|
|
2011
|
+
/* @__PURE__ */ jsxs(Text, { muted: true, size: 1, children: [
|
|
2012
|
+
asset.renditions.length,
|
|
2013
|
+
" sizes",
|
|
2014
|
+
asset.hasAudio ? " \xB7 audio" : "",
|
|
2015
|
+
asset.isUsed ? "" : " \xB7 unused"
|
|
2016
|
+
] })
|
|
1862
2017
|
] })
|
|
1863
2018
|
] })
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
)) })
|
|
2019
|
+
}
|
|
2020
|
+
)
|
|
2021
|
+
] }, asset._id)) })
|
|
1868
2022
|
] }) }) }),
|
|
1869
2023
|
isDragging && /* @__PURE__ */ jsx(DropToUpload, { isRejected }),
|
|
1870
2024
|
isUploadOpen && /* @__PURE__ */ jsx(
|
|
@@ -1889,7 +2043,7 @@ var ToolVideoLibrary = () => {
|
|
|
1889
2043
|
asset: detailing,
|
|
1890
2044
|
onChanged: load,
|
|
1891
2045
|
onDelete: () => {
|
|
1892
|
-
setDeleting(detailing);
|
|
2046
|
+
setDeleting([detailing]);
|
|
1893
2047
|
setDetailing(null);
|
|
1894
2048
|
},
|
|
1895
2049
|
onClose: () => setDetailing(null)
|
|
@@ -1898,7 +2052,7 @@ var ToolVideoLibrary = () => {
|
|
|
1898
2052
|
deleting && /* @__PURE__ */ jsx(
|
|
1899
2053
|
DialogDelete,
|
|
1900
2054
|
{
|
|
1901
|
-
|
|
2055
|
+
assets: deleting,
|
|
1902
2056
|
onClose: () => setDeleting(null),
|
|
1903
2057
|
onDeleted: () => {
|
|
1904
2058
|
setDeleting(null);
|