@viniaraujo68/plinth 0.3.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +197 -19
- package/dist/components/DateRangePicker.svelte +93 -57
- package/dist/components/DateRangePicker.svelte.d.ts +32 -15
- package/dist/components/ErrorDisplay.svelte +30 -16
- package/dist/components/ErrorDisplay.svelte.d.ts +4 -0
- package/dist/components/JsonView.svelte +43 -0
- package/dist/components/JsonView.svelte.d.ts +4 -0
- package/dist/components/JsonViewFrame.svelte +654 -0
- package/dist/components/JsonViewFrame.svelte.d.ts +31 -0
- package/dist/components/Paginator.svelte +34 -8
- package/dist/components/Paginator.svelte.d.ts +10 -0
- package/dist/components/RequestErrorDisplay.svelte +43 -0
- package/dist/components/RequestErrorDisplay.svelte.d.ts +11 -0
- package/dist/components/Tabs.svelte +204 -0
- package/dist/components/Tabs.svelte.d.ts +76 -0
- package/dist/components/VirtualJsonView.svelte +67 -0
- package/dist/components/VirtualJsonView.svelte.d.ts +4 -0
- package/dist/components/date-range.d.ts +82 -14
- package/dist/components/date-range.js +86 -5
- package/dist/components/index.d.ts +9 -2
- package/dist/components/index.js +15 -9
- package/dist/components/json-view.d.ts +70 -0
- package/dist/components/json-view.js +302 -0
- package/dist/components/request-error.d.ts +8 -0
- package/dist/components/request-error.js +55 -0
- package/dist/components/tabs.d.ts +5 -0
- package/dist/components/tabs.js +1 -0
- package/dist/http.d.ts +10 -5
- package/dist/http.js +34 -6
- package/dist/i18n/LocaleToggle.svelte +45 -0
- package/dist/i18n/LocaleToggle.svelte.d.ts +48 -0
- package/dist/i18n/i18n.svelte.d.ts +56 -0
- package/dist/i18n/i18n.svelte.js +94 -0
- package/dist/i18n/index.d.ts +3 -0
- package/dist/i18n/index.js +7 -0
- package/dist/i18n/translate.d.ts +30 -0
- package/dist/i18n/translate.js +38 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/redirect.d.ts +40 -0
- package/dist/redirect.js +59 -0
- package/dist/routing.svelte.d.ts +1 -0
- package/dist/routing.svelte.js +3 -0
- package/dist/shell/AppShell.svelte +265 -93
- package/dist/shell/AppShell.svelte.d.ts +3 -2
- package/dist/shell/NavigationProgress.svelte +85 -0
- package/dist/shell/NavigationProgress.svelte.d.ts +31 -0
- package/dist/shell/TopBar.svelte +191 -0
- package/dist/shell/TopBar.svelte.d.ts +41 -0
- package/dist/shell/UserAvatar.svelte +50 -0
- package/dist/shell/UserAvatar.svelte.d.ts +14 -0
- package/dist/shell/index.d.ts +2 -0
- package/dist/shell/index.js +7 -3
- package/dist/shell/initials.d.ts +6 -0
- package/dist/shell/initials.js +13 -0
- package/dist/table/DataTable.svelte +199 -9
- package/dist/table/DataTable.svelte.d.ts +28 -5
- package/dist/table/FilterBar.svelte +417 -0
- package/dist/table/FilterBar.svelte.d.ts +34 -0
- package/dist/table/FilterRuleEditor.svelte +290 -0
- package/dist/table/FilterRuleEditor.svelte.d.ts +33 -0
- package/dist/table/filter-labels.d.ts +23 -0
- package/dist/table/filter-labels.js +30 -0
- package/dist/table/filter.d.ts +27 -0
- package/dist/table/filter.js +279 -0
- package/dist/table/index.d.ts +4 -1
- package/dist/table/index.js +5 -2
- package/dist/table/types.d.ts +49 -0
- package/dist/toast/Toaster.svelte +7 -3
- package/dist/toast/index.d.ts +1 -1
- package/dist/toast/toast-manager.svelte.d.ts +29 -1
- package/dist/toast/toast-manager.svelte.js +61 -1
- package/dist/url-state/codec.d.ts +15 -0
- package/dist/url-state/codec.js +1 -0
- package/dist/url-state/index.d.ts +5 -0
- package/dist/url-state/index.js +4 -0
- package/dist/url-state/params-codec.d.ts +73 -0
- package/dist/url-state/params-codec.js +116 -0
- package/dist/url-state/search-params.d.ts +4 -0
- package/dist/url-state/search-params.js +28 -0
- package/dist/url-state/synced-state.svelte.d.ts +20 -0
- package/dist/url-state/synced-state.svelte.js +88 -0
- package/dist/url-state/url-store.svelte.d.ts +12 -0
- package/dist/url-state/url-store.svelte.js +56 -0
- package/dist/virtual/DataList.svelte +215 -0
- package/dist/virtual/DataList.svelte.d.ts +52 -0
- package/dist/virtual/VirtualList.svelte +150 -0
- package/dist/virtual/VirtualList.svelte.d.ts +42 -0
- package/dist/virtual/index.d.ts +7 -0
- package/dist/virtual/index.js +6 -0
- package/dist/virtual/keyed-cache.d.ts +7 -0
- package/dist/virtual/keyed-cache.js +20 -0
- package/dist/virtual/labels.d.ts +12 -0
- package/dist/virtual/labels.js +6 -0
- package/dist/virtual/paging.d.ts +5 -0
- package/dist/virtual/paging.js +11 -0
- package/dist/virtual/range.d.ts +9 -0
- package/dist/virtual/range.js +31 -0
- package/dist/virtual/size-tree.d.ts +10 -0
- package/dist/virtual/size-tree.js +56 -0
- package/dist/virtual/virtualizer.svelte.d.ts +38 -0
- package/dist/virtual/virtualizer.svelte.js +295 -0
- package/dist/virtual/windowed-data.svelte.d.ts +38 -0
- package/dist/virtual/windowed-data.svelte.js +139 -0
- package/dist/virtual/windowed-virtualizer.svelte.d.ts +9 -0
- package/dist/virtual/windowed-virtualizer.svelte.js +22 -0
- package/package.json +13 -1
|
@@ -14,9 +14,16 @@ export const DEFAULT_PRESETS = [
|
|
|
14
14
|
{ id: "7d", label: "7d", durationMs: 7 * DAY_MS },
|
|
15
15
|
{ id: "30d", label: "30d", durationMs: 30 * DAY_MS },
|
|
16
16
|
];
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
const pad = (value, length = 2) => String(value).padStart(length, "0");
|
|
18
|
+
/** A local calendar day as `YYYY-MM-DD`, the value an `<input type="date">` holds. */
|
|
19
|
+
export const toDay = (date) => `${pad(date.getFullYear(), 4)}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
20
|
+
/** Builds the window a preset names as of `now`, in the picker's `mode`. */
|
|
21
|
+
export const buildRange = (preset, now = Date.now(), mode = "datetime") => {
|
|
19
22
|
const end = now instanceof Date ? now.getTime() : now;
|
|
23
|
+
if (preset.range)
|
|
24
|
+
return preset.range(new Date(end));
|
|
25
|
+
if (mode === "date")
|
|
26
|
+
return { from: toDay(new Date(end - preset.durationMs)), to: toDay(new Date(end)) };
|
|
20
27
|
return { from: new Date(end - preset.durationMs).toISOString(), to: new Date(end).toISOString() };
|
|
21
28
|
};
|
|
22
29
|
/**
|
|
@@ -27,12 +34,87 @@ export const buildRange = (preset, now = Date.now()) => {
|
|
|
27
34
|
* range is never re-anchored" a property of the data rather than of the timer's wiring: even a
|
|
28
35
|
* tick that somehow fired while the user was in custom mode would have nothing to emit.
|
|
29
36
|
*/
|
|
30
|
-
export const anchorSelection = (selection, presets, now = Date.now()) => {
|
|
37
|
+
export const anchorSelection = (selection, presets, now = Date.now(), mode = "datetime") => {
|
|
31
38
|
if (selection === CUSTOM_PRESET_ID)
|
|
32
39
|
return null;
|
|
33
40
|
const preset = presets.find(({ id }) => id === selection);
|
|
34
|
-
return preset ? buildRange(preset, now) : null;
|
|
41
|
+
return preset ? buildRange(preset, now, mode) : null;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The selection a picker handed `value` (and no `preset`) should open on: the preset whose window
|
|
45
|
+
* right now is exactly that pair, or {@link CUSTOM_PRESET_ID}. A page that remounts its picker —
|
|
46
|
+
* one per tab, say — gets its "Last month" button back instead of a custom pair spelling it out.
|
|
47
|
+
*
|
|
48
|
+
* A duration preset in `"datetime"` mode only matches to the millisecond, so in practice a seeded
|
|
49
|
+
* instant pair still opens as custom, as it always did.
|
|
50
|
+
*/
|
|
51
|
+
export const selectionFor = (value, presets, now = Date.now(), mode = "datetime") => {
|
|
52
|
+
const match = presets.find((preset) => {
|
|
53
|
+
const range = buildRange(preset, now, mode);
|
|
54
|
+
return range.from === value.from && range.to === value.to;
|
|
55
|
+
});
|
|
56
|
+
return match?.id ?? CUSTOM_PRESET_ID;
|
|
35
57
|
};
|
|
58
|
+
/**
|
|
59
|
+
* Whether a pair of days is in order. An empty end leaves that side open, so it can never put the
|
|
60
|
+
* pair out of order.
|
|
61
|
+
*/
|
|
62
|
+
export const isValidDayRange = (from, to) => !from || !to || from <= to;
|
|
63
|
+
const DAY_PATTERN = /^([1-9]\d{3})-(\d{2})-(\d{2})$/;
|
|
64
|
+
/**
|
|
65
|
+
* Reads an `<input type="date">` value back: the day itself, `""` for an empty field — no bound on
|
|
66
|
+
* that side — or `null` for anything that is not yet a real day.
|
|
67
|
+
*
|
|
68
|
+
* A year below 1000 counts as not yet a day. Chrome reports a value on every digit of the year as
|
|
69
|
+
* it is typed — `0002-03-01`, `0020-03-01`, `0202-03-01` — and each of those would otherwise be
|
|
70
|
+
* emitted, and fetched, on the way to `2026`.
|
|
71
|
+
*/
|
|
72
|
+
export const parseDay = (value) => {
|
|
73
|
+
if (!value)
|
|
74
|
+
return "";
|
|
75
|
+
const match = DAY_PATTERN.exec(value);
|
|
76
|
+
if (!match)
|
|
77
|
+
return null;
|
|
78
|
+
const [, year, month, day] = match;
|
|
79
|
+
const date = new Date(Number(year), Number(month) - 1, Number(day));
|
|
80
|
+
return date.getMonth() === Number(month) - 1 && date.getDate() === Number(day) ? value : null;
|
|
81
|
+
};
|
|
82
|
+
/** `now` moved back `months` calendar months, kept on its day or the last one the month has. */
|
|
83
|
+
const monthsBefore = (now, months) => {
|
|
84
|
+
const month = now.getMonth() - months;
|
|
85
|
+
const lastDay = new Date(now.getFullYear(), month + 1, 0).getDate();
|
|
86
|
+
return new Date(now.getFullYear(), month, Math.min(now.getDate(), lastDay));
|
|
87
|
+
};
|
|
88
|
+
/** "This month", for `"date"` mode: from the 1st, open at the end. */
|
|
89
|
+
export const thisMonth = (label, id = "this-month") => ({
|
|
90
|
+
id,
|
|
91
|
+
label,
|
|
92
|
+
range: (now) => ({ from: toDay(new Date(now.getFullYear(), now.getMonth(), 1)), to: "" }),
|
|
93
|
+
});
|
|
94
|
+
/** "Last month", for `"date"` mode: its first day to its last. */
|
|
95
|
+
export const lastMonth = (label, id = "last-month") => ({
|
|
96
|
+
id,
|
|
97
|
+
label,
|
|
98
|
+
range: (now) => ({
|
|
99
|
+
from: toDay(new Date(now.getFullYear(), now.getMonth() - 1, 1)),
|
|
100
|
+
to: toDay(new Date(now.getFullYear(), now.getMonth(), 0)),
|
|
101
|
+
}),
|
|
102
|
+
});
|
|
103
|
+
/**
|
|
104
|
+
* The last `months` months, for `"date"` mode: from the same day that many months ago — or the
|
|
105
|
+
* last day that month has — open at the end.
|
|
106
|
+
*/
|
|
107
|
+
export const lastMonths = (months, label, id = `last-${months}-months`) => ({
|
|
108
|
+
id,
|
|
109
|
+
label,
|
|
110
|
+
range: (now) => ({ from: toDay(monthsBefore(now, months)), to: "" }),
|
|
111
|
+
});
|
|
112
|
+
/** "All time", for `"date"` mode: both ends open. */
|
|
113
|
+
export const allTime = (label, id = "all-time") => ({
|
|
114
|
+
id,
|
|
115
|
+
label,
|
|
116
|
+
range: () => ({ from: "", to: "" }),
|
|
117
|
+
});
|
|
36
118
|
/** Whether the pair is in order. An empty end is not a range, so it is not valid either. */
|
|
37
119
|
export const isValidRange = (from, to) => {
|
|
38
120
|
if (from === null || to === null)
|
|
@@ -41,7 +123,6 @@ export const isValidRange = (from, to) => {
|
|
|
41
123
|
const end = Date.parse(to);
|
|
42
124
|
return !Number.isNaN(start) && !Number.isNaN(end) && start <= end;
|
|
43
125
|
};
|
|
44
|
-
const pad = (value, length = 2) => String(value).padStart(length, "0");
|
|
45
126
|
const LOCAL_INPUT_PATTERN = /^(\d{4,6})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?$/;
|
|
46
127
|
/**
|
|
47
128
|
* Formats a UTC instant for the `value` of an `<input type="datetime-local">`, whose contents are
|
|
@@ -8,19 +8,26 @@ export { default as Dropdown } from "./Dropdown.svelte";
|
|
|
8
8
|
export { default as ErrorDisplay } from "./ErrorDisplay.svelte";
|
|
9
9
|
export { default as ErrorPage } from "./ErrorPage.svelte";
|
|
10
10
|
export { default as JsonInput } from "./JsonInput.svelte";
|
|
11
|
+
export { default as JsonView } from "./JsonView.svelte";
|
|
12
|
+
export { default as VirtualJsonView } from "./VirtualJsonView.svelte";
|
|
11
13
|
export { default as Listbox } from "./Listbox.svelte";
|
|
12
14
|
export { default as LoadingButton } from "./LoadingButton.svelte";
|
|
13
15
|
export { default as Logo } from "./Logo.svelte";
|
|
14
16
|
export { default as Modal } from "./Modal.svelte";
|
|
15
17
|
export { default as MultiSelect } from "./MultiSelect.svelte";
|
|
16
|
-
export { default as Paginator } from "./Paginator.svelte";
|
|
18
|
+
export { default as Paginator, DEFAULT_PAGINATOR_LABELS, type PaginatorLabels, } from "./Paginator.svelte";
|
|
17
19
|
export { default as RefreshButton } from "./RefreshButton.svelte";
|
|
20
|
+
export { default as RequestErrorDisplay } from "./RequestErrorDisplay.svelte";
|
|
18
21
|
export { default as SegmentedControl } from "./SegmentedControl.svelte";
|
|
19
22
|
export { default as Select } from "./Select.svelte";
|
|
20
23
|
export { default as Skeleton } from "./Skeleton.svelte";
|
|
24
|
+
export { default as Tabs } from "./Tabs.svelte";
|
|
21
25
|
export { default as Tooltip } from "./Tooltip.svelte";
|
|
22
26
|
export { PICK_CLIP_PATH, PICK_PATH } from "./pick.js";
|
|
23
|
-
export { buildRange, CUSTOM_PRESET_ID, DEFAULT_PRESETS, isoToLocalInput, localInputToIso, type DateRange, type DateRangePreset, } from "./date-range.js";
|
|
27
|
+
export { allTime, buildRange, CUSTOM_PRESET_ID, DEFAULT_PRESETS, isoToLocalInput, lastMonth, lastMonths, localInputToIso, thisMonth, toDay, type CalendarPreset, type DateRange, type DateRangeMode, type DateRangePreset, type DurationPreset, } from "./date-range.js";
|
|
24
28
|
export { matchesSelectQuery, normalizeForSearch, type SelectOption } from "./select.js";
|
|
25
29
|
export { formatJsonText, parseJsonText, type JsonTextOptions, type JsonTextResult, } from "./json-text.js";
|
|
30
|
+
export { DEFAULT_JSON_VIEW_LABELS, type JsonViewLabels } from "./json-view.js";
|
|
31
|
+
export { DEFAULT_REQUEST_ERROR_COPY, type RequestErrorCopy, type RequestErrorLabels, } from "./request-error.js";
|
|
26
32
|
export type { SegmentedOption } from "./segmented.js";
|
|
33
|
+
export type { TabItem } from "./tabs.js";
|
package/dist/components/index.js
CHANGED
|
@@ -8,31 +8,37 @@ export { default as Dropdown } from "./Dropdown.svelte";
|
|
|
8
8
|
export { default as ErrorDisplay } from "./ErrorDisplay.svelte";
|
|
9
9
|
export { default as ErrorPage } from "./ErrorPage.svelte";
|
|
10
10
|
export { default as JsonInput } from "./JsonInput.svelte";
|
|
11
|
+
export { default as JsonView } from "./JsonView.svelte";
|
|
12
|
+
export { default as VirtualJsonView } from "./VirtualJsonView.svelte";
|
|
11
13
|
export { default as Listbox } from "./Listbox.svelte";
|
|
12
14
|
export { default as LoadingButton } from "./LoadingButton.svelte";
|
|
13
15
|
export { default as Logo } from "./Logo.svelte";
|
|
14
16
|
export { default as Modal } from "./Modal.svelte";
|
|
15
17
|
export { default as MultiSelect } from "./MultiSelect.svelte";
|
|
16
|
-
export { default as Paginator } from "./Paginator.svelte";
|
|
18
|
+
export { default as Paginator, DEFAULT_PAGINATOR_LABELS, } from "./Paginator.svelte";
|
|
17
19
|
export { default as RefreshButton } from "./RefreshButton.svelte";
|
|
20
|
+
export { default as RequestErrorDisplay } from "./RequestErrorDisplay.svelte";
|
|
18
21
|
export { default as SegmentedControl } from "./SegmentedControl.svelte";
|
|
19
22
|
export { default as Select } from "./Select.svelte";
|
|
20
23
|
export { default as Skeleton } from "./Skeleton.svelte";
|
|
24
|
+
export { default as Tabs } from "./Tabs.svelte";
|
|
21
25
|
export { default as Tooltip } from "./Tooltip.svelte";
|
|
22
|
-
// The mark's outline as data. `Logo`
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
+
// The mark's outline as data. `Logo` already reads it from here, and an app that wants the same
|
|
27
|
+
// silhouette -- a placeholder cut to the pick, a mark of its own -- has no other way to get it than
|
|
28
|
+
// copying the curve out of the library, which is how two versions of one shape start existing.
|
|
29
|
+
// Not for avatars: a profile picture cut to anything but a disc reads as a badge.
|
|
26
30
|
export { PICK_CLIP_PATH, PICK_PATH } from "./pick.js";
|
|
27
|
-
// The half of `date-range.js` a consumer of the picker has a use for: the
|
|
28
|
-
// preset
|
|
29
|
-
// pairing their own field with a `DateRange` has to repeat otherwise. The rest -- the selection
|
|
31
|
+
// The half of `date-range.js` a consumer of the picker has a use for: the prop types, the preset
|
|
32
|
+
// list, the calendar preset builders and the reserved id they are written against, and the
|
|
33
|
+
// conversions anyone pairing their own field with a `DateRange` has to repeat otherwise. The rest -- the selection
|
|
30
34
|
// anchor, the pair validator and the re-anchor clock -- is how the picker drives itself, and the
|
|
31
35
|
// picker never emits a pair that would fail the validator.
|
|
32
|
-
export { buildRange, CUSTOM_PRESET_ID, DEFAULT_PRESETS, isoToLocalInput, localInputToIso, } from "./date-range.js";
|
|
36
|
+
export { allTime, buildRange, CUSTOM_PRESET_ID, DEFAULT_PRESETS, isoToLocalInput, lastMonth, lastMonths, localInputToIso, thisMonth, toDay, } from "./date-range.js";
|
|
33
37
|
// The half of `select.ts` a consumer has a use for: the option type every caller of either
|
|
34
38
|
// control has to name, and the default matcher, so widening the search -- matching the value too,
|
|
35
39
|
// a synonym list -- can compose with the accent folding instead of reimplementing it. The
|
|
36
40
|
// highlight walk stays private: it is how the two components drive themselves.
|
|
37
41
|
export { matchesSelectQuery, normalizeForSearch } from "./select.js";
|
|
38
42
|
export { formatJsonText, parseJsonText, } from "./json-text.js";
|
|
43
|
+
export { DEFAULT_JSON_VIEW_LABELS } from "./json-view.js";
|
|
44
|
+
export { DEFAULT_REQUEST_ERROR_COPY, } from "./request-error.js";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export type JsonLineKind = "open" | "close" | "value";
|
|
2
|
+
export type JsonTokenType = "string" | "number" | "boolean" | "null" | "circular";
|
|
3
|
+
export interface JsonLine {
|
|
4
|
+
kind: JsonLineKind;
|
|
5
|
+
pointer: string;
|
|
6
|
+
id: string;
|
|
7
|
+
depth: number;
|
|
8
|
+
parent: number;
|
|
9
|
+
end: number;
|
|
10
|
+
name: string | null;
|
|
11
|
+
text: string;
|
|
12
|
+
type: JsonTokenType | null;
|
|
13
|
+
size: number;
|
|
14
|
+
comma: boolean;
|
|
15
|
+
source: unknown;
|
|
16
|
+
}
|
|
17
|
+
export interface JsonFlattenOptions {
|
|
18
|
+
circular?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface JsonMatch {
|
|
21
|
+
line: number;
|
|
22
|
+
field: "name" | "value";
|
|
23
|
+
start: number;
|
|
24
|
+
length: number;
|
|
25
|
+
}
|
|
26
|
+
export interface JsonMatchSegment {
|
|
27
|
+
text: string;
|
|
28
|
+
start: number;
|
|
29
|
+
match: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface JsonPoint {
|
|
32
|
+
line: number;
|
|
33
|
+
column: number;
|
|
34
|
+
}
|
|
35
|
+
export interface JsonViewLabels {
|
|
36
|
+
region: string;
|
|
37
|
+
search: string;
|
|
38
|
+
searchField: string;
|
|
39
|
+
previousMatch: string;
|
|
40
|
+
nextMatch: string;
|
|
41
|
+
closeSearch: string;
|
|
42
|
+
matchCount: (current: number, total: number) => string;
|
|
43
|
+
noMatches: string;
|
|
44
|
+
expandAll: string;
|
|
45
|
+
collapseAll: string;
|
|
46
|
+
copy: string;
|
|
47
|
+
copied: string;
|
|
48
|
+
expand: string;
|
|
49
|
+
collapse: string;
|
|
50
|
+
items: (count: number) => string;
|
|
51
|
+
circular: string;
|
|
52
|
+
empty: string;
|
|
53
|
+
}
|
|
54
|
+
export declare const DEFAULT_JSON_VIEW_LABELS: JsonViewLabels;
|
|
55
|
+
export declare const JSON_VIEW_GUTTER_CH = 2;
|
|
56
|
+
export declare const JSON_VIEW_INDENT_CH = 2;
|
|
57
|
+
export declare const flattenJson: (value: unknown, options?: JsonFlattenOptions) => JsonLine[];
|
|
58
|
+
export declare const isFoldable: (line: JsonLine) => boolean;
|
|
59
|
+
export declare const isFolded: (line: JsonLine, collapsed: ReadonlySet<string>) => boolean;
|
|
60
|
+
export declare const visibleLines: (lines: readonly JsonLine[], collapsed: ReadonlySet<string>) => number[];
|
|
61
|
+
export declare const ancestorLines: (lines: readonly JsonLine[], index: number) => number[];
|
|
62
|
+
export declare const searchJson: (lines: readonly JsonLine[], query: string) => JsonMatch[];
|
|
63
|
+
export declare const matchSegments: (text: string, query: string) => JsonMatchSegment[];
|
|
64
|
+
export declare const subtreeHasMatch: (matches: readonly JsonMatch[], open: number, end: number) => boolean;
|
|
65
|
+
export declare const lineWidthCh: (line: JsonLine) => number;
|
|
66
|
+
export declare const maxLineWidthCh: (lines: readonly JsonLine[]) => number;
|
|
67
|
+
export declare const matchColumnCh: (line: JsonLine, match: JsonMatch) => number;
|
|
68
|
+
export declare const lineText: (line: JsonLine) => string;
|
|
69
|
+
export declare const copyJson: (lines: readonly JsonLine[], collapsed: ReadonlySet<string>, from: JsonPoint, to: JsonPoint) => string;
|
|
70
|
+
export declare const copyAllJson: (lines: readonly JsonLine[]) => string;
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
export const DEFAULT_JSON_VIEW_LABELS = {
|
|
2
|
+
region: "JSON viewer",
|
|
3
|
+
search: "Search",
|
|
4
|
+
searchField: "Search keys and values",
|
|
5
|
+
previousMatch: "Previous match",
|
|
6
|
+
nextMatch: "Next match",
|
|
7
|
+
closeSearch: "Close search",
|
|
8
|
+
matchCount: (current, total) => `${current} of ${total}`,
|
|
9
|
+
noMatches: "No matches",
|
|
10
|
+
expandAll: "Expand all",
|
|
11
|
+
collapseAll: "Collapse all",
|
|
12
|
+
copy: "Copy JSON",
|
|
13
|
+
copied: "Copied",
|
|
14
|
+
expand: "Expand",
|
|
15
|
+
collapse: "Collapse",
|
|
16
|
+
items: (count) => (count === 1 ? "1 item" : `${count} items`),
|
|
17
|
+
circular: "[Circular]",
|
|
18
|
+
empty: "Nothing to show",
|
|
19
|
+
};
|
|
20
|
+
export const JSON_VIEW_GUTTER_CH = 2;
|
|
21
|
+
export const JSON_VIEW_INDENT_CH = 2;
|
|
22
|
+
const SKIPPED = Symbol("skipped");
|
|
23
|
+
const escapePointerSegment = (segment) => segment.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
24
|
+
const unbox = (value) => {
|
|
25
|
+
if (value instanceof Number)
|
|
26
|
+
return Number(value);
|
|
27
|
+
if (value instanceof String)
|
|
28
|
+
return String(value);
|
|
29
|
+
if (value instanceof Boolean || value instanceof BigInt)
|
|
30
|
+
return value.valueOf();
|
|
31
|
+
return value;
|
|
32
|
+
};
|
|
33
|
+
const resolveForJson = (raw, key) => {
|
|
34
|
+
let value = raw;
|
|
35
|
+
if ((typeof value === "object" && value !== null) || typeof value === "bigint") {
|
|
36
|
+
const toJSON = value.toJSON;
|
|
37
|
+
if (typeof toJSON === "function")
|
|
38
|
+
value = toJSON.call(value, key);
|
|
39
|
+
}
|
|
40
|
+
value = unbox(value);
|
|
41
|
+
if (value === undefined || typeof value === "function" || typeof value === "symbol")
|
|
42
|
+
return SKIPPED;
|
|
43
|
+
return value;
|
|
44
|
+
};
|
|
45
|
+
const isContainer = (value) => typeof value === "object" && value !== null;
|
|
46
|
+
const closingBracket = (open) => (open === "[" ? "]" : "}");
|
|
47
|
+
const primitiveToken = (value) => {
|
|
48
|
+
if (value === null)
|
|
49
|
+
return { text: "null", type: "null" };
|
|
50
|
+
if (typeof value === "string")
|
|
51
|
+
return { text: JSON.stringify(value), type: "string" };
|
|
52
|
+
if (typeof value === "boolean")
|
|
53
|
+
return { text: String(value), type: "boolean" };
|
|
54
|
+
if (typeof value === "bigint")
|
|
55
|
+
return { text: value.toString(), type: "number" };
|
|
56
|
+
if (typeof value === "number")
|
|
57
|
+
return Number.isFinite(value)
|
|
58
|
+
? { text: JSON.stringify(value), type: "number" }
|
|
59
|
+
: { text: "null", type: "null" };
|
|
60
|
+
return { text: "null", type: "null" };
|
|
61
|
+
};
|
|
62
|
+
export const flattenJson = (value, options = {}) => {
|
|
63
|
+
const circular = options.circular ?? DEFAULT_JSON_VIEW_LABELS.circular;
|
|
64
|
+
const lines = [];
|
|
65
|
+
const ancestors = new Set();
|
|
66
|
+
const childrenOf = (container) => {
|
|
67
|
+
if (Array.isArray(container)) {
|
|
68
|
+
const items = container;
|
|
69
|
+
return Array.from({ length: items.length }, (_, index) => {
|
|
70
|
+
const segment = String(index);
|
|
71
|
+
const resolved = resolveForJson(items[index], segment);
|
|
72
|
+
return { segment, raw: items[index], value: resolved === SKIPPED ? null : resolved };
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const record = container;
|
|
76
|
+
return Object.keys(record)
|
|
77
|
+
.map((segment) => ({
|
|
78
|
+
segment,
|
|
79
|
+
raw: record[segment],
|
|
80
|
+
value: resolveForJson(record[segment], segment),
|
|
81
|
+
}))
|
|
82
|
+
.filter((child) => child.value !== SKIPPED);
|
|
83
|
+
};
|
|
84
|
+
const visit = (raw, node, pointer, name, depth, parent, comma) => {
|
|
85
|
+
const index = lines.length;
|
|
86
|
+
if (!isContainer(node)) {
|
|
87
|
+
const { text, type } = primitiveToken(node);
|
|
88
|
+
lines.push({
|
|
89
|
+
kind: "value",
|
|
90
|
+
pointer,
|
|
91
|
+
id: pointer,
|
|
92
|
+
depth,
|
|
93
|
+
parent,
|
|
94
|
+
end: index + 1,
|
|
95
|
+
name,
|
|
96
|
+
text,
|
|
97
|
+
type,
|
|
98
|
+
size: 0,
|
|
99
|
+
comma,
|
|
100
|
+
source: raw,
|
|
101
|
+
});
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (ancestors.has(node)) {
|
|
105
|
+
lines.push({
|
|
106
|
+
kind: "value",
|
|
107
|
+
pointer,
|
|
108
|
+
id: pointer,
|
|
109
|
+
depth,
|
|
110
|
+
parent,
|
|
111
|
+
end: index + 1,
|
|
112
|
+
name,
|
|
113
|
+
text: circular,
|
|
114
|
+
type: "circular",
|
|
115
|
+
size: 0,
|
|
116
|
+
comma,
|
|
117
|
+
source: raw,
|
|
118
|
+
});
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const isArray = Array.isArray(node);
|
|
122
|
+
const bracket = isArray ? "[" : "{";
|
|
123
|
+
const children = childrenOf(node);
|
|
124
|
+
const open = {
|
|
125
|
+
kind: "open",
|
|
126
|
+
pointer,
|
|
127
|
+
id: pointer,
|
|
128
|
+
depth,
|
|
129
|
+
parent,
|
|
130
|
+
end: index + 1,
|
|
131
|
+
name,
|
|
132
|
+
text: bracket,
|
|
133
|
+
type: null,
|
|
134
|
+
size: children.length,
|
|
135
|
+
comma,
|
|
136
|
+
source: raw,
|
|
137
|
+
};
|
|
138
|
+
lines.push(open);
|
|
139
|
+
if (children.length === 0)
|
|
140
|
+
return;
|
|
141
|
+
ancestors.add(node);
|
|
142
|
+
children.forEach((child, position) => {
|
|
143
|
+
visit(child.raw, child.value, `${pointer}/${escapePointerSegment(child.segment)}`, isArray ? null : JSON.stringify(child.segment), depth + 1, index, position < children.length - 1);
|
|
144
|
+
});
|
|
145
|
+
ancestors.delete(node);
|
|
146
|
+
lines.push({
|
|
147
|
+
kind: "close",
|
|
148
|
+
pointer,
|
|
149
|
+
id: `~${pointer}`,
|
|
150
|
+
depth,
|
|
151
|
+
parent: index,
|
|
152
|
+
end: lines.length + 1,
|
|
153
|
+
name: null,
|
|
154
|
+
text: closingBracket(bracket),
|
|
155
|
+
type: null,
|
|
156
|
+
size: 0,
|
|
157
|
+
comma,
|
|
158
|
+
source: raw,
|
|
159
|
+
});
|
|
160
|
+
open.end = lines.length;
|
|
161
|
+
};
|
|
162
|
+
const root = resolveForJson(value, "");
|
|
163
|
+
if (root !== SKIPPED)
|
|
164
|
+
visit(value, root, "", null, 0, -1, false);
|
|
165
|
+
return lines;
|
|
166
|
+
};
|
|
167
|
+
export const isFoldable = (line) => line.kind === "open" && line.size > 0;
|
|
168
|
+
export const isFolded = (line, collapsed) => isFoldable(line) && collapsed.has(line.pointer);
|
|
169
|
+
export const visibleLines = (lines, collapsed) => {
|
|
170
|
+
const visible = [];
|
|
171
|
+
let index = 0;
|
|
172
|
+
while (index < lines.length) {
|
|
173
|
+
visible.push(index);
|
|
174
|
+
const line = lines[index];
|
|
175
|
+
index = isFolded(line, collapsed) ? line.end : index + 1;
|
|
176
|
+
}
|
|
177
|
+
return visible;
|
|
178
|
+
};
|
|
179
|
+
export const ancestorLines = (lines, index) => {
|
|
180
|
+
const chain = [];
|
|
181
|
+
for (let parent = lines[index].parent; parent !== -1; parent = lines[parent].parent)
|
|
182
|
+
chain.push(parent);
|
|
183
|
+
return chain;
|
|
184
|
+
};
|
|
185
|
+
const foldCase = (text) => {
|
|
186
|
+
const lower = text.toLowerCase();
|
|
187
|
+
if (lower.length === text.length)
|
|
188
|
+
return lower;
|
|
189
|
+
let folded = "";
|
|
190
|
+
for (const character of text) {
|
|
191
|
+
const candidate = character.toLowerCase();
|
|
192
|
+
folded += candidate.length === character.length ? candidate : character;
|
|
193
|
+
}
|
|
194
|
+
return folded;
|
|
195
|
+
};
|
|
196
|
+
const occurrences = (text, needle) => {
|
|
197
|
+
const haystack = foldCase(text);
|
|
198
|
+
const starts = [];
|
|
199
|
+
for (let start = haystack.indexOf(needle); start !== -1; start = haystack.indexOf(needle, start + needle.length))
|
|
200
|
+
starts.push(start);
|
|
201
|
+
return starts;
|
|
202
|
+
};
|
|
203
|
+
export const searchJson = (lines, query) => {
|
|
204
|
+
if (query === "")
|
|
205
|
+
return [];
|
|
206
|
+
const needle = foldCase(query);
|
|
207
|
+
const matches = [];
|
|
208
|
+
lines.forEach((line, index) => {
|
|
209
|
+
if (line.name !== null)
|
|
210
|
+
for (const start of occurrences(line.name, needle))
|
|
211
|
+
matches.push({ line: index, field: "name", start, length: query.length });
|
|
212
|
+
if (line.kind === "value")
|
|
213
|
+
for (const start of occurrences(line.text, needle))
|
|
214
|
+
matches.push({ line: index, field: "value", start, length: query.length });
|
|
215
|
+
});
|
|
216
|
+
return matches;
|
|
217
|
+
};
|
|
218
|
+
export const matchSegments = (text, query) => {
|
|
219
|
+
if (query === "")
|
|
220
|
+
return [{ text, start: 0, match: false }];
|
|
221
|
+
const segments = [];
|
|
222
|
+
let cursor = 0;
|
|
223
|
+
for (const start of occurrences(text, foldCase(query))) {
|
|
224
|
+
if (start > cursor)
|
|
225
|
+
segments.push({ text: text.slice(cursor, start), start: cursor, match: false });
|
|
226
|
+
segments.push({ text: text.slice(start, start + query.length), start, match: true });
|
|
227
|
+
cursor = start + query.length;
|
|
228
|
+
}
|
|
229
|
+
if (cursor < text.length || segments.length === 0)
|
|
230
|
+
segments.push({ text: text.slice(cursor), start: cursor, match: false });
|
|
231
|
+
return segments;
|
|
232
|
+
};
|
|
233
|
+
export const subtreeHasMatch = (matches, open, end) => {
|
|
234
|
+
let low = 0;
|
|
235
|
+
let high = matches.length;
|
|
236
|
+
while (low < high) {
|
|
237
|
+
const middle = (low + high) >> 1;
|
|
238
|
+
if (matches[middle].line <= open)
|
|
239
|
+
low = middle + 1;
|
|
240
|
+
else
|
|
241
|
+
high = middle;
|
|
242
|
+
}
|
|
243
|
+
return low < matches.length && matches[low].line < end;
|
|
244
|
+
};
|
|
245
|
+
const nameWidth = (line) => (line.name === null ? 0 : line.name.length + 2);
|
|
246
|
+
const leadWidth = (line) => JSON_VIEW_GUTTER_CH + line.depth * JSON_VIEW_INDENT_CH + nameWidth(line);
|
|
247
|
+
export const lineWidthCh = (line) => {
|
|
248
|
+
const comma = line.comma ? 1 : 0;
|
|
249
|
+
if (line.kind === "value" || line.kind === "close")
|
|
250
|
+
return leadWidth(line) + line.text.length + comma;
|
|
251
|
+
if (line.size === 0)
|
|
252
|
+
return leadWidth(line) + 2 + comma;
|
|
253
|
+
return leadWidth(line) + 3 + comma + 1 + String(line.size).length;
|
|
254
|
+
};
|
|
255
|
+
export const maxLineWidthCh = (lines) => {
|
|
256
|
+
let widest = 0;
|
|
257
|
+
for (const line of lines)
|
|
258
|
+
widest = Math.max(widest, lineWidthCh(line));
|
|
259
|
+
return widest;
|
|
260
|
+
};
|
|
261
|
+
export const matchColumnCh = (line, match) => JSON_VIEW_GUTTER_CH +
|
|
262
|
+
line.depth * JSON_VIEW_INDENT_CH +
|
|
263
|
+
(match.field === "name" ? match.start : nameWidth(line) + match.start);
|
|
264
|
+
export const lineText = (line) => {
|
|
265
|
+
const lead = line.name === null ? "" : `${line.name}: `;
|
|
266
|
+
const comma = line.comma ? "," : "";
|
|
267
|
+
if (line.kind === "value")
|
|
268
|
+
return lead + line.text + comma;
|
|
269
|
+
if (line.kind === "close")
|
|
270
|
+
return line.text + comma;
|
|
271
|
+
if (line.size === 0)
|
|
272
|
+
return lead + line.text + closingBracket(line.text) + comma;
|
|
273
|
+
return lead + line.text;
|
|
274
|
+
};
|
|
275
|
+
const NOTHING_COLLAPSED = new Set();
|
|
276
|
+
const comparePoints = (a, b) => a.line === b.line ? a.column - b.column : a.line - b.line;
|
|
277
|
+
export const copyJson = (lines, collapsed, from, to) => {
|
|
278
|
+
if (lines.length === 0)
|
|
279
|
+
return "";
|
|
280
|
+
const clamp = (point) => ({
|
|
281
|
+
line: Math.min(Math.max(point.line, 0), lines.length - 1),
|
|
282
|
+
column: Math.max(point.column, 0),
|
|
283
|
+
});
|
|
284
|
+
let [start, end] = comparePoints(from, to) <= 0 ? [from, to] : [to, from];
|
|
285
|
+
start = clamp(start);
|
|
286
|
+
end = clamp(end);
|
|
287
|
+
const startLine = lines[start.line];
|
|
288
|
+
if (isFolded(startLine, collapsed))
|
|
289
|
+
start = { line: start.line, column: Math.min(start.column, lineText(startLine).length) };
|
|
290
|
+
const endLine = lines[end.line];
|
|
291
|
+
if (isFolded(endLine, collapsed) && end.column > lineText(endLine).length)
|
|
292
|
+
end = { line: endLine.end - 1, column: Number.POSITIVE_INFINITY };
|
|
293
|
+
if (start.line === end.line)
|
|
294
|
+
return lineText(lines[start.line]).slice(start.column, end.column);
|
|
295
|
+
const parts = [lineText(startLine).slice(start.column)];
|
|
296
|
+
for (let index = start.line + 1; index < end.line; index += 1)
|
|
297
|
+
parts.push(" ".repeat(lines[index].depth * 2) + lineText(lines[index]));
|
|
298
|
+
const last = lines[end.line];
|
|
299
|
+
parts.push(end.column === 0 ? "" : " ".repeat(last.depth * 2) + lineText(last).slice(0, end.column));
|
|
300
|
+
return parts.join("\n");
|
|
301
|
+
};
|
|
302
|
+
export const copyAllJson = (lines) => copyJson(lines, NOTHING_COLLAPSED, { line: 0, column: 0 }, { line: lines.length - 1, column: Number.POSITIVE_INFINITY });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RequestErrorKind } from "../http.js";
|
|
2
|
+
export interface RequestErrorCopy {
|
|
3
|
+
title: string;
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
export type RequestErrorLabels = Partial<Record<RequestErrorKind, Partial<RequestErrorCopy>>>;
|
|
7
|
+
export declare const DEFAULT_REQUEST_ERROR_COPY: Record<RequestErrorKind, RequestErrorCopy>;
|
|
8
|
+
export declare const REQUEST_ERROR_ICONS: Record<RequestErrorKind, string[]>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export const DEFAULT_REQUEST_ERROR_COPY = {
|
|
2
|
+
offline: {
|
|
3
|
+
title: "Connection problem",
|
|
4
|
+
message: "The request never reached the server. Check your connection and try again.",
|
|
5
|
+
},
|
|
6
|
+
unauthorized: {
|
|
7
|
+
title: "Sign-in required",
|
|
8
|
+
message: "Your session may have expired. Sign in again to continue.",
|
|
9
|
+
},
|
|
10
|
+
forbidden: {
|
|
11
|
+
title: "Access denied",
|
|
12
|
+
message: "You do not have permission to see this.",
|
|
13
|
+
},
|
|
14
|
+
"not-found": {
|
|
15
|
+
title: "Not found",
|
|
16
|
+
message: "What you asked for does not exist, or has moved.",
|
|
17
|
+
},
|
|
18
|
+
server: {
|
|
19
|
+
title: "Server error",
|
|
20
|
+
message: "The server ran into a problem. Try again in a moment.",
|
|
21
|
+
},
|
|
22
|
+
unknown: {
|
|
23
|
+
title: "Something went wrong",
|
|
24
|
+
message: "The application ran into an unexpected problem.",
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
export const REQUEST_ERROR_ICONS = {
|
|
28
|
+
offline: [
|
|
29
|
+
"M12 20h.01",
|
|
30
|
+
"M8.5 16.4a5 5 0 0 1 7 0",
|
|
31
|
+
"M5 12.9a10 10 0 0 1 5.2-2.7",
|
|
32
|
+
"M16.9 11.3a10 10 0 0 1 2.2 1.6",
|
|
33
|
+
"M2 8.8a15 15 0 0 1 4.2-2.6",
|
|
34
|
+
"M10.7 5a15 15 0 0 1 11.3 3.8",
|
|
35
|
+
"m2 2 20 20",
|
|
36
|
+
],
|
|
37
|
+
unauthorized: [
|
|
38
|
+
"M5 11h14a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2",
|
|
39
|
+
"M7 11V7a5 5 0 0 1 10 0v4",
|
|
40
|
+
],
|
|
41
|
+
forbidden: ["M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20", "m4.9 4.9 14.2 14.2"],
|
|
42
|
+
"not-found": ["M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16", "m21 21-4.3-4.3", "M8.5 11h5"],
|
|
43
|
+
server: [
|
|
44
|
+
"M6 10H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-2",
|
|
45
|
+
"M6 14H4a2 2 0 0 0-2 2v4a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-4a2 2 0 0 0-2-2h-2",
|
|
46
|
+
"M6 6h.01",
|
|
47
|
+
"M6 18h.01",
|
|
48
|
+
"m13 6-4 6h6l-4 6",
|
|
49
|
+
],
|
|
50
|
+
unknown: [
|
|
51
|
+
"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",
|
|
52
|
+
"M12 9v4",
|
|
53
|
+
"M12 17h.01",
|
|
54
|
+
],
|
|
55
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/http.d.ts
CHANGED
|
@@ -32,12 +32,15 @@ export interface HttpClientOptions {
|
|
|
32
32
|
auth?: AuthTokenSource;
|
|
33
33
|
parseError?: ErrorMessageParser;
|
|
34
34
|
}
|
|
35
|
+
export interface RequestOptions {
|
|
36
|
+
signal?: AbortSignal;
|
|
37
|
+
}
|
|
35
38
|
export interface HttpClient {
|
|
36
|
-
get: <T = unknown>(path: string) => Promise<T>;
|
|
37
|
-
post: <T = unknown>(path: string, body?: unknown) => Promise<T>;
|
|
38
|
-
put: <T = unknown>(path: string, body?: unknown) => Promise<T>;
|
|
39
|
-
patch: <T = unknown>(path: string, body?: unknown) => Promise<T>;
|
|
40
|
-
del: <T = unknown>(path: string) => Promise<T>;
|
|
39
|
+
get: <T = unknown>(path: string, options?: RequestOptions) => Promise<T>;
|
|
40
|
+
post: <T = unknown>(path: string, body?: unknown, options?: RequestOptions) => Promise<T>;
|
|
41
|
+
put: <T = unknown>(path: string, body?: unknown, options?: RequestOptions) => Promise<T>;
|
|
42
|
+
patch: <T = unknown>(path: string, body?: unknown, options?: RequestOptions) => Promise<T>;
|
|
43
|
+
del: <T = unknown>(path: string, options?: RequestOptions) => Promise<T>;
|
|
41
44
|
}
|
|
42
45
|
/**
|
|
43
46
|
* Build a client bound to one API.
|
|
@@ -66,3 +69,5 @@ export declare const errorMessage: (error: unknown) => string;
|
|
|
66
69
|
* caller special-case 403 or 404 without an `instanceof` dance.
|
|
67
70
|
*/
|
|
68
71
|
export declare const errorStatus: (error: unknown) => number;
|
|
72
|
+
export type RequestErrorKind = "offline" | "unauthorized" | "forbidden" | "not-found" | "server" | "unknown";
|
|
73
|
+
export declare const classifyError: (error: unknown, online?: boolean | undefined) => RequestErrorKind;
|