sanity-plugin-image-resizer 1.0.3 → 1.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/dist/_chunks-cjs/resources.js +67 -1
- package/dist/_chunks-cjs/resources.js.map +1 -1
- package/dist/_chunks-es/resources.mjs +67 -1
- package/dist/_chunks-es/resources.mjs.map +1 -1
- package/dist/index.js +107 -84
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +108 -85
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/helpers.ts +56 -3
- package/src/i18n/resources.ts +74 -1
- package/src/plugin.tsx +10 -1
- package/src/tool/ImageResizer.tsx +80 -23
- package/src/tool/components/AssetCard.tsx +46 -25
|
@@ -1,4 +1,70 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var resources = {
|
|
2
|
+
var resources = {
|
|
3
|
+
// ── Tool ────────────────────────────────────────────────────────────────
|
|
4
|
+
/** Tool title shown in the Studio sidebar */
|
|
5
|
+
"tool.title": "Image Resizer",
|
|
6
|
+
// ── Header ──────────────────────────────────────────────────────────────
|
|
7
|
+
/** Main heading on the tool page */
|
|
8
|
+
"header.title": "Image Resizer",
|
|
9
|
+
/** Description below the heading ({{maxWidth}} = pixel limit, {{maxSize}} = MB limit) */
|
|
10
|
+
"header.description": "Converts TIFF images to WebP. Resizes/compresses all images to fit within {{maxWidth}}px / {{maxSize}} MB.",
|
|
11
|
+
// ── Actions ─────────────────────────────────────────────────────────────
|
|
12
|
+
/** Refresh button label */
|
|
13
|
+
"action.refresh": "Refresh",
|
|
14
|
+
/** Process-all button label ({{count}} = number of pending assets) */
|
|
15
|
+
"action.process-all": "Process All ({{count}})",
|
|
16
|
+
/** Label for the button that lets the current in-flight tasks finish */
|
|
17
|
+
"action.finish-ongoing": "Finish ongoing tasks ({{count}})",
|
|
18
|
+
/** Label for the stop button that cancels the queue immediately */
|
|
19
|
+
"action.stop-all": "Stop All (possible data loss)",
|
|
20
|
+
// ── Status badges ───────────────────────────────────────────────────────
|
|
21
|
+
/** Pending badge ({{count}} = number) */
|
|
22
|
+
"status.pending": "{{count}} pending",
|
|
23
|
+
/** Processing badge */
|
|
24
|
+
"status.processing": "{{count}} processing",
|
|
25
|
+
/** Done badge */
|
|
26
|
+
"status.done": "{{count}} done",
|
|
27
|
+
/** Failed badge */
|
|
28
|
+
"status.failed": "{{count}} failed",
|
|
29
|
+
// ── Empty / loading states ──────────────────────────────────────────────
|
|
30
|
+
/** Shown while scanning assets */
|
|
31
|
+
"state.scanning": "Scanning assets\u2026",
|
|
32
|
+
/** Shown when no violating assets are found */
|
|
33
|
+
"state.all-good": "All images meet the requirements.",
|
|
34
|
+
// ── Settings dialog ─────────────────────────────────────────────────────
|
|
35
|
+
/** Settings dialog header */
|
|
36
|
+
"settings.title": "Conversion Settings",
|
|
37
|
+
/** PNG → WebP toggle label */
|
|
38
|
+
"settings.png-to-webp": "Convert PNG \u2192 WebP",
|
|
39
|
+
/** TIFF → JPG toggle label */
|
|
40
|
+
"settings.tiff-to-jpg": "Convert TIFF \u2192 JPG (instead of WebP)",
|
|
41
|
+
/** Hint below toggles */
|
|
42
|
+
"settings.apply-hint": "Changes apply on next Refresh.",
|
|
43
|
+
// ── Asset card ──────────────────────────────────────────────────────────
|
|
44
|
+
/** Violation badge: TIFF → WebP */
|
|
45
|
+
"violation.tiff-to-webp": "TIFF \u2192 WebP",
|
|
46
|
+
/** Violation badge: TIFF → JPG */
|
|
47
|
+
"violation.tiff-to-jpg": "TIFF \u2192 JPG",
|
|
48
|
+
/** Violation badge: PNG → WebP */
|
|
49
|
+
"violation.png-to-webp": "PNG \u2192 WebP",
|
|
50
|
+
/** Violation badge: exceeds max width ({{maxWidth}} = pixel limit) */
|
|
51
|
+
"violation.width": "> {{maxWidth}}px",
|
|
52
|
+
/** Violation badge: exceeds max size ({{maxSize}} = MB limit) */
|
|
53
|
+
"violation.size": "> {{maxSize}} MB",
|
|
54
|
+
/** Asset size summary ({{size}} MB — {{width}}px wide) */
|
|
55
|
+
"asset.summary": "{{size}} MB \u2014 {{width}}px wide",
|
|
56
|
+
/** Asset done summary ({{oldSize}} MB → {{newSize}} MB — {{width}}px wide) */
|
|
57
|
+
"asset.summary-done": "{{oldSize}} MB \u2192 {{newSize}} MB{{reduction}} \u2014 {{width}}px wide",
|
|
58
|
+
/** Reduction percentage shown after size (e.g. " (−32%)") */
|
|
59
|
+
"asset.reduction": " (\u2212{{percent}}%)",
|
|
60
|
+
/** Width reduction badge ({{oldWidth}}px → {{newWidth}}px) */
|
|
61
|
+
"asset.width-reduced": "{{oldWidth}}px \u2192 {{newWidth}}px",
|
|
62
|
+
/** Process button */
|
|
63
|
+
"asset.process": "Process",
|
|
64
|
+
/** Done badge */
|
|
65
|
+
"asset.done": "Done",
|
|
66
|
+
/** Retry button */
|
|
67
|
+
"asset.retry": "Retry"
|
|
68
|
+
};
|
|
3
69
|
exports.default = resources;
|
|
4
70
|
//# sourceMappingURL=resources.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resources.js","sources":["../../src/i18n/resources.ts"],"sourcesContent":["export default {}\n"],"names":[],"mappings":";AAAA,IAAA,YAAe,
|
|
1
|
+
{"version":3,"file":"resources.js","sources":["../../src/i18n/resources.ts"],"sourcesContent":["export default {\n // ── Tool ────────────────────────────────────────────────────────────────\n /** Tool title shown in the Studio sidebar */\n 'tool.title': 'Image Resizer',\n\n // ── Header ──────────────────────────────────────────────────────────────\n /** Main heading on the tool page */\n 'header.title': 'Image Resizer',\n /** Description below the heading ({{maxWidth}} = pixel limit, {{maxSize}} = MB limit) */\n 'header.description':\n 'Converts TIFF images to WebP. Resizes/compresses all images to fit within {{maxWidth}}px / {{maxSize}} MB.',\n\n // ── Actions ─────────────────────────────────────────────────────────────\n /** Refresh button label */\n 'action.refresh': 'Refresh',\n /** Process-all button label ({{count}} = number of pending assets) */\n 'action.process-all': 'Process All ({{count}})',\n /** Label for the button that lets the current in-flight tasks finish */\n 'action.finish-ongoing': 'Finish ongoing tasks ({{count}})',\n /** Label for the stop button that cancels the queue immediately */\n 'action.stop-all': 'Stop All (possible data loss)',\n\n // ── Status badges ───────────────────────────────────────────────────────\n /** Pending badge ({{count}} = number) */\n 'status.pending': '{{count}} pending',\n /** Processing badge */\n 'status.processing': '{{count}} processing',\n /** Done badge */\n 'status.done': '{{count}} done',\n /** Failed badge */\n 'status.failed': '{{count}} failed',\n\n // ── Empty / loading states ──────────────────────────────────────────────\n /** Shown while scanning assets */\n 'state.scanning': 'Scanning assets…',\n /** Shown when no violating assets are found */\n 'state.all-good': 'All images meet the requirements.',\n\n // ── Settings dialog ─────────────────────────────────────────────────────\n /** Settings dialog header */\n 'settings.title': 'Conversion Settings',\n /** PNG → WebP toggle label */\n 'settings.png-to-webp': 'Convert PNG → WebP',\n /** TIFF → JPG toggle label */\n 'settings.tiff-to-jpg': 'Convert TIFF → JPG (instead of WebP)',\n /** Hint below toggles */\n 'settings.apply-hint': 'Changes apply on next Refresh.',\n\n // ── Asset card ──────────────────────────────────────────────────────────\n /** Violation badge: TIFF → WebP */\n 'violation.tiff-to-webp': 'TIFF → WebP',\n /** Violation badge: TIFF → JPG */\n 'violation.tiff-to-jpg': 'TIFF → JPG',\n /** Violation badge: PNG → WebP */\n 'violation.png-to-webp': 'PNG → WebP',\n /** Violation badge: exceeds max width ({{maxWidth}} = pixel limit) */\n 'violation.width': '> {{maxWidth}}px',\n /** Violation badge: exceeds max size ({{maxSize}} = MB limit) */\n 'violation.size': '> {{maxSize}} MB',\n /** Asset size summary ({{size}} MB — {{width}}px wide) */\n 'asset.summary': '{{size}} MB — {{width}}px wide',\n /** Asset done summary ({{oldSize}} MB → {{newSize}} MB — {{width}}px wide) */\n 'asset.summary-done': '{{oldSize}} MB → {{newSize}} MB{{reduction}} — {{width}}px wide',\n /** Reduction percentage shown after size (e.g. \" (−32%)\") */\n 'asset.reduction': ' (−{{percent}}%)',\n /** Width reduction badge ({{oldWidth}}px → {{newWidth}}px) */\n 'asset.width-reduced': '{{oldWidth}}px → {{newWidth}}px',\n /** Process button */\n 'asset.process': 'Process',\n /** Done badge */\n 'asset.done': 'Done',\n /** Retry button */\n 'asset.retry': 'Retry',\n}\n"],"names":[],"mappings":";AAAA,IAAA,YAAe;AAAA;AAAA;AAAA,EAGX,cAAc;AAAA;AAAA;AAAA,EAId,gBAAgB;AAAA;AAAA,EAEhB,sBACI;AAAA;AAAA;AAAA,EAIJ,kBAAkB;AAAA;AAAA,EAElB,sBAAsB;AAAA;AAAA,EAEtB,yBAAyB;AAAA;AAAA,EAEzB,mBAAmB;AAAA;AAAA;AAAA,EAInB,kBAAkB;AAAA;AAAA,EAElB,qBAAqB;AAAA;AAAA,EAErB,eAAe;AAAA;AAAA,EAEf,iBAAiB;AAAA;AAAA;AAAA,EAIjB,kBAAkB;AAAA;AAAA,EAElB,kBAAkB;AAAA;AAAA;AAAA,EAIlB,kBAAkB;AAAA;AAAA,EAElB,wBAAwB;AAAA;AAAA,EAExB,wBAAwB;AAAA;AAAA,EAExB,uBAAuB;AAAA;AAAA;AAAA,EAIvB,0BAA0B;AAAA;AAAA,EAE1B,yBAAyB;AAAA;AAAA,EAEzB,yBAAyB;AAAA;AAAA,EAEzB,mBAAmB;AAAA;AAAA,EAEnB,kBAAkB;AAAA;AAAA,EAElB,iBAAiB;AAAA;AAAA,EAEjB,sBAAsB;AAAA;AAAA,EAEtB,mBAAmB;AAAA;AAAA,EAEnB,uBAAuB;AAAA;AAAA,EAEvB,iBAAiB;AAAA;AAAA,EAEjB,cAAc;AAAA;AAAA,EAEd,eAAe;AACnB;;"}
|
|
@@ -1,4 +1,70 @@
|
|
|
1
|
-
var resources = {
|
|
1
|
+
var resources = {
|
|
2
|
+
// ── Tool ────────────────────────────────────────────────────────────────
|
|
3
|
+
/** Tool title shown in the Studio sidebar */
|
|
4
|
+
"tool.title": "Image Resizer",
|
|
5
|
+
// ── Header ──────────────────────────────────────────────────────────────
|
|
6
|
+
/** Main heading on the tool page */
|
|
7
|
+
"header.title": "Image Resizer",
|
|
8
|
+
/** Description below the heading ({{maxWidth}} = pixel limit, {{maxSize}} = MB limit) */
|
|
9
|
+
"header.description": "Converts TIFF images to WebP. Resizes/compresses all images to fit within {{maxWidth}}px / {{maxSize}} MB.",
|
|
10
|
+
// ── Actions ─────────────────────────────────────────────────────────────
|
|
11
|
+
/** Refresh button label */
|
|
12
|
+
"action.refresh": "Refresh",
|
|
13
|
+
/** Process-all button label ({{count}} = number of pending assets) */
|
|
14
|
+
"action.process-all": "Process All ({{count}})",
|
|
15
|
+
/** Label for the button that lets the current in-flight tasks finish */
|
|
16
|
+
"action.finish-ongoing": "Finish ongoing tasks ({{count}})",
|
|
17
|
+
/** Label for the stop button that cancels the queue immediately */
|
|
18
|
+
"action.stop-all": "Stop All (possible data loss)",
|
|
19
|
+
// ── Status badges ───────────────────────────────────────────────────────
|
|
20
|
+
/** Pending badge ({{count}} = number) */
|
|
21
|
+
"status.pending": "{{count}} pending",
|
|
22
|
+
/** Processing badge */
|
|
23
|
+
"status.processing": "{{count}} processing",
|
|
24
|
+
/** Done badge */
|
|
25
|
+
"status.done": "{{count}} done",
|
|
26
|
+
/** Failed badge */
|
|
27
|
+
"status.failed": "{{count}} failed",
|
|
28
|
+
// ── Empty / loading states ──────────────────────────────────────────────
|
|
29
|
+
/** Shown while scanning assets */
|
|
30
|
+
"state.scanning": "Scanning assets\u2026",
|
|
31
|
+
/** Shown when no violating assets are found */
|
|
32
|
+
"state.all-good": "All images meet the requirements.",
|
|
33
|
+
// ── Settings dialog ─────────────────────────────────────────────────────
|
|
34
|
+
/** Settings dialog header */
|
|
35
|
+
"settings.title": "Conversion Settings",
|
|
36
|
+
/** PNG → WebP toggle label */
|
|
37
|
+
"settings.png-to-webp": "Convert PNG \u2192 WebP",
|
|
38
|
+
/** TIFF → JPG toggle label */
|
|
39
|
+
"settings.tiff-to-jpg": "Convert TIFF \u2192 JPG (instead of WebP)",
|
|
40
|
+
/** Hint below toggles */
|
|
41
|
+
"settings.apply-hint": "Changes apply on next Refresh.",
|
|
42
|
+
// ── Asset card ──────────────────────────────────────────────────────────
|
|
43
|
+
/** Violation badge: TIFF → WebP */
|
|
44
|
+
"violation.tiff-to-webp": "TIFF \u2192 WebP",
|
|
45
|
+
/** Violation badge: TIFF → JPG */
|
|
46
|
+
"violation.tiff-to-jpg": "TIFF \u2192 JPG",
|
|
47
|
+
/** Violation badge: PNG → WebP */
|
|
48
|
+
"violation.png-to-webp": "PNG \u2192 WebP",
|
|
49
|
+
/** Violation badge: exceeds max width ({{maxWidth}} = pixel limit) */
|
|
50
|
+
"violation.width": "> {{maxWidth}}px",
|
|
51
|
+
/** Violation badge: exceeds max size ({{maxSize}} = MB limit) */
|
|
52
|
+
"violation.size": "> {{maxSize}} MB",
|
|
53
|
+
/** Asset size summary ({{size}} MB — {{width}}px wide) */
|
|
54
|
+
"asset.summary": "{{size}} MB \u2014 {{width}}px wide",
|
|
55
|
+
/** Asset done summary ({{oldSize}} MB → {{newSize}} MB — {{width}}px wide) */
|
|
56
|
+
"asset.summary-done": "{{oldSize}} MB \u2192 {{newSize}} MB{{reduction}} \u2014 {{width}}px wide",
|
|
57
|
+
/** Reduction percentage shown after size (e.g. " (−32%)") */
|
|
58
|
+
"asset.reduction": " (\u2212{{percent}}%)",
|
|
59
|
+
/** Width reduction badge ({{oldWidth}}px → {{newWidth}}px) */
|
|
60
|
+
"asset.width-reduced": "{{oldWidth}}px \u2192 {{newWidth}}px",
|
|
61
|
+
/** Process button */
|
|
62
|
+
"asset.process": "Process",
|
|
63
|
+
/** Done badge */
|
|
64
|
+
"asset.done": "Done",
|
|
65
|
+
/** Retry button */
|
|
66
|
+
"asset.retry": "Retry"
|
|
67
|
+
};
|
|
2
68
|
export {
|
|
3
69
|
resources as default
|
|
4
70
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resources.mjs","sources":["../../src/i18n/resources.ts"],"sourcesContent":["export default {}\n"],"names":[],"mappings":"AAAA,IAAA,YAAe,
|
|
1
|
+
{"version":3,"file":"resources.mjs","sources":["../../src/i18n/resources.ts"],"sourcesContent":["export default {\n // ── Tool ────────────────────────────────────────────────────────────────\n /** Tool title shown in the Studio sidebar */\n 'tool.title': 'Image Resizer',\n\n // ── Header ──────────────────────────────────────────────────────────────\n /** Main heading on the tool page */\n 'header.title': 'Image Resizer',\n /** Description below the heading ({{maxWidth}} = pixel limit, {{maxSize}} = MB limit) */\n 'header.description':\n 'Converts TIFF images to WebP. Resizes/compresses all images to fit within {{maxWidth}}px / {{maxSize}} MB.',\n\n // ── Actions ─────────────────────────────────────────────────────────────\n /** Refresh button label */\n 'action.refresh': 'Refresh',\n /** Process-all button label ({{count}} = number of pending assets) */\n 'action.process-all': 'Process All ({{count}})',\n /** Label for the button that lets the current in-flight tasks finish */\n 'action.finish-ongoing': 'Finish ongoing tasks ({{count}})',\n /** Label for the stop button that cancels the queue immediately */\n 'action.stop-all': 'Stop All (possible data loss)',\n\n // ── Status badges ───────────────────────────────────────────────────────\n /** Pending badge ({{count}} = number) */\n 'status.pending': '{{count}} pending',\n /** Processing badge */\n 'status.processing': '{{count}} processing',\n /** Done badge */\n 'status.done': '{{count}} done',\n /** Failed badge */\n 'status.failed': '{{count}} failed',\n\n // ── Empty / loading states ──────────────────────────────────────────────\n /** Shown while scanning assets */\n 'state.scanning': 'Scanning assets…',\n /** Shown when no violating assets are found */\n 'state.all-good': 'All images meet the requirements.',\n\n // ── Settings dialog ─────────────────────────────────────────────────────\n /** Settings dialog header */\n 'settings.title': 'Conversion Settings',\n /** PNG → WebP toggle label */\n 'settings.png-to-webp': 'Convert PNG → WebP',\n /** TIFF → JPG toggle label */\n 'settings.tiff-to-jpg': 'Convert TIFF → JPG (instead of WebP)',\n /** Hint below toggles */\n 'settings.apply-hint': 'Changes apply on next Refresh.',\n\n // ── Asset card ──────────────────────────────────────────────────────────\n /** Violation badge: TIFF → WebP */\n 'violation.tiff-to-webp': 'TIFF → WebP',\n /** Violation badge: TIFF → JPG */\n 'violation.tiff-to-jpg': 'TIFF → JPG',\n /** Violation badge: PNG → WebP */\n 'violation.png-to-webp': 'PNG → WebP',\n /** Violation badge: exceeds max width ({{maxWidth}} = pixel limit) */\n 'violation.width': '> {{maxWidth}}px',\n /** Violation badge: exceeds max size ({{maxSize}} = MB limit) */\n 'violation.size': '> {{maxSize}} MB',\n /** Asset size summary ({{size}} MB — {{width}}px wide) */\n 'asset.summary': '{{size}} MB — {{width}}px wide',\n /** Asset done summary ({{oldSize}} MB → {{newSize}} MB — {{width}}px wide) */\n 'asset.summary-done': '{{oldSize}} MB → {{newSize}} MB{{reduction}} — {{width}}px wide',\n /** Reduction percentage shown after size (e.g. \" (−32%)\") */\n 'asset.reduction': ' (−{{percent}}%)',\n /** Width reduction badge ({{oldWidth}}px → {{newWidth}}px) */\n 'asset.width-reduced': '{{oldWidth}}px → {{newWidth}}px',\n /** Process button */\n 'asset.process': 'Process',\n /** Done badge */\n 'asset.done': 'Done',\n /** Retry button */\n 'asset.retry': 'Retry',\n}\n"],"names":[],"mappings":"AAAA,IAAA,YAAe;AAAA;AAAA;AAAA,EAGX,cAAc;AAAA;AAAA;AAAA,EAId,gBAAgB;AAAA;AAAA,EAEhB,sBACI;AAAA;AAAA;AAAA,EAIJ,kBAAkB;AAAA;AAAA,EAElB,sBAAsB;AAAA;AAAA,EAEtB,yBAAyB;AAAA;AAAA,EAEzB,mBAAmB;AAAA;AAAA;AAAA,EAInB,kBAAkB;AAAA;AAAA,EAElB,qBAAqB;AAAA;AAAA,EAErB,eAAe;AAAA;AAAA,EAEf,iBAAiB;AAAA;AAAA;AAAA,EAIjB,kBAAkB;AAAA;AAAA,EAElB,kBAAkB;AAAA;AAAA;AAAA,EAIlB,kBAAkB;AAAA;AAAA,EAElB,wBAAwB;AAAA;AAAA,EAExB,wBAAwB;AAAA;AAAA,EAExB,uBAAuB;AAAA;AAAA;AAAA,EAIvB,0BAA0B;AAAA;AAAA,EAE1B,yBAAyB;AAAA;AAAA,EAEzB,yBAAyB;AAAA;AAAA,EAEzB,mBAAmB;AAAA;AAAA,EAEnB,kBAAkB;AAAA;AAAA,EAElB,iBAAiB;AAAA;AAAA,EAEjB,sBAAsB;AAAA;AAAA,EAEtB,mBAAmB;AAAA;AAAA,EAEnB,uBAAuB;AAAA;AAAA,EAEvB,iBAAiB;AAAA;AAAA,EAEjB,cAAc;AAAA;AAAA,EAEd,eAAe;AACnB;"}
|
package/dist/index.js
CHANGED
|
@@ -66,7 +66,7 @@ function buildReplacementPatch(obj, oldId, newId, path = "") {
|
|
|
66
66
|
{}
|
|
67
67
|
);
|
|
68
68
|
const record = obj;
|
|
69
|
-
return record.asset?._ref === oldId ? { [path ? `${path}.asset` : "asset"]: { _type: "reference", _ref: newId } } : Object.keys(record).filter((k) => !k.startsWith("_")).reduce(
|
|
69
|
+
return record.asset?._ref === oldId ? { [path ? `${path}.asset` : "asset"]: { _type: "reference", _ref: newId } } : record._type === "reference" && record._ref === oldId ? { [path || "_ref"]: { _type: "reference", _ref: newId } } : Object.keys(record).filter((k) => !k.startsWith("_")).reduce(
|
|
70
70
|
(acc, key) => {
|
|
71
71
|
const childPath = path ? `${path}.${key}` : key;
|
|
72
72
|
return Object.assign(
|
|
@@ -77,6 +77,25 @@ function buildReplacementPatch(obj, oldId, newId, path = "") {
|
|
|
77
77
|
{}
|
|
78
78
|
);
|
|
79
79
|
}
|
|
80
|
+
const ASSET_METADATA_FIELDS = [
|
|
81
|
+
"title",
|
|
82
|
+
"description",
|
|
83
|
+
"altText",
|
|
84
|
+
"creditLine",
|
|
85
|
+
"source",
|
|
86
|
+
"opt"
|
|
87
|
+
];
|
|
88
|
+
async function copyAssetMetadata(client, oldAssetId, newAssetId) {
|
|
89
|
+
const projection = ASSET_METADATA_FIELDS.join(", "), oldMeta = await client.fetch(
|
|
90
|
+
`*[_id == $id][0]{ ${projection} }`,
|
|
91
|
+
{ id: oldAssetId }
|
|
92
|
+
);
|
|
93
|
+
if (!oldMeta) return;
|
|
94
|
+
const patch = {};
|
|
95
|
+
for (const field of ASSET_METADATA_FIELDS)
|
|
96
|
+
oldMeta[field] !== void 0 && oldMeta[field] !== null && (patch[field] = oldMeta[field]);
|
|
97
|
+
Object.keys(patch).length > 0 && await client.patch(newAssetId).set(patch).commit();
|
|
98
|
+
}
|
|
80
99
|
const validateImageSize = async (value, context) => {
|
|
81
100
|
if (!value?.asset?._ref) return !0;
|
|
82
101
|
const asset = await context.getClient({ apiVersion: "2025-02-19" }).fetch(
|
|
@@ -92,21 +111,20 @@ const validateImageSize = async (value, context) => {
|
|
|
92
111
|
return `Image size (${sizeMB}MB) exceeds the maximum of ${maxMB}MB`;
|
|
93
112
|
}
|
|
94
113
|
return asset.width && asset.width > exports.IMAGE_MAX_WIDTH ? `Image width (${asset.width}px) exceeds the maximum of ${exports.IMAGE_MAX_WIDTH}px` : !0;
|
|
95
|
-
}, MAX_SIZE_MB$1 = exports.IMAGE_MAX_SIZE / 1024 / 1024
|
|
96
|
-
|
|
97
|
-
width: `> ${exports.IMAGE_MAX_WIDTH}px`,
|
|
98
|
-
size: `> ${MAX_SIZE_MB$1} MB`
|
|
99
|
-
};
|
|
100
|
-
function formatViolationLabel(settings) {
|
|
114
|
+
}, MAX_SIZE_MB$1 = exports.IMAGE_MAX_SIZE / 1024 / 1024;
|
|
115
|
+
function formatViolationLabel(settings, t) {
|
|
101
116
|
const parts = [];
|
|
102
|
-
return parts.push(
|
|
117
|
+
return parts.push(
|
|
118
|
+
settings.tiffToJpg ? t("violation.tiff-to-jpg") : t("violation.tiff-to-webp")
|
|
119
|
+
), settings.pngToWebp && parts.push(t("violation.png-to-webp")), parts.join(", ");
|
|
103
120
|
}
|
|
104
121
|
function ViolationBadge({
|
|
105
122
|
type,
|
|
106
|
-
settings
|
|
123
|
+
settings,
|
|
124
|
+
t
|
|
107
125
|
}) {
|
|
108
|
-
|
|
109
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { tone: "caution", size: 1, children: label });
|
|
126
|
+
let label;
|
|
127
|
+
return type === "format" ? label = formatViolationLabel(settings, t) : type === "width" ? label = t("violation.width", { maxWidth: exports.IMAGE_MAX_WIDTH }) : label = t("violation.size", { maxSize: MAX_SIZE_MB$1 }), /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { tone: "caution", size: 1, children: label });
|
|
110
128
|
}
|
|
111
129
|
function statusTone(status) {
|
|
112
130
|
return {
|
|
@@ -121,7 +139,7 @@ function AssetCard({
|
|
|
121
139
|
onProcess,
|
|
122
140
|
settings
|
|
123
141
|
}) {
|
|
124
|
-
const isDone = asset.status === "done" && asset.newUrl, thumbUrl = isDone ? asset.newUrl : asset.url, sizeReduction = isDone && asset.newSize != null ? Math.round((1 - asset.newSize / asset.size) * 100) : null;
|
|
142
|
+
const { t } = sanity.useTranslation(imageResizerLocaleNamespace), isDone = asset.status === "done" && asset.newUrl, thumbUrl = isDone ? asset.newUrl : asset.url, sizeReduction = isDone && asset.newSize != null ? Math.round((1 - asset.newSize / asset.size) * 100) : null;
|
|
125
143
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
126
144
|
ui.Card,
|
|
127
145
|
{
|
|
@@ -145,32 +163,22 @@ function AssetCard({
|
|
|
145
163
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, style: { flex: 1, minWidth: 0 }, children: [
|
|
146
164
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Box, { style: { width: "100%", minWidth: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, weight: "semibold", children: isDone ? asset.newFilename || asset.originalFilename || asset._id : asset.originalFilename || asset._id }) }),
|
|
147
165
|
isDone ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
148
|
-
/* @__PURE__ */ jsxRuntime.
|
|
149
|
-
(asset.size / 1024 / 1024).toFixed(1),
|
|
150
|
-
|
|
151
|
-
" ",
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
"px wide"
|
|
159
|
-
] }),
|
|
160
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { gap: 2, wrap: "wrap", children: asset.newWidth != null && asset.newWidth < asset.width && /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { tone: "positive", size: 1, children: [
|
|
161
|
-
asset.width,
|
|
162
|
-
"px \u2192 ",
|
|
163
|
-
asset.newWidth,
|
|
164
|
-
"px"
|
|
165
|
-
] }) })
|
|
166
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, muted: !0, style: { wordBreak: "break-word" }, children: t("asset.summary-done", {
|
|
167
|
+
oldSize: (asset.size / 1024 / 1024).toFixed(1),
|
|
168
|
+
newSize: (asset.newSize / 1024 / 1024).toFixed(1),
|
|
169
|
+
reduction: sizeReduction !== null && sizeReduction > 0 ? t("asset.reduction", { percent: sizeReduction }) : "",
|
|
170
|
+
width: asset.newWidth
|
|
171
|
+
}) }),
|
|
172
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { gap: 2, wrap: "wrap", children: asset.newWidth != null && asset.newWidth < asset.width && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { tone: "positive", size: 1, children: t("asset.width-reduced", {
|
|
173
|
+
oldWidth: asset.width,
|
|
174
|
+
newWidth: asset.newWidth
|
|
175
|
+
}) }) })
|
|
166
176
|
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
167
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { gap: 2, wrap: "wrap", children: asset.violations.map((v) => /* @__PURE__ */ jsxRuntime.jsx(ViolationBadge, { type: v, settings }, v)) }),
|
|
168
|
-
/* @__PURE__ */ jsxRuntime.
|
|
169
|
-
(asset.size / 1024 / 1024).toFixed(1),
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
"px wide"
|
|
173
|
-
] })
|
|
177
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { gap: 2, wrap: "wrap", children: asset.violations.map((v) => /* @__PURE__ */ jsxRuntime.jsx(ViolationBadge, { type: v, settings, t }, v)) }),
|
|
178
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, muted: !0, style: { wordBreak: "break-word" }, children: t("asset.summary", {
|
|
179
|
+
size: (asset.size / 1024 / 1024).toFixed(1),
|
|
180
|
+
width: asset.width
|
|
181
|
+
}) })
|
|
174
182
|
] }),
|
|
175
183
|
asset.status === "error" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
176
184
|
ui.Text,
|
|
@@ -188,18 +196,18 @@ function AssetCard({
|
|
|
188
196
|
asset.status === "idle" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
189
197
|
ui.Button,
|
|
190
198
|
{
|
|
191
|
-
text: "
|
|
199
|
+
text: t("asset.process"),
|
|
192
200
|
mode: "ghost",
|
|
193
201
|
tone: "primary",
|
|
194
202
|
onClick: () => onProcess(asset)
|
|
195
203
|
}
|
|
196
204
|
),
|
|
197
205
|
asset.status === "processing" && /* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {}),
|
|
198
|
-
asset.status === "done" && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { tone: "positive", children: "
|
|
206
|
+
asset.status === "done" && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { tone: "positive", children: t("asset.done") }),
|
|
199
207
|
asset.status === "error" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
200
208
|
ui.Button,
|
|
201
209
|
{
|
|
202
|
-
text: "
|
|
210
|
+
text: t("asset.retry"),
|
|
203
211
|
mode: "ghost",
|
|
204
212
|
tone: "critical",
|
|
205
213
|
onClick: () => onProcess(asset)
|
|
@@ -227,7 +235,7 @@ function loadSettings() {
|
|
|
227
235
|
return DEFAULT_SETTINGS;
|
|
228
236
|
}
|
|
229
237
|
function ImageResizerView() {
|
|
230
|
-
const client = sanity.useClient({ apiVersion: "2025-02-19" }), [assets, setAssets] = react.useState([]), [loading, setLoading] = react.useState(!0), [processingAll, setProcessingAll] = react.useState(!1), [settings, setSettings] = react.useState(loadSettings), [showSettings, setShowSettings] = react.useState(!1), updateSettings = react.useCallback(
|
|
238
|
+
const client = sanity.useClient({ apiVersion: "2025-02-19" }), { t } = sanity.useTranslation(imageResizerLocaleNamespace), [assets, setAssets] = react.useState([]), [loading, setLoading] = react.useState(!0), [processingAll, setProcessingAll] = react.useState(!1), [settings, setSettings] = react.useState(loadSettings), [showSettings, setShowSettings] = react.useState(!1), updateSettings = react.useCallback(
|
|
231
239
|
(updater) => {
|
|
232
240
|
setSettings((prev) => {
|
|
233
241
|
const next = updater(prev);
|
|
@@ -239,7 +247,7 @@ function ImageResizerView() {
|
|
|
239
247
|
react.useEffect(() => {
|
|
240
248
|
assetsRef.current = assets;
|
|
241
249
|
}, [assets]);
|
|
242
|
-
const fetchAssets = react.useCallback(async () => {
|
|
250
|
+
const cancelRef = react.useRef(!1), fetchAssets = react.useCallback(async () => {
|
|
243
251
|
setLoading(!0);
|
|
244
252
|
try {
|
|
245
253
|
const raw = await client.fetch(
|
|
@@ -291,7 +299,9 @@ function ImageResizerView() {
|
|
|
291
299
|
const baseName = asset.originalFilename?.replace(/\.[^.]+$/, "") || "image", newAsset = await client.assets.upload("image", blob, {
|
|
292
300
|
filename: `${baseName}.${outFormat}`,
|
|
293
301
|
contentType: `image/${outFormat}`
|
|
294
|
-
})
|
|
302
|
+
});
|
|
303
|
+
await copyAssetMetadata(client, asset._id, newAsset._id);
|
|
304
|
+
const refs = await client.fetch(
|
|
295
305
|
"*[references($id)]{ _id }",
|
|
296
306
|
{ id: asset._id }
|
|
297
307
|
);
|
|
@@ -320,19 +330,31 @@ function ImageResizerView() {
|
|
|
320
330
|
},
|
|
321
331
|
[client, updateAsset, settings]
|
|
322
332
|
), processAll = react.useCallback(async () => {
|
|
323
|
-
setProcessingAll(!0);
|
|
333
|
+
cancelRef.current = !1, setProcessingAll(!0);
|
|
324
334
|
const pending = assetsRef.current.filter(
|
|
325
335
|
(a) => a.status === "idle" || a.status === "error"
|
|
326
336
|
);
|
|
327
337
|
let idx = 0;
|
|
328
338
|
const next = async () => {
|
|
329
|
-
for (; idx < pending.length; ) {
|
|
339
|
+
for (; idx < pending.length && !cancelRef.current; ) {
|
|
330
340
|
const asset = pending[idx++];
|
|
331
341
|
await processAsset(asset);
|
|
332
342
|
}
|
|
333
343
|
};
|
|
334
344
|
await Promise.all(Array.from({ length: CONCURRENCY }, () => next())), setProcessingAll(!1);
|
|
335
|
-
}, [processAsset])
|
|
345
|
+
}, [processAsset]);
|
|
346
|
+
react.useCallback(() => {
|
|
347
|
+
cancelRef.current = !0;
|
|
348
|
+
}, []), react.useEffect(() => {
|
|
349
|
+
if (!processingAll) return;
|
|
350
|
+
const handler = (e) => {
|
|
351
|
+
e.preventDefault();
|
|
352
|
+
};
|
|
353
|
+
return window.addEventListener("beforeunload", handler), () => window.removeEventListener("beforeunload", handler);
|
|
354
|
+
}, [processingAll]), react.useEffect(() => () => {
|
|
355
|
+
cancelRef.current = !0;
|
|
356
|
+
}, []);
|
|
357
|
+
const counts = react.useMemo(
|
|
336
358
|
() => ({
|
|
337
359
|
pending: assets.filter((a) => a.status === "idle").length,
|
|
338
360
|
processing: assets.filter((a) => a.status === "processing").length,
|
|
@@ -351,20 +373,17 @@ function ImageResizerView() {
|
|
|
351
373
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 5, children: [
|
|
352
374
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "flex-start", justify: "space-between", gap: 4, wrap: "wrap", children: [
|
|
353
375
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 2, style: { flex: 1, minWidth: 0 }, children: [
|
|
354
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { size: 2, children: "
|
|
355
|
-
/* @__PURE__ */ jsxRuntime.
|
|
376
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { size: 2, children: t("header.title") }),
|
|
377
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
356
378
|
ui.Card,
|
|
357
379
|
{
|
|
358
380
|
size: 1,
|
|
359
381
|
tone: "transparent",
|
|
360
382
|
style: { wordBreak: "break-word" },
|
|
361
|
-
children:
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
MAX_SIZE_MB,
|
|
366
|
-
" MB."
|
|
367
|
-
]
|
|
383
|
+
children: t("header.description", {
|
|
384
|
+
maxWidth: exports.IMAGE_MAX_WIDTH,
|
|
385
|
+
maxSize: MAX_SIZE_MB
|
|
386
|
+
})
|
|
368
387
|
}
|
|
369
388
|
)
|
|
370
389
|
] }),
|
|
@@ -381,46 +400,44 @@ function ImageResizerView() {
|
|
|
381
400
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
382
401
|
ui.Button,
|
|
383
402
|
{
|
|
384
|
-
text: "
|
|
403
|
+
text: t("action.refresh"),
|
|
385
404
|
mode: "ghost",
|
|
386
405
|
onClick: fetchAssets,
|
|
387
406
|
disabled: loading || processingAll
|
|
388
407
|
}
|
|
389
408
|
),
|
|
390
|
-
counts.pending > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
409
|
+
counts.pending > 0 && !processingAll && /* @__PURE__ */ jsxRuntime.jsx(
|
|
391
410
|
ui.Button,
|
|
392
411
|
{
|
|
393
|
-
text:
|
|
412
|
+
text: t("action.process-all", { count: counts.pending }),
|
|
394
413
|
tone: "primary",
|
|
395
414
|
onClick: processAll,
|
|
396
|
-
disabled:
|
|
397
|
-
icon: processingAll ? ui.Spinner : void 0
|
|
415
|
+
disabled: loading
|
|
398
416
|
}
|
|
399
|
-
)
|
|
417
|
+
),
|
|
418
|
+
processingAll && /* @__PURE__ */ jsxRuntime.jsx(ui.Flex, { gap: 2, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
419
|
+
ui.Button,
|
|
420
|
+
{
|
|
421
|
+
text: t("action.finish-ongoing", {
|
|
422
|
+
count: counts.processing
|
|
423
|
+
}),
|
|
424
|
+
tone: "caution",
|
|
425
|
+
mode: "ghost",
|
|
426
|
+
disabled: !0
|
|
427
|
+
}
|
|
428
|
+
) })
|
|
400
429
|
] })
|
|
401
430
|
] }),
|
|
402
431
|
!loading && assets.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { gap: 3, wrap: "wrap", children: [
|
|
403
|
-
counts.pending > 0 && /* @__PURE__ */ jsxRuntime.
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
counts.processing > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { tone: "primary", children: [
|
|
408
|
-
counts.processing,
|
|
409
|
-
" processing"
|
|
410
|
-
] }),
|
|
411
|
-
counts.done > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { tone: "positive", children: [
|
|
412
|
-
counts.done,
|
|
413
|
-
" done"
|
|
414
|
-
] }),
|
|
415
|
-
counts.error > 0 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { tone: "critical", children: [
|
|
416
|
-
counts.error,
|
|
417
|
-
" failed"
|
|
418
|
-
] })
|
|
432
|
+
counts.pending > 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { tone: "caution", children: t("status.pending", { count: counts.pending }) }),
|
|
433
|
+
counts.processing > 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { tone: "primary", children: t("status.processing", { count: counts.processing }) }),
|
|
434
|
+
counts.done > 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { tone: "positive", children: t("status.done", { count: counts.done }) }),
|
|
435
|
+
counts.error > 0 && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { tone: "critical", children: t("status.failed", { count: counts.error }) })
|
|
419
436
|
] }),
|
|
420
437
|
loading ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { padding: 6, justify: "center", align: "center", gap: 3, children: [
|
|
421
438
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Spinner, {}),
|
|
422
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { muted: !0, children: "
|
|
423
|
-
] }) : assets.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { padding: 5, radius: 2, tone: "positive", border: !0, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { align: "center", children: "
|
|
439
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { muted: !0, children: t("state.scanning") })
|
|
440
|
+
] }) : assets.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Card, { padding: 5, radius: 2, tone: "positive", border: !0, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { align: "center", children: t("state.all-good") }) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Stack, { space: 2, children: assets.map((asset) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
424
441
|
AssetCard,
|
|
425
442
|
{
|
|
426
443
|
asset,
|
|
@@ -434,7 +451,7 @@ function ImageResizerView() {
|
|
|
434
451
|
ui.Dialog,
|
|
435
452
|
{
|
|
436
453
|
id: "image-resizer-settings",
|
|
437
|
-
header: "
|
|
454
|
+
header: t("settings.title"),
|
|
438
455
|
onClose: () => setShowSettings(!1),
|
|
439
456
|
width: 1,
|
|
440
457
|
children: /* @__PURE__ */ jsxRuntime.jsx(ui.Box, { padding: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Stack, { space: 4, children: [
|
|
@@ -450,7 +467,7 @@ function ImageResizerView() {
|
|
|
450
467
|
}
|
|
451
468
|
}
|
|
452
469
|
),
|
|
453
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "png-to-webp", style: { cursor: "pointer" }, children: "
|
|
470
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "png-to-webp", style: { cursor: "pointer" }, children: t("settings.png-to-webp") })
|
|
454
471
|
] }),
|
|
455
472
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.Flex, { align: "center", gap: 3, children: [
|
|
456
473
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -464,9 +481,9 @@ function ImageResizerView() {
|
|
|
464
481
|
}
|
|
465
482
|
}
|
|
466
483
|
),
|
|
467
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "tiff-to-jpg", style: { cursor: "pointer" }, children: "
|
|
484
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "tiff-to-jpg", style: { cursor: "pointer" }, children: t("settings.tiff-to-jpg") })
|
|
468
485
|
] }),
|
|
469
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, muted: !0, children: "
|
|
486
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: 1, muted: !0, children: t("settings.apply-hint") })
|
|
470
487
|
] }) })
|
|
471
488
|
}
|
|
472
489
|
)
|
|
@@ -482,7 +499,13 @@ const imageResizerPlugin = sanity.definePlugin(
|
|
|
482
499
|
{
|
|
483
500
|
name: "image-resizer",
|
|
484
501
|
title: "Image Resizer",
|
|
485
|
-
component: ImageResizerView
|
|
502
|
+
component: ImageResizerView,
|
|
503
|
+
i18n: {
|
|
504
|
+
title: {
|
|
505
|
+
key: "tool.title",
|
|
506
|
+
ns: imageResizerLocaleNamespace
|
|
507
|
+
}
|
|
508
|
+
}
|
|
486
509
|
}
|
|
487
510
|
],
|
|
488
511
|
i18n: {
|