@marimo-team/frontend 0.19.7-dev33 → 0.19.7-dev34
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/assets/{JsonOutput-Dw_ZIGoz.js → JsonOutput-DvKIRGOg.js} +10 -10
- package/dist/assets/{agent-panel-BOEtQvcD.js → agent-panel-C9codfcr.js} +1 -1
- package/dist/assets/{cell-editor-Bed2HJRi.js → cell-editor-BxhibLVM.js} +1 -1
- package/dist/assets/{column-preview-YJ759JhT.js → column-preview-C6jEPj3t.js} +1 -1
- package/dist/assets/{command-palette-c1vQwlGd.js → command-palette-D1g3pU47.js} +1 -1
- package/dist/assets/{edit-page-D-_8Dpee.js → edit-page-UqaWbc_J.js} +3 -3
- package/dist/assets/{file-explorer-panel-Bhrg_Wb0.js → file-explorer-panel-y7F8Uqi-.js} +1 -1
- package/dist/assets/{hooks-CPM5R4f-.js → hooks-D_OOStv3.js} +1 -1
- package/dist/assets/{index-DL0FpFYQ.js → index-BSBPZDCV.js} +3 -3
- package/dist/assets/{layout-C50dPCAb.js → layout-Xf51uwDc.js} +1 -1
- package/dist/assets/{panels-H0GWZM90.js → panels-D-yo_63g.js} +1 -1
- package/dist/assets/{run-page-CiOYN_zF.js → run-page-CoCql9Xm.js} +1 -1
- package/dist/assets/{scratchpad-panel-BiE33kLt.js → scratchpad-panel-DPnpCcOn.js} +1 -1
- package/dist/assets/{session-panel-DCRBt85z.js → session-panel-CjoiPf6W.js} +1 -1
- package/dist/assets/{useNotebookActions-C1MxSBln.js → useNotebookActions-XKe-QMLa.js} +1 -1
- package/dist/assets/{utilities.esm-BsBOP1QQ.js → utilities.esm-mPQPstBT.js} +1 -1
- package/dist/index.html +4 -4
- package/package.json +1 -1
- package/src/components/editor/Output.tsx +6 -10
- package/src/utils/__tests__/mime-types.test.ts +326 -0
- package/src/utils/mime-types.ts +181 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import type { MimeType } from "@/components/editor/Output";
|
|
4
|
+
import {
|
|
5
|
+
applyHidingRules,
|
|
6
|
+
createMimeConfig,
|
|
7
|
+
getDefaultMimeConfig,
|
|
8
|
+
processMimeBundle,
|
|
9
|
+
sortByPrecedence,
|
|
10
|
+
} from "../mime-types";
|
|
11
|
+
|
|
12
|
+
/** Helper to build a hiding rules Map inline */
|
|
13
|
+
function hidingRules(
|
|
14
|
+
rules: Record<string, MimeType[]>,
|
|
15
|
+
): ReadonlyMap<MimeType, ReadonlySet<MimeType>> {
|
|
16
|
+
const map = new Map<MimeType, ReadonlySet<MimeType>>();
|
|
17
|
+
for (const [trigger, toHide] of Object.entries(rules)) {
|
|
18
|
+
map.set(trigger as MimeType, new Set(toHide));
|
|
19
|
+
}
|
|
20
|
+
return map;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Helper to build a precedence Map inline */
|
|
24
|
+
function precedenceMap(types: MimeType[]): ReadonlyMap<MimeType, number> {
|
|
25
|
+
const map = new Map<MimeType, number>();
|
|
26
|
+
types.forEach((mime, i) => map.set(mime, i));
|
|
27
|
+
return map;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe("mime-types", () => {
|
|
31
|
+
describe("applyHidingRules", () => {
|
|
32
|
+
it("should return all visible when no rules match", () => {
|
|
33
|
+
const mimeTypes = new Set<MimeType>(["text/plain", "text/markdown"]);
|
|
34
|
+
const rules = hidingRules({ "text/html": ["image/png"] });
|
|
35
|
+
|
|
36
|
+
const result = applyHidingRules(mimeTypes, rules);
|
|
37
|
+
|
|
38
|
+
expect(result.visible).toEqual(new Set(["text/plain", "text/markdown"]));
|
|
39
|
+
expect(result.hidden.size).toBe(0);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should hide mime types when trigger is present", () => {
|
|
43
|
+
const mimeTypes = new Set<MimeType>([
|
|
44
|
+
"text/html",
|
|
45
|
+
"image/png",
|
|
46
|
+
"text/plain",
|
|
47
|
+
]);
|
|
48
|
+
const rules = hidingRules({ "text/html": ["image/png"] });
|
|
49
|
+
|
|
50
|
+
const result = applyHidingRules(mimeTypes, rules);
|
|
51
|
+
|
|
52
|
+
expect(result.visible).toEqual(new Set(["text/html", "text/plain"]));
|
|
53
|
+
expect(result.hidden).toEqual(new Set(["image/png"]));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should not hide markdown when html is present (per requirements)", () => {
|
|
57
|
+
const mimeTypes = new Set<MimeType>([
|
|
58
|
+
"text/html",
|
|
59
|
+
"text/markdown",
|
|
60
|
+
"image/png",
|
|
61
|
+
]);
|
|
62
|
+
const rules = hidingRules({ "text/html": ["image/png"] });
|
|
63
|
+
|
|
64
|
+
const result = applyHidingRules(mimeTypes, rules);
|
|
65
|
+
|
|
66
|
+
expect(result.visible.has("text/markdown")).toBe(true);
|
|
67
|
+
expect(result.visible.has("text/html")).toBe(true);
|
|
68
|
+
expect(result.hidden.has("image/png")).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should handle multiple matching rules", () => {
|
|
72
|
+
const mimeTypes = new Set<MimeType>([
|
|
73
|
+
"text/html",
|
|
74
|
+
"application/vnd.vegalite.v5+json",
|
|
75
|
+
"image/png",
|
|
76
|
+
"image/jpeg",
|
|
77
|
+
]);
|
|
78
|
+
const rules = hidingRules({
|
|
79
|
+
"text/html": ["image/png"],
|
|
80
|
+
"application/vnd.vegalite.v5+json": ["image/jpeg"],
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const result = applyHidingRules(mimeTypes, rules);
|
|
84
|
+
|
|
85
|
+
expect(result.hidden).toEqual(new Set(["image/png", "image/jpeg"]));
|
|
86
|
+
expect(result.visible).toEqual(
|
|
87
|
+
new Set(["text/html", "application/vnd.vegalite.v5+json"]),
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should handle empty mime types", () => {
|
|
92
|
+
const mimeTypes = new Set<MimeType>();
|
|
93
|
+
const rules = hidingRules({ "text/html": ["image/png"] });
|
|
94
|
+
|
|
95
|
+
const result = applyHidingRules(mimeTypes, rules);
|
|
96
|
+
|
|
97
|
+
expect(result.visible.size).toBe(0);
|
|
98
|
+
expect(result.hidden.size).toBe(0);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should handle empty rules", () => {
|
|
102
|
+
const mimeTypes = new Set<MimeType>(["text/html", "image/png"]);
|
|
103
|
+
const rules = hidingRules({});
|
|
104
|
+
|
|
105
|
+
const result = applyHidingRules(mimeTypes, rules);
|
|
106
|
+
|
|
107
|
+
expect(result.visible).toEqual(mimeTypes);
|
|
108
|
+
expect(result.hidden.size).toBe(0);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should hide a type that is also a trigger if configured", () => {
|
|
112
|
+
const mimeTypes = new Set<MimeType>(["text/html", "text/plain"]);
|
|
113
|
+
const rules = hidingRules({ "text/html": ["text/html"] });
|
|
114
|
+
|
|
115
|
+
const result = applyHidingRules(mimeTypes, rules);
|
|
116
|
+
|
|
117
|
+
expect(result.hidden.has("text/html")).toBe(true);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should not hide types that are not present", () => {
|
|
121
|
+
const mimeTypes = new Set<MimeType>(["text/html"]);
|
|
122
|
+
const rules = hidingRules({
|
|
123
|
+
"text/html": ["image/png", "image/jpeg"],
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const result = applyHidingRules(mimeTypes, rules);
|
|
127
|
+
|
|
128
|
+
expect(result.hidden.size).toBe(0);
|
|
129
|
+
expect(result.visible).toEqual(new Set(["text/html"]));
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("sortByPrecedence", () => {
|
|
134
|
+
it("should sort entries by precedence order", () => {
|
|
135
|
+
const entries: Array<[MimeType, string]> = [
|
|
136
|
+
["text/plain", "plain"],
|
|
137
|
+
["text/html", "html"],
|
|
138
|
+
["image/png", "png"],
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
const result = sortByPrecedence(
|
|
142
|
+
entries,
|
|
143
|
+
precedenceMap(["text/html", "image/png", "text/plain"]),
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
expect(result.map(([m]) => m)).toEqual([
|
|
147
|
+
"text/html",
|
|
148
|
+
"image/png",
|
|
149
|
+
"text/plain",
|
|
150
|
+
]);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("should place unknown mime types at the end", () => {
|
|
154
|
+
const entries: Array<[MimeType, string]> = [
|
|
155
|
+
["text/plain", "plain"],
|
|
156
|
+
["text/html", "html"],
|
|
157
|
+
["application/json", "json"],
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
const result = sortByPrecedence(entries, precedenceMap(["text/html"]));
|
|
161
|
+
|
|
162
|
+
expect(result[0][0]).toBe("text/html");
|
|
163
|
+
expect(result.slice(1).map(([m]) => m)).toEqual([
|
|
164
|
+
"text/plain",
|
|
165
|
+
"application/json",
|
|
166
|
+
]);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("should handle empty entries", () => {
|
|
170
|
+
const result = sortByPrecedence([], precedenceMap(["text/html"]));
|
|
171
|
+
|
|
172
|
+
expect(result).toEqual([]);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("should handle empty precedence", () => {
|
|
176
|
+
const entries: Array<[MimeType, string]> = [
|
|
177
|
+
["text/plain", "plain"],
|
|
178
|
+
["text/html", "html"],
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
const result = sortByPrecedence(entries, precedenceMap([]));
|
|
182
|
+
|
|
183
|
+
expect(result.map(([m]) => m)).toEqual(["text/plain", "text/html"]);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("should not mutate original array", () => {
|
|
187
|
+
const entries: Array<[MimeType, string]> = [
|
|
188
|
+
["text/plain", "plain"],
|
|
189
|
+
["text/html", "html"],
|
|
190
|
+
];
|
|
191
|
+
const original = [...entries];
|
|
192
|
+
|
|
193
|
+
sortByPrecedence(entries, precedenceMap(["text/html", "text/plain"]));
|
|
194
|
+
|
|
195
|
+
expect(entries).toEqual(original);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe("processMimeBundle", () => {
|
|
200
|
+
it("should filter and sort mime entries", () => {
|
|
201
|
+
const entries: Array<[MimeType, string]> = [
|
|
202
|
+
["text/plain", "plain"],
|
|
203
|
+
["text/html", "html"],
|
|
204
|
+
["image/png", "png"],
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
const config = createMimeConfig({
|
|
208
|
+
precedence: ["text/html", "text/plain"],
|
|
209
|
+
hidingRules: { "text/html": ["image/png"] },
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const result = processMimeBundle(entries, config);
|
|
213
|
+
|
|
214
|
+
expect(result.entries.map(([m]) => m)).toEqual([
|
|
215
|
+
"text/html",
|
|
216
|
+
"text/plain",
|
|
217
|
+
]);
|
|
218
|
+
expect(result.hidden).toEqual(["image/png"]);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("should handle empty entries", () => {
|
|
222
|
+
const result = processMimeBundle([]);
|
|
223
|
+
|
|
224
|
+
expect(result.entries).toEqual([]);
|
|
225
|
+
expect(result.hidden).toEqual([]);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("should use default config when not provided", () => {
|
|
229
|
+
const entries: Array<[MimeType, string]> = [
|
|
230
|
+
["text/html", "html"],
|
|
231
|
+
["image/png", "png"],
|
|
232
|
+
["text/markdown", "md"],
|
|
233
|
+
];
|
|
234
|
+
|
|
235
|
+
const result = processMimeBundle(entries);
|
|
236
|
+
|
|
237
|
+
expect(result.entries.map(([m]) => m)).not.toContain("image/png");
|
|
238
|
+
expect(result.entries.map(([m]) => m)).toContain("text/markdown");
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("should preserve data associated with mime types", () => {
|
|
242
|
+
const htmlData = { content: "<h1>Hello</h1>" };
|
|
243
|
+
const entries: Array<[MimeType, typeof htmlData]> = [
|
|
244
|
+
["text/html", htmlData],
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
const result = processMimeBundle(entries);
|
|
248
|
+
|
|
249
|
+
expect(result.entries[0][1]).toBe(htmlData);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("should sort by precedence after filtering", () => {
|
|
253
|
+
const entries: Array<[MimeType, string]> = [
|
|
254
|
+
["text/plain", "plain"],
|
|
255
|
+
["text/markdown", "md"],
|
|
256
|
+
["text/html", "html"],
|
|
257
|
+
];
|
|
258
|
+
|
|
259
|
+
const result = processMimeBundle(entries);
|
|
260
|
+
|
|
261
|
+
expect(result.entries[0][0]).toBe("text/html");
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
describe("getDefaultMimeConfig", () => {
|
|
266
|
+
const config = getDefaultMimeConfig();
|
|
267
|
+
|
|
268
|
+
it("should have text/html as highest precedence", () => {
|
|
269
|
+
expect(config.precedence.get("text/html")).toBe(0);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("should hide image types when html is present", () => {
|
|
273
|
+
const htmlHides = config.hidingRules.get("text/html");
|
|
274
|
+
|
|
275
|
+
expect(htmlHides).toBeDefined();
|
|
276
|
+
expect(htmlHides?.has("image/png")).toBe(true);
|
|
277
|
+
expect(htmlHides?.has("image/jpeg")).toBe(true);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("should NOT hide markdown when html is present", () => {
|
|
281
|
+
const htmlHides = config.hidingRules.get("text/html");
|
|
282
|
+
|
|
283
|
+
expect(htmlHides?.has("text/markdown")).toBeFalsy();
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("should hide images when vega charts are present", () => {
|
|
287
|
+
const vegaHides = config.hidingRules.get(
|
|
288
|
+
"application/vnd.vegalite.v5+json",
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
expect(vegaHides).toBeDefined();
|
|
292
|
+
expect(vegaHides?.has("image/png")).toBe(true);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("should return the same instance on repeated calls", () => {
|
|
296
|
+
expect(getDefaultMimeConfig()).toBe(config);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
describe("createMimeConfig", () => {
|
|
301
|
+
it("should compile precedence array into a Map", () => {
|
|
302
|
+
const config = createMimeConfig({
|
|
303
|
+
precedence: ["text/html", "image/png"],
|
|
304
|
+
hidingRules: {},
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
expect(config.precedence.get("text/html")).toBe(0);
|
|
308
|
+
expect(config.precedence.get("image/png")).toBe(1);
|
|
309
|
+
expect(config.precedence.has("text/plain")).toBe(false);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("should compile hiding rules into Map<MimeType, Set>", () => {
|
|
313
|
+
const config = createMimeConfig({
|
|
314
|
+
precedence: [],
|
|
315
|
+
hidingRules: {
|
|
316
|
+
"text/html": ["image/png", "image/jpeg"],
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
const htmlHides = config.hidingRules.get("text/html");
|
|
321
|
+
expect(htmlHides).toBeInstanceOf(Set);
|
|
322
|
+
expect(htmlHides?.has("image/png")).toBe(true);
|
|
323
|
+
expect(htmlHides?.has("image/jpeg")).toBe(true);
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
});
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/* Copyright 2026 Marimo. All rights reserved. */
|
|
2
|
+
|
|
3
|
+
import type { MimeType } from "@/components/editor/Output";
|
|
4
|
+
import { once } from "./once";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Configuration for mime type precedence and filtering.
|
|
8
|
+
* Uses Map/Set for O(1) lookups at runtime.
|
|
9
|
+
*/
|
|
10
|
+
export interface MimeTypeConfig {
|
|
11
|
+
/**
|
|
12
|
+
* Pre-computed precedence map: mime type -> sort index.
|
|
13
|
+
* Lower index = higher priority. Types not in the map are placed at the end.
|
|
14
|
+
*/
|
|
15
|
+
precedence: ReadonlyMap<MimeType, number>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Hiding rules: trigger mime type -> set of mime types to hide.
|
|
19
|
+
* When the key mime type is present, all mime types in the value set are hidden.
|
|
20
|
+
*/
|
|
21
|
+
hidingRules: ReadonlyMap<MimeType, ReadonlySet<MimeType>>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Result of processing mime types through the filtering and sorting pipeline.
|
|
26
|
+
*/
|
|
27
|
+
export interface ProcessedMimeTypes<T> {
|
|
28
|
+
/** The filtered and sorted mime entries */
|
|
29
|
+
entries: Array<[MimeType, T]>;
|
|
30
|
+
/** Mime types that were hidden by rules */
|
|
31
|
+
hidden: MimeType[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Creates a compiled MimeTypeConfig from readable arrays.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* const config = createMimeConfig({
|
|
40
|
+
* precedence: ["text/html", "image/png", "text/plain"],
|
|
41
|
+
* hidingRules: {
|
|
42
|
+
* "text/html": ["image/png", "image/jpeg"],
|
|
43
|
+
* },
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function createMimeConfig(input: {
|
|
48
|
+
precedence: MimeType[];
|
|
49
|
+
hidingRules: Record<string, MimeType[]>;
|
|
50
|
+
}): MimeTypeConfig {
|
|
51
|
+
const precedence = new Map<MimeType, number>();
|
|
52
|
+
for (let i = 0; i < input.precedence.length; i++) {
|
|
53
|
+
precedence.set(input.precedence[i], i);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const hidingRules = new Map<MimeType, ReadonlySet<MimeType>>();
|
|
57
|
+
for (const [trigger, toHide] of Object.entries(input.hidingRules)) {
|
|
58
|
+
hidingRules.set(trigger as MimeType, new Set(toHide));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { precedence, hidingRules };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Default configuration for mime type handling.
|
|
66
|
+
* Lazily compiled on first access.
|
|
67
|
+
*
|
|
68
|
+
* Design rationale:
|
|
69
|
+
* - text/html typically contains rich rendered output and should take precedence
|
|
70
|
+
* - When text/html is present, image fallbacks (png, jpeg, etc.) are often redundant
|
|
71
|
+
* static renders and should be hidden to reduce UI clutter
|
|
72
|
+
* - text/markdown should NOT be hidden by text/html as they serve different purposes
|
|
73
|
+
* - Vega charts should remain visible as they provide interactivity
|
|
74
|
+
*/
|
|
75
|
+
export const getDefaultMimeConfig = once((): MimeTypeConfig => {
|
|
76
|
+
const IMAGE_FALLBACKS: MimeType[] = ["image/png", "image/jpeg", "image/gif"];
|
|
77
|
+
|
|
78
|
+
return createMimeConfig({
|
|
79
|
+
precedence: [
|
|
80
|
+
"text/html",
|
|
81
|
+
"application/vnd.vegalite.v6+json",
|
|
82
|
+
"application/vnd.vegalite.v5+json",
|
|
83
|
+
"application/vnd.vega.v6+json",
|
|
84
|
+
"application/vnd.vega.v5+json",
|
|
85
|
+
"image/svg+xml",
|
|
86
|
+
"image/png",
|
|
87
|
+
"image/jpeg",
|
|
88
|
+
"image/gif",
|
|
89
|
+
"text/markdown",
|
|
90
|
+
"text/latex",
|
|
91
|
+
"text/csv",
|
|
92
|
+
"application/json",
|
|
93
|
+
"text/plain",
|
|
94
|
+
"video/mp4",
|
|
95
|
+
"video/mpeg",
|
|
96
|
+
],
|
|
97
|
+
hidingRules: {
|
|
98
|
+
// When HTML is present, hide static image fallbacks
|
|
99
|
+
"text/html": [
|
|
100
|
+
...IMAGE_FALLBACKS,
|
|
101
|
+
"image/avif",
|
|
102
|
+
"image/bmp",
|
|
103
|
+
"image/tiff",
|
|
104
|
+
],
|
|
105
|
+
// When Vega charts are present, hide image fallbacks
|
|
106
|
+
"application/vnd.vegalite.v6+json": IMAGE_FALLBACKS,
|
|
107
|
+
"application/vnd.vegalite.v5+json": IMAGE_FALLBACKS,
|
|
108
|
+
"application/vnd.vega.v6+json": IMAGE_FALLBACKS,
|
|
109
|
+
"application/vnd.vega.v5+json": IMAGE_FALLBACKS,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Filters mime types based on hiding rules.
|
|
116
|
+
*/
|
|
117
|
+
export function applyHidingRules(
|
|
118
|
+
mimeTypes: ReadonlySet<MimeType>,
|
|
119
|
+
rules: ReadonlyMap<MimeType, ReadonlySet<MimeType>>,
|
|
120
|
+
): { visible: Set<MimeType>; hidden: Set<MimeType> } {
|
|
121
|
+
const hidden = new Set<MimeType>();
|
|
122
|
+
|
|
123
|
+
for (const mime of mimeTypes) {
|
|
124
|
+
const toHide = rules.get(mime);
|
|
125
|
+
if (toHide) {
|
|
126
|
+
for (const hideType of toHide) {
|
|
127
|
+
if (mimeTypes.has(hideType)) {
|
|
128
|
+
hidden.add(hideType);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const visible = new Set<MimeType>();
|
|
135
|
+
for (const mime of mimeTypes) {
|
|
136
|
+
if (!hidden.has(mime)) {
|
|
137
|
+
visible.add(mime);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { visible, hidden };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Sorts mime entries according to a precedence map.
|
|
146
|
+
* Mime types not in the map are placed at the end, preserving their original order.
|
|
147
|
+
*/
|
|
148
|
+
export function sortByPrecedence<T>(
|
|
149
|
+
entries: Array<[MimeType, T]>,
|
|
150
|
+
precedence: ReadonlyMap<MimeType, number>,
|
|
151
|
+
): Array<[MimeType, T]> {
|
|
152
|
+
const unknownPrecedence = precedence.size;
|
|
153
|
+
|
|
154
|
+
return [...entries].sort((a, b) => {
|
|
155
|
+
const indexA = precedence.get(a[0]) ?? unknownPrecedence;
|
|
156
|
+
const indexB = precedence.get(b[0]) ?? unknownPrecedence;
|
|
157
|
+
return indexA - indexB;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Main entry point: processes mime entries by applying hiding rules and sorting.
|
|
163
|
+
*/
|
|
164
|
+
export function processMimeBundle<T>(
|
|
165
|
+
entries: Array<[MimeType, T]>,
|
|
166
|
+
config: MimeTypeConfig = getDefaultMimeConfig(),
|
|
167
|
+
): ProcessedMimeTypes<T> {
|
|
168
|
+
if (entries.length === 0) {
|
|
169
|
+
return { entries: [], hidden: [] };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const mimeTypes = new Set(entries.map(([mime]) => mime));
|
|
173
|
+
const { visible, hidden } = applyHidingRules(mimeTypes, config.hidingRules);
|
|
174
|
+
const filteredEntries = entries.filter(([mime]) => visible.has(mime));
|
|
175
|
+
const sortedEntries = sortByPrecedence(filteredEntries, config.precedence);
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
entries: sortedEntries,
|
|
179
|
+
hidden: Array.from(hidden),
|
|
180
|
+
};
|
|
181
|
+
}
|