@revenexx/studio-shared 0.2.0 → 0.3.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/module.d.mts +6 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +32 -1
- package/dist/runtime/components/DataTable/ColumnsMenu.vue +3 -2
- package/dist/runtime/components/DataTable/DataTable.d.vue.ts +32 -4
- package/dist/runtime/components/DataTable/DataTable.vue +91 -34
- package/dist/runtime/components/DataTable/DataTable.vue.d.ts +32 -4
- package/dist/runtime/components/DataTable/FilterMenu.d.vue.ts +12 -3
- package/dist/runtime/components/DataTable/FilterMenu.vue +20 -10
- package/dist/runtime/components/DataTable/FilterMenu.vue.d.ts +12 -3
- package/dist/runtime/components/DataTable/SavedViewsMenu.d.vue.ts +6 -0
- package/dist/runtime/components/DataTable/SavedViewsMenu.vue +189 -0
- package/dist/runtime/components/DataTable/SavedViewsMenu.vue.d.ts +6 -0
- package/dist/runtime/components/DataTable/SortMenu.d.vue.ts +13 -0
- package/dist/runtime/components/DataTable/SortMenu.vue +47 -0
- package/dist/runtime/components/DataTable/SortMenu.vue.d.ts +13 -0
- package/dist/runtime/components/DataTable/ViewModeSwitch.d.vue.ts +20 -0
- package/dist/runtime/components/DataTable/ViewModeSwitch.vue +26 -0
- package/dist/runtime/components/DataTable/ViewModeSwitch.vue.d.ts +20 -0
- package/dist/runtime/components/DataTable/types.d.ts +34 -0
- package/dist/runtime/components/Media/MediaFolderTree.d.vue.ts +17 -0
- package/dist/runtime/components/Media/MediaFolderTree.vue +65 -0
- package/dist/runtime/components/Media/MediaFolderTree.vue.d.ts +17 -0
- package/dist/runtime/components/Media/MediaPickerDialog.d.vue.ts +18 -0
- package/dist/runtime/components/Media/MediaPickerDialog.vue +163 -0
- package/dist/runtime/components/Media/MediaPickerDialog.vue.d.ts +18 -0
- package/dist/runtime/components/Media/MediaUploader.d.vue.ts +29 -0
- package/dist/runtime/components/Media/MediaUploader.vue +161 -0
- package/dist/runtime/components/Media/MediaUploader.vue.d.ts +29 -0
- package/dist/runtime/components/Shared/BuildLogsFlyout.vue +14 -13
- package/dist/runtime/components/Shared/GitDeploymentSettings.vue +11 -10
- package/dist/runtime/components/Shared/ListSurface.d.vue.ts +38 -0
- package/dist/runtime/components/Shared/ListSurface.vue +27 -0
- package/dist/runtime/components/Shared/ListSurface.vue.d.ts +38 -0
- package/dist/runtime/components/Shared/RecordLayout.d.vue.ts +52 -0
- package/dist/runtime/components/Shared/RecordLayout.vue +63 -0
- package/dist/runtime/components/Shared/RecordLayout.vue.d.ts +52 -0
- package/dist/runtime/components/Shared/RecordRailCard.d.vue.ts +26 -0
- package/dist/runtime/components/Shared/RecordRailCard.vue +20 -0
- package/dist/runtime/components/Shared/RecordRailCard.vue.d.ts +26 -0
- package/dist/runtime/components/Shared/SidebarNav.d.vue.ts +5 -0
- package/dist/runtime/components/Shared/SidebarNav.vue +6 -26
- package/dist/runtime/components/Shared/SidebarNav.vue.d.ts +5 -0
- package/dist/runtime/components/Shared/SidebarNavLink.d.vue.ts +15 -0
- package/dist/runtime/components/Shared/SidebarNavLink.vue +49 -0
- package/dist/runtime/components/Shared/SidebarNavLink.vue.d.ts +15 -0
- package/dist/runtime/components/Shared/Wizard.vue +6 -3
- package/dist/runtime/composables/useListRail.d.ts +9 -0
- package/dist/runtime/composables/useListRail.js +19 -0
- package/dist/runtime/composables/useMediaLibrary.d.ts +41 -0
- package/dist/runtime/composables/useMediaLibrary.js +36 -0
- package/dist/runtime/composables/usePlatformAuth.d.ts +18 -0
- package/dist/runtime/composables/usePlatformTalkback.d.ts +29 -0
- package/dist/runtime/composables/usePlatformTalkback.js +54 -0
- package/dist/runtime/composables/useT.d.ts +11 -0
- package/dist/runtime/composables/useT.js +8 -0
- package/dist/runtime/composables/useTalkbackChannels.d.ts +78 -0
- package/dist/runtime/composables/useTalkbackChannels.js +52 -0
- package/dist/runtime/lib/formatBridge.d.ts +50 -0
- package/dist/runtime/lib/formatBridge.js +54 -0
- package/dist/runtime/lib/i18nBridge.d.ts +31 -0
- package/dist/runtime/lib/i18nBridge.js +17 -0
- package/dist/runtime/lib/isoLists.d.ts +61 -0
- package/dist/runtime/lib/isoLists.js +62 -0
- package/dist/runtime/lib/sidebarNav.d.ts +34 -0
- package/dist/runtime/lib/sidebarNav.js +22 -0
- package/dist/runtime/lib/talkback.d.ts +59 -0
- package/dist/runtime/lib/talkback.js +49 -0
- package/dist/runtime/types/nav.d.ts +15 -1
- package/dist/runtime/types/storage.d.ts +67 -0
- package/dist/runtime/types/storage.js +0 -0
- package/dist/types.d.mts +12 -0
- package/package.json +18 -2
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Ref } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* What the host registers: both reactive, so a computed that formatted once
|
|
4
|
+
* re-runs when the operator changes the preference — no reload.
|
|
5
|
+
*/
|
|
6
|
+
export interface FormatPreferenceSource {
|
|
7
|
+
/** Resolved BCP-47 tag, e.g. `de-AT`. Never `'auto'` — the host resolves. */
|
|
8
|
+
locale: Ref<string>;
|
|
9
|
+
/**
|
|
10
|
+
* `true` forces a 12-hour clock, `false` a 24-hour one, `undefined` leaves
|
|
11
|
+
* the choice to the locale's own convention (which is what `Intl` does when
|
|
12
|
+
* the option is absent).
|
|
13
|
+
*/
|
|
14
|
+
hour12: Ref<boolean | undefined>;
|
|
15
|
+
}
|
|
16
|
+
/** Used when there is no host AND no i18n bridge. See the file comment. */
|
|
17
|
+
export declare const FALLBACK_FORMAT_LOCALE = "en-GB";
|
|
18
|
+
/**
|
|
19
|
+
* The region an `auto` preference resolves to for a UI language.
|
|
20
|
+
*
|
|
21
|
+
* Takes the language subtag, so `en-US` and `en` land on the same answer, and
|
|
22
|
+
* falls back to {@link FALLBACK_FORMAT_LOCALE} for a language we do not ship.
|
|
23
|
+
*/
|
|
24
|
+
export declare function defaultRegionForLanguage(language: string | null | undefined): string;
|
|
25
|
+
/** Called once by the host. Pass `null` to detach (tests). */
|
|
26
|
+
export declare function setFormatPreferences(source: FormatPreferenceSource | null): void;
|
|
27
|
+
/** Whether a host preference is installed. Rarely needed — prefer the getters. */
|
|
28
|
+
export declare function hasFormatPreferences(): boolean;
|
|
29
|
+
/** The BCP-47 tag every package should format in. */
|
|
30
|
+
export declare function formatLocale(): string;
|
|
31
|
+
/** The operator's clock, or `undefined` to let the locale decide. */
|
|
32
|
+
export declare function hour12(): boolean | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* A cached `DateTimeFormat` for an explicitly given locale and clock.
|
|
35
|
+
*
|
|
36
|
+
* The host uses this one: it has already resolved both from its own reactive
|
|
37
|
+
* state and must not have the answer re-derived from a registry it may not
|
|
38
|
+
* have populated yet. Packages want {@link dateTimeFormat}.
|
|
39
|
+
*
|
|
40
|
+
* `clock` is folded into the options rather than left to each call site, but an
|
|
41
|
+
* explicit `hour12` in `options` still wins — a cell that must print a 24-hour
|
|
42
|
+
* timestamp regardless (a log line, a cron preview) can say so.
|
|
43
|
+
*/
|
|
44
|
+
export declare function dateTimeFormatFor(locale: string, options?: Intl.DateTimeFormatOptions, clock?: boolean): Intl.DateTimeFormat;
|
|
45
|
+
/** A cached `NumberFormat` for an explicitly given locale. */
|
|
46
|
+
export declare function numberFormatFor(locale: string, options?: Intl.NumberFormatOptions): Intl.NumberFormat;
|
|
47
|
+
/** A `DateTimeFormat` for whatever the host registered. What packages call. */
|
|
48
|
+
export declare function dateTimeFormat(options?: Intl.DateTimeFormatOptions): Intl.DateTimeFormat;
|
|
49
|
+
/** A `NumberFormat` for whatever the host registered. What packages call. */
|
|
50
|
+
export declare function numberFormat(options?: Intl.NumberFormatOptions): Intl.NumberFormat;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { shallowRef } from "vue";
|
|
2
|
+
import { currentLocale } from "./i18nBridge.js";
|
|
3
|
+
export const FALLBACK_FORMAT_LOCALE = "en-GB";
|
|
4
|
+
const DEFAULT_REGION_BY_LANGUAGE = {
|
|
5
|
+
de: "de-DE",
|
|
6
|
+
en: "en-GB"
|
|
7
|
+
};
|
|
8
|
+
export function defaultRegionForLanguage(language) {
|
|
9
|
+
if (!language) return FALLBACK_FORMAT_LOCALE;
|
|
10
|
+
const subtag = String(language).split(/[-_]/)[0].toLowerCase();
|
|
11
|
+
return DEFAULT_REGION_BY_LANGUAGE[subtag] ?? FALLBACK_FORMAT_LOCALE;
|
|
12
|
+
}
|
|
13
|
+
const registered = shallowRef(null);
|
|
14
|
+
export function setFormatPreferences(source) {
|
|
15
|
+
registered.value = source;
|
|
16
|
+
}
|
|
17
|
+
export function hasFormatPreferences() {
|
|
18
|
+
return registered.value !== null;
|
|
19
|
+
}
|
|
20
|
+
export function formatLocale() {
|
|
21
|
+
const source = registered.value;
|
|
22
|
+
if (source) return source.locale.value;
|
|
23
|
+
return defaultRegionForLanguage(currentLocale());
|
|
24
|
+
}
|
|
25
|
+
export function hour12() {
|
|
26
|
+
return registered.value?.hour12.value;
|
|
27
|
+
}
|
|
28
|
+
const dateTimeFormats = /* @__PURE__ */ new Map();
|
|
29
|
+
const numberFormats = /* @__PURE__ */ new Map();
|
|
30
|
+
export function dateTimeFormatFor(locale, options = {}, clock) {
|
|
31
|
+
const resolved = clock === void 0 || "hour12" in options ? options : { ...options, hour12: clock };
|
|
32
|
+
const key = `${locale}|${JSON.stringify(resolved)}`;
|
|
33
|
+
let format = dateTimeFormats.get(key);
|
|
34
|
+
if (!format) {
|
|
35
|
+
format = new Intl.DateTimeFormat(locale, resolved);
|
|
36
|
+
dateTimeFormats.set(key, format);
|
|
37
|
+
}
|
|
38
|
+
return format;
|
|
39
|
+
}
|
|
40
|
+
export function numberFormatFor(locale, options = {}) {
|
|
41
|
+
const key = `${locale}|${JSON.stringify(options)}`;
|
|
42
|
+
let format = numberFormats.get(key);
|
|
43
|
+
if (!format) {
|
|
44
|
+
format = new Intl.NumberFormat(locale, options);
|
|
45
|
+
numberFormats.set(key, format);
|
|
46
|
+
}
|
|
47
|
+
return format;
|
|
48
|
+
}
|
|
49
|
+
export function dateTimeFormat(options = {}) {
|
|
50
|
+
return dateTimeFormatFor(formatLocale(), options, hour12());
|
|
51
|
+
}
|
|
52
|
+
export function numberFormat(options = {}) {
|
|
53
|
+
return numberFormatFor(formatLocale(), options);
|
|
54
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Ref } from 'vue';
|
|
2
|
+
/** Interpolation values for a message with `{placeholders}`. */
|
|
3
|
+
export type TranslateNamed = Record<string, unknown>;
|
|
4
|
+
/**
|
|
5
|
+
* The subset of vue-i18n's `t` the packages are allowed to rely on:
|
|
6
|
+
* `t(key)`, `t(key, named)`, `t(key, plural)`, `t(key, plural, named)`.
|
|
7
|
+
*/
|
|
8
|
+
export interface Translate {
|
|
9
|
+
(key: string): string;
|
|
10
|
+
(key: string, named: TranslateNamed): string;
|
|
11
|
+
(key: string, plural: number): string;
|
|
12
|
+
(key: string, plural: number, named: TranslateNamed): string;
|
|
13
|
+
}
|
|
14
|
+
export interface Translator {
|
|
15
|
+
t: Translate;
|
|
16
|
+
/** The host's current UI locale. Reactive, so computeds re-run on a switch. */
|
|
17
|
+
locale: Ref<string>;
|
|
18
|
+
}
|
|
19
|
+
/** Called once by the host. Pass `null` to detach (tests). */
|
|
20
|
+
export declare function setTranslator(translator: Translator | null): void;
|
|
21
|
+
/** Whether a host translator is installed. Rarely needed — prefer `translate`. */
|
|
22
|
+
export declare function hasTranslator(): boolean;
|
|
23
|
+
/** The host's current UI locale, or `'en'` when the host has no i18n. */
|
|
24
|
+
export declare function currentLocale(): string;
|
|
25
|
+
/**
|
|
26
|
+
* Translate through the host, or return the key unchanged.
|
|
27
|
+
*
|
|
28
|
+
* Reads `locale.value` before delegating so the caller picks up a reactive
|
|
29
|
+
* dependency on the locale even if the host's `t` were to memoize.
|
|
30
|
+
*/
|
|
31
|
+
export declare const translate: Translate;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { shallowRef } from "vue";
|
|
2
|
+
const registered = shallowRef(null);
|
|
3
|
+
export function setTranslator(translator) {
|
|
4
|
+
registered.value = translator;
|
|
5
|
+
}
|
|
6
|
+
export function hasTranslator() {
|
|
7
|
+
return registered.value !== null;
|
|
8
|
+
}
|
|
9
|
+
export function currentLocale() {
|
|
10
|
+
return registered.value?.locale.value ?? "en";
|
|
11
|
+
}
|
|
12
|
+
export const translate = ((key, ...args) => {
|
|
13
|
+
const translator = registered.value;
|
|
14
|
+
if (!translator) return key;
|
|
15
|
+
void translator.locale.value;
|
|
16
|
+
return translator.t(key, ...args);
|
|
17
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The two international lists every studio ends up needing: countries
|
|
3
|
+
* (ISO 3166-1 alpha-2) and currencies (ISO 4217).
|
|
4
|
+
*
|
|
5
|
+
* They live in `studio-shared` rather than in a studio because a country is
|
|
6
|
+
* not a Commerce concern — Mail wants a sender country, Analytics wants a
|
|
7
|
+
* region breakdown, and whichever surface asked second would otherwise make
|
|
8
|
+
* the second copy. This module is that first copy, and the only one.
|
|
9
|
+
*
|
|
10
|
+
* ## Codes are data, names are derived
|
|
11
|
+
*
|
|
12
|
+
* The country CODES are written out here, because that literally is the
|
|
13
|
+
* standard: 249 two-letter identifiers, stable, and not enumerable from any
|
|
14
|
+
* runtime API. The NAMES are not written out — `Intl.DisplayNames` has them
|
|
15
|
+
* for every locale the browser ships, so a German cockpit reads
|
|
16
|
+
* "Vereinigtes Königreich" without anyone translating a hardcoded English
|
|
17
|
+
* list. Currencies need neither: `Intl.supportedValuesOf('currency')` is the
|
|
18
|
+
* code list, and `Intl.DisplayNames` is the names.
|
|
19
|
+
*
|
|
20
|
+
* The practical consequence is that adding a language costs nothing here.
|
|
21
|
+
*
|
|
22
|
+
* ## Sorted by the label, never by the code
|
|
23
|
+
*
|
|
24
|
+
* An operator scans a country list alphabetically by what they read.
|
|
25
|
+
* "Ägypten" belongs at the top of a German list and "Egypt" belongs under E;
|
|
26
|
+
* sorting by `EG` would put it in neither place. So every list is collated in
|
|
27
|
+
* the locale it is being displayed in, and the sort is redone per locale.
|
|
28
|
+
* The result is cached per locale — the collation is the expensive part and
|
|
29
|
+
* the answer never changes within a session.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* The locale the lists are labelled in when a caller does not say. The cockpit
|
|
33
|
+
* is German-facing; when a real `useLocale()` lands, pass it through and this
|
|
34
|
+
* default stops being used.
|
|
35
|
+
*/
|
|
36
|
+
export declare const DEFAULT_ISO_LOCALE = "de";
|
|
37
|
+
/** One choice: the stored code plus what a human reads in their language. */
|
|
38
|
+
export interface IsoOption {
|
|
39
|
+
/** The value written to the column — `'DE'`, `'EUR'`. */
|
|
40
|
+
code: string;
|
|
41
|
+
/** Localized name — `'Deutschland'`, `'Euro'`. Falls back to the code. */
|
|
42
|
+
label: string;
|
|
43
|
+
}
|
|
44
|
+
/** ISO 3166-1 alpha-2, unsorted. Use `countryOptions()` to display them. */
|
|
45
|
+
export declare function countryCodes(): readonly string[];
|
|
46
|
+
/**
|
|
47
|
+
* ISO 4217, as the runtime knows it. Empty on an engine without
|
|
48
|
+
* `Intl.supportedValuesOf` — callers treat that like any other unresolvable
|
|
49
|
+
* list and fall back to whatever literal options they were given.
|
|
50
|
+
*/
|
|
51
|
+
export declare function currencyCodes(): readonly string[];
|
|
52
|
+
/** Drop the memoised lists — for tests, and after a language switch. */
|
|
53
|
+
export declare function clearIsoListCache(): void;
|
|
54
|
+
/** Every country, sorted by its name in `locale`. */
|
|
55
|
+
export declare function countryOptions(locale?: string): IsoOption[];
|
|
56
|
+
/** Every currency the runtime knows, sorted by its name in `locale`. */
|
|
57
|
+
export declare function currencyOptions(locale?: string): IsoOption[];
|
|
58
|
+
/** `'DE'` → `'Deutschland'`. Unknown codes come back unchanged. */
|
|
59
|
+
export declare function countryLabel(code: unknown, locale?: string): string;
|
|
60
|
+
/** `'EUR'` → `'Euro'`. Unknown codes come back unchanged. */
|
|
61
|
+
export declare function currencyLabel(code: unknown, locale?: string): string;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const COUNTRY_CODES = Object.freeze(
|
|
2
|
+
"AD AE AF AG AI AL AM AO AQ AR AS AT AU AW AX AZ BA BB BD BE BF BG BH BI BJ BL BM BN BO BQ BR BS BT BV BW BY BZ CA CC CD CF CG CH CI CK CL CM CN CO CR CU CV CW CX CY CZ DE DJ DK DM DO DZ EC EE EG EH ER ES ET FI FJ FK FM FO FR GA GB GD GE GF GG GH GI GL GM GN GP GQ GR GS GT GU GW GY HK HM HN HR HT HU ID IE IL IM IN IO IQ IR IS IT JE JM JO JP KE KG KH KI KM KN KP KR KW KY KZ LA LB LC LI LK LR LS LT LU LV LY MA MC MD ME MF MG MH MK ML MM MN MO MP MQ MR MS MT MU MV MW MX MY MZ NA NC NE NF NG NI NL NO NP NR NU NZ OM PA PE PF PG PH PK PL PM PN PR PS PT PW PY QA RE RO RS RU RW SA SB SC SD SE SG SH SI SJ SK SL SM SN SO SR SS ST SV SX SY SZ TC TD TF TG TH TJ TK TL TM TN TO TR TT TV TW TZ UA UG UM US UY UZ VA VC VE VG VI VN VU WF WS YE YT ZA ZM ZW".split(" ")
|
|
3
|
+
);
|
|
4
|
+
export const DEFAULT_ISO_LOCALE = "de";
|
|
5
|
+
export function countryCodes() {
|
|
6
|
+
return COUNTRY_CODES;
|
|
7
|
+
}
|
|
8
|
+
export function currencyCodes() {
|
|
9
|
+
const supported = Intl.supportedValuesOf;
|
|
10
|
+
if (typeof supported !== "function") return [];
|
|
11
|
+
try {
|
|
12
|
+
return Object.freeze(supported.call(Intl, "currency"));
|
|
13
|
+
} catch {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function displayNames(type, locale) {
|
|
18
|
+
try {
|
|
19
|
+
return new Intl.DisplayNames([locale], { type, fallback: "code" });
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function collator(locale) {
|
|
25
|
+
try {
|
|
26
|
+
return new Intl.Collator(locale);
|
|
27
|
+
} catch {
|
|
28
|
+
return { compare: (a, b) => a < b ? -1 : a > b ? 1 : 0 };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function build(codes, type, locale) {
|
|
32
|
+
const names = displayNames(type, locale);
|
|
33
|
+
const compare = collator(locale).compare;
|
|
34
|
+
return codes.map((code) => ({ code, label: names?.of(code) || code })).sort((a, b) => compare(a.label, b.label));
|
|
35
|
+
}
|
|
36
|
+
const cache = /* @__PURE__ */ new Map();
|
|
37
|
+
function cached(key, make) {
|
|
38
|
+
const hit = cache.get(key);
|
|
39
|
+
if (hit) return hit;
|
|
40
|
+
const built = make();
|
|
41
|
+
cache.set(key, built);
|
|
42
|
+
return built;
|
|
43
|
+
}
|
|
44
|
+
export function clearIsoListCache() {
|
|
45
|
+
cache.clear();
|
|
46
|
+
}
|
|
47
|
+
export function countryOptions(locale = DEFAULT_ISO_LOCALE) {
|
|
48
|
+
return cached(`region:${locale}`, () => build(COUNTRY_CODES, "region", locale));
|
|
49
|
+
}
|
|
50
|
+
export function currencyOptions(locale = DEFAULT_ISO_LOCALE) {
|
|
51
|
+
return cached(`currency:${locale}`, () => build(currencyCodes(), "currency", locale));
|
|
52
|
+
}
|
|
53
|
+
export function countryLabel(code, locale = DEFAULT_ISO_LOCALE) {
|
|
54
|
+
const key = typeof code === "string" ? code.trim().toUpperCase() : "";
|
|
55
|
+
if (!key) return "";
|
|
56
|
+
return displayNames("region", locale)?.of(key) || key;
|
|
57
|
+
}
|
|
58
|
+
export function currencyLabel(code, locale = DEFAULT_ISO_LOCALE) {
|
|
59
|
+
const key = typeof code === "string" ? code.trim().toUpperCase() : "";
|
|
60
|
+
if (!key) return "";
|
|
61
|
+
return displayNames("currency", locale)?.of(key) || key;
|
|
62
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SidebarNavItem } from '../types/nav.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pure helpers behind `SidebarNav` / `SidebarNavLink`. They live outside the
|
|
4
|
+
* SFCs so the active-route and link-target rules can be tested without
|
|
5
|
+
* mounting a component (and so both SFCs share exactly one implementation).
|
|
6
|
+
*
|
|
7
|
+
* **`children` means tabs, not sub-navigation.** An item with children is one
|
|
8
|
+
* sidebar row; its children are the tabs of the page that row opens. Nothing
|
|
9
|
+
* here expands, so there is no collapse state to keep — the row is a link like
|
|
10
|
+
* any other, it just knows about the routes its tabs carry. Anything that
|
|
11
|
+
* should stand on its own in the sidebar is declared as its own item.
|
|
12
|
+
*/
|
|
13
|
+
/** Stable identity for `v-for` keys — `to` is unique where it exists. */
|
|
14
|
+
export declare function sidebarItemKey(item: SidebarNavItem): string;
|
|
15
|
+
/** True when `path` is the item's own route, or sits below it. */
|
|
16
|
+
export declare function sidebarItemIsActive(item: SidebarNavItem, path: string): boolean;
|
|
17
|
+
/** True when the item, or any of its tabs, matches `path`. */
|
|
18
|
+
export declare function sidebarItemHoldsActive(item: SidebarNavItem, path: string): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Which row gets the highlight. The tabs of a group are not rows of their own,
|
|
21
|
+
* so the group stays lit for every one of them — an operator on *Carriers*
|
|
22
|
+
* still sees where they are, which is **Shipping**.
|
|
23
|
+
*/
|
|
24
|
+
export declare function sidebarItemIsCurrent(item: SidebarNavItem, path: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Where the row links. A group with a route of its own opens that (it is the
|
|
27
|
+
* default tab); a pure container — a heading several apps contribute to, where
|
|
28
|
+
* no single app's view is the right landing page — opens its first tab.
|
|
29
|
+
*
|
|
30
|
+
* `undefined` means the row has nothing to open at all. `navCompose` prunes
|
|
31
|
+
* those before they reach a sidebar, but the guard keeps a hand-written
|
|
32
|
+
* section (Settings, Apps, …) from rendering a dead row.
|
|
33
|
+
*/
|
|
34
|
+
export declare function sidebarItemTarget(item: SidebarNavItem): string | undefined;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function sidebarItemKey(item) {
|
|
2
|
+
return item.key ?? item.to ?? item.label;
|
|
3
|
+
}
|
|
4
|
+
export function sidebarItemIsActive(item, path) {
|
|
5
|
+
if (!item.to) return false;
|
|
6
|
+
if (item.exact) return path === item.to;
|
|
7
|
+
return path === item.to || path.startsWith(`${item.to}/`);
|
|
8
|
+
}
|
|
9
|
+
export function sidebarItemHoldsActive(item, path) {
|
|
10
|
+
return sidebarItemIsActive(item, path) || (item.children ?? []).some((child) => sidebarItemHoldsActive(child, path));
|
|
11
|
+
}
|
|
12
|
+
export function sidebarItemIsCurrent(item, path) {
|
|
13
|
+
return sidebarItemHoldsActive(item, path);
|
|
14
|
+
}
|
|
15
|
+
export function sidebarItemTarget(item) {
|
|
16
|
+
if (item.to) return item.to;
|
|
17
|
+
for (const child of item.children ?? []) {
|
|
18
|
+
const target = sidebarItemTarget(child);
|
|
19
|
+
if (target) return target;
|
|
20
|
+
}
|
|
21
|
+
return void 0;
|
|
22
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ShallowRef } from 'vue';
|
|
2
|
+
import type { Talkback, TalkbackOptions } from '@revenexx/talkback-js';
|
|
3
|
+
export interface PlatformTalkbackConfig {
|
|
4
|
+
/** Centrifugo's client transport, e.g. `https://talkback.revenexx.com`. */
|
|
5
|
+
host: string;
|
|
6
|
+
/**
|
|
7
|
+
* Where a connection token is minted. The facade's own `${host}/v1/tokens` by
|
|
8
|
+
* default — the browser's Zitadel token authorises it directly, so no BFF route
|
|
9
|
+
* stands in between.
|
|
10
|
+
*/
|
|
11
|
+
tokenEndpoint: string;
|
|
12
|
+
/** Likewise, `${host}/v1/subscription-tokens`. */
|
|
13
|
+
subscriptionTokenEndpoint: string;
|
|
14
|
+
/**
|
|
15
|
+
* The active tenant slug. A GETTER, not a value: operator impersonation
|
|
16
|
+
* changes it at run time, and it is positionally first in every channel name.
|
|
17
|
+
*/
|
|
18
|
+
tenant: () => string;
|
|
19
|
+
/**
|
|
20
|
+
* The caller's Zitadel access token, passed to the facade as the bearer.
|
|
21
|
+
*
|
|
22
|
+
* The facade admits it because an end user holds `user` or `admin` in the
|
|
23
|
+
* Zitadel USER project, and such a caller may mint only for itself: the request
|
|
24
|
+
* carries no `user_id` and the facade fills it from this token's own `sub`.
|
|
25
|
+
*/
|
|
26
|
+
getToken: () => Promise<string | null>;
|
|
27
|
+
/**
|
|
28
|
+
* Replaces the Centrifuge client. Left unset outside tests — the fake in
|
|
29
|
+
* `@revenexx/talkback-js/testing` satisfies it.
|
|
30
|
+
*/
|
|
31
|
+
client?: TalkbackOptions['client'];
|
|
32
|
+
debug?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The `sub` claim of a JWT, or null when it isn't one.
|
|
36
|
+
*
|
|
37
|
+
* This is where the client's `userId` comes from, and the choice is
|
|
38
|
+
* load-bearing rather than convenient. The BFF derives the token's subject from
|
|
39
|
+
* the same claim of the same token, so client and server cannot disagree about
|
|
40
|
+
* the name of the `user:` channel — a mismatch would mint a grant for one name
|
|
41
|
+
* and subscribe to another, which fails as a silent 403 on a channel nobody
|
|
42
|
+
* publishes to. Deliberately NOT verified: nothing is authorised here, and the
|
|
43
|
+
* BFF verifies the signature before it grants anything.
|
|
44
|
+
*
|
|
45
|
+
* Mirrors the base64url handling in the cockpit's `useConsoleAuth`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function decodeJwtSub(jwt: string | null | undefined): string | null;
|
|
48
|
+
/**
|
|
49
|
+
* Builds the client for a host.
|
|
50
|
+
*
|
|
51
|
+
* `tenant` and `userId` are providers because talkback-js reads them on every
|
|
52
|
+
* channel build; passing values would pin the first tenant the page saw.
|
|
53
|
+
*/
|
|
54
|
+
export declare function createPlatformTalkback(config: PlatformTalkbackConfig): Promise<Talkback>;
|
|
55
|
+
/** Registered once by the host's provider. Pass `null` to detach (tests). */
|
|
56
|
+
export declare function setTalkback(tb: Talkback | null): void;
|
|
57
|
+
export declare function getTalkback(): Talkback | null;
|
|
58
|
+
/** The registry itself, for composables that must react to it appearing. */
|
|
59
|
+
export declare function talkbackRef(): ShallowRef<Talkback | null>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { shallowRef } from "vue";
|
|
2
|
+
export function decodeJwtSub(jwt) {
|
|
3
|
+
if (!jwt) return null;
|
|
4
|
+
try {
|
|
5
|
+
const parts = jwt.split(".");
|
|
6
|
+
if (parts.length !== 3) return null;
|
|
7
|
+
const p = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
8
|
+
const b64 = p.padEnd(p.length + (4 - p.length % 4) % 4, "=");
|
|
9
|
+
const claims = JSON.parse(atob(b64));
|
|
10
|
+
return typeof claims.sub === "string" && claims.sub ? claims.sub : null;
|
|
11
|
+
} catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export async function createPlatformTalkback(config) {
|
|
16
|
+
const { createTalkback } = await import("@revenexx/talkback-js");
|
|
17
|
+
let userId = "";
|
|
18
|
+
const accessToken = async () => {
|
|
19
|
+
const token = await config.getToken();
|
|
20
|
+
if (token) {
|
|
21
|
+
userId = decodeJwtSub(token) ?? userId;
|
|
22
|
+
}
|
|
23
|
+
return token;
|
|
24
|
+
};
|
|
25
|
+
try {
|
|
26
|
+
userId = decodeJwtSub(await config.getToken()) ?? "";
|
|
27
|
+
} catch {
|
|
28
|
+
}
|
|
29
|
+
return createTalkback({
|
|
30
|
+
host: config.host,
|
|
31
|
+
tenant: () => config.tenant(),
|
|
32
|
+
userId: () => userId,
|
|
33
|
+
tokenEndpoint: config.tokenEndpoint,
|
|
34
|
+
subscriptionTokenEndpoint: config.subscriptionTokenEndpoint,
|
|
35
|
+
accessToken,
|
|
36
|
+
...config.client ? { client: config.client } : {},
|
|
37
|
+
...config.debug === void 0 ? {} : { debug: config.debug }
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const current = shallowRef(null);
|
|
41
|
+
export function setTalkback(tb) {
|
|
42
|
+
current.value = tb;
|
|
43
|
+
}
|
|
44
|
+
export function getTalkback() {
|
|
45
|
+
return current.value;
|
|
46
|
+
}
|
|
47
|
+
export function talkbackRef() {
|
|
48
|
+
return current;
|
|
49
|
+
}
|
|
@@ -7,13 +7,27 @@ import type { Component } from 'vue';
|
|
|
7
7
|
*/
|
|
8
8
|
export type SidebarNavItem = {
|
|
9
9
|
label: string;
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Target route. Omit it for a pure container — a heading several apps
|
|
12
|
+
* contribute pages to, where no single view is the right landing page. The
|
|
13
|
+
* row then links to the first of its `children` instead.
|
|
14
|
+
*/
|
|
15
|
+
to?: string;
|
|
11
16
|
icon?: Component;
|
|
12
17
|
badge?: number | string | null;
|
|
13
18
|
/** Dim + show a "soon" tag (not-yet-shipped surfaces). */
|
|
14
19
|
preview?: boolean;
|
|
15
20
|
/** Match the route exactly (for studio roots like `/settings`). */
|
|
16
21
|
exact?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* The item's **tabs**, not sub-navigation. The sidebar shows one row; the
|
|
24
|
+
* page it opens shows these across the top, each addressed by its own route
|
|
25
|
+
* (see `studio-commerce/lib/navGroups.ts`). The row stays highlighted on all
|
|
26
|
+
* of them.
|
|
27
|
+
*/
|
|
28
|
+
children?: SidebarNavItem[];
|
|
29
|
+
/** Stable `v-for` key. Defaults to `to ?? label`. */
|
|
30
|
+
key?: string;
|
|
17
31
|
};
|
|
18
32
|
export type SidebarNavSection = {
|
|
19
33
|
key: string;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media storage service (revenexx/storage) `/v1` API shapes — the subset the
|
|
3
|
+
* shared media components (uploader, folder tree, picker dialog) render.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the cockpit's `app/types/storage.ts` field-for-field so the two are
|
|
6
|
+
* structurally identical: the cockpit passes its own `StorageAsset` straight
|
|
7
|
+
* into these components and back out of their emits. The module can't import
|
|
8
|
+
* the cockpit's copy (host-only path), so the contract lives here.
|
|
9
|
+
*/
|
|
10
|
+
export type AssetKind = 'image' | 'video' | 'audio' | 'pdf' | 'document' | 'archive' | 'model3d' | 'other';
|
|
11
|
+
export type AssetStatus = 'uploading' | 'pending_scan' | 'pending_processing' | 'available' | 'quarantined' | 'processing_failed' | 'soft_deleted';
|
|
12
|
+
export type AssetVisibility = 'public' | 'private';
|
|
13
|
+
export interface StorageAsset {
|
|
14
|
+
id: string;
|
|
15
|
+
tenant_id: string;
|
|
16
|
+
folder_id: string | null;
|
|
17
|
+
kind: AssetKind;
|
|
18
|
+
status: AssetStatus;
|
|
19
|
+
visibility: AssetVisibility;
|
|
20
|
+
mime_type: string;
|
|
21
|
+
original_name: string;
|
|
22
|
+
path_name: string;
|
|
23
|
+
display_name: string | null;
|
|
24
|
+
alt_text: string | null;
|
|
25
|
+
description: string | null;
|
|
26
|
+
tags: string[];
|
|
27
|
+
size_bytes: number;
|
|
28
|
+
content_hash: string | null;
|
|
29
|
+
width: number | null;
|
|
30
|
+
height: number | null;
|
|
31
|
+
duration_ms: number | null;
|
|
32
|
+
page_count: number | null;
|
|
33
|
+
dominant_color: string | null;
|
|
34
|
+
metadata: Record<string, unknown>;
|
|
35
|
+
/** Delivery URL for public assets; null for private (use a signed URL). */
|
|
36
|
+
url: string | null;
|
|
37
|
+
/** 3D assets only: imgproxy-resizable preview image (rendered from the model). */
|
|
38
|
+
preview_url?: string | null;
|
|
39
|
+
/** 3D assets only: the web-renderable mesh (.glb) for <rx-model-viewer>. */
|
|
40
|
+
model_url?: string | null;
|
|
41
|
+
created_at: string | null;
|
|
42
|
+
updated_at: string | null;
|
|
43
|
+
processed_at: string | null;
|
|
44
|
+
deleted_at: string | null;
|
|
45
|
+
}
|
|
46
|
+
export interface StorageFolder {
|
|
47
|
+
id: string;
|
|
48
|
+
tenant_id: string;
|
|
49
|
+
parent_id: string | null;
|
|
50
|
+
name: string;
|
|
51
|
+
/** Full hierarchy path, e.g. `/marketing/q1-campaign`. */
|
|
52
|
+
path: string;
|
|
53
|
+
is_system: boolean;
|
|
54
|
+
created_at: string | null;
|
|
55
|
+
updated_at: string | null;
|
|
56
|
+
}
|
|
57
|
+
/** A folder augmented with its children, for rendering the tree. */
|
|
58
|
+
export interface StorageFolderNode extends StorageFolder {
|
|
59
|
+
children: StorageFolderNode[];
|
|
60
|
+
}
|
|
61
|
+
/** Per-file state while a batch uploads. */
|
|
62
|
+
export interface UploadProgress {
|
|
63
|
+
name: string;
|
|
64
|
+
pct: number;
|
|
65
|
+
status: 'pending' | 'uploading' | 'done' | 'failed';
|
|
66
|
+
error?: string;
|
|
67
|
+
}
|
|
File without changes
|
package/dist/types.d.mts
CHANGED
|
@@ -10,6 +10,18 @@ export { type PlatformAuth } from '../dist/runtime/composables/usePlatformAuth.j
|
|
|
10
10
|
|
|
11
11
|
export { type PlatformTenant } from '../dist/runtime/composables/usePlatformTenant.js'
|
|
12
12
|
|
|
13
|
+
export { type PlatformTalkback } from '../dist/runtime/composables/usePlatformTalkback.js'
|
|
14
|
+
|
|
15
|
+
export { type TalkbackChannel, type TalkbackChannelOptions, type TalkbackMaybeRef } from '../dist/runtime/composables/useTalkbackChannels.js'
|
|
16
|
+
|
|
17
|
+
export { type MediaLibraryClient } from '../dist/runtime/composables/useMediaLibrary.js'
|
|
18
|
+
|
|
19
|
+
export { type IsoOption } from '../dist/runtime/lib/isoLists.js'
|
|
20
|
+
|
|
21
|
+
export { type Translate, type TranslateNamed, type Translator } from '../dist/runtime/lib/i18nBridge.js'
|
|
22
|
+
|
|
13
23
|
export { default } from './module.mjs'
|
|
14
24
|
|
|
15
25
|
export * from '../dist/runtime/components/DataTable/types.js'
|
|
26
|
+
|
|
27
|
+
export * from '../dist/runtime/types/storage.js'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revenexx/studio-shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shared revenexx Studio shell primitives (PageHeader, SidebarNav, StateCard, DataTable, …) + the platform provider contract (usePlatformAuth/usePlatformTenant). A Nuxt module — add it to `modules`.",
|
|
6
6
|
"main": "./dist/module.mjs",
|
|
@@ -10,7 +10,11 @@
|
|
|
10
10
|
"types": "./dist/types.d.mts",
|
|
11
11
|
"import": "./dist/module.mjs"
|
|
12
12
|
},
|
|
13
|
-
"./components/*": "./dist/runtime/components/*"
|
|
13
|
+
"./components/*": "./dist/runtime/components/*",
|
|
14
|
+
"./lib/*": {
|
|
15
|
+
"types": "./dist/runtime/lib/*.d.ts",
|
|
16
|
+
"import": "./dist/runtime/lib/*.js"
|
|
17
|
+
}
|
|
14
18
|
},
|
|
15
19
|
"files": [
|
|
16
20
|
"dist",
|
|
@@ -25,14 +29,26 @@
|
|
|
25
29
|
},
|
|
26
30
|
"peerDependencies": {
|
|
27
31
|
"@revenexx/studio": "*",
|
|
32
|
+
"@revenexx/talkback-js": "^0.3.0",
|
|
28
33
|
"@solar-icons/nuxt": "^1.2.0",
|
|
34
|
+
"centrifuge": ">=5.2.0",
|
|
29
35
|
"nuxt": "^4.0.0",
|
|
30
36
|
"vue": "^3.5.0",
|
|
31
37
|
"vue-sonner": "^2.0.0"
|
|
32
38
|
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"@revenexx/talkback-js": {
|
|
41
|
+
"optional": true
|
|
42
|
+
},
|
|
43
|
+
"centrifuge": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
33
47
|
"devDependencies": {
|
|
34
48
|
"@nuxt/module-builder": "^1.0.2",
|
|
35
49
|
"@nuxt/kit": "^4.0.0",
|
|
50
|
+
"@revenexx/talkback-js": "^0.3.0",
|
|
51
|
+
"centrifuge": "^5.4.0",
|
|
36
52
|
"nuxt": "^4.2.2",
|
|
37
53
|
"vue": "^3.5.26",
|
|
38
54
|
"vue-tsc": "^2.2.0"
|