@morscherlab/mint-sdk 1.0.0 → 1.0.1
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/BaseModal-B9UA8Y_I.js +165 -0
- package/dist/BaseModal-B9UA8Y_I.js.map +1 -0
- package/dist/BaseSelect-DksaKYq_.js +176 -0
- package/dist/BaseSelect-DksaKYq_.js.map +1 -0
- package/dist/ExperimentPopover-CCYB1oWp.js +361 -0
- package/dist/ExperimentPopover-CCYB1oWp.js.map +1 -0
- package/dist/ExperimentPopover-D0bg_fqM.js +3 -0
- package/dist/ExperimentSelectorModal-B_kPbXcg.js +4 -0
- package/dist/ExperimentSelectorModal-wm7yUdAr.js +720 -0
- package/dist/ExperimentSelectorModal-wm7yUdAr.js.map +1 -0
- package/dist/SettingsModal-L7Ejny45.js +5 -0
- package/dist/SettingsModal-LEKI6Ebl.js +521 -0
- package/dist/SettingsModal-LEKI6Ebl.js.map +1 -0
- package/dist/{auth-BulIv_km.js → auth-D9q2GIcv.js} +3 -80
- package/dist/auth-D9q2GIcv.js.map +1 -0
- package/dist/components/DataFrame.vue.d.ts +3 -0
- package/dist/components/ExperimentDataViewer.vue.d.ts +2 -0
- package/dist/components/PluginWorkspaceView.vue.d.ts +2 -2
- package/dist/components/index.js +7 -2
- package/dist/{components-DtX3LDLq.js → components-CdjRzHI2.js} +533 -2025
- package/dist/components-CdjRzHI2.js.map +1 -0
- package/dist/composables/index.js +9 -3
- package/dist/composables/usePluginClient.d.ts +2 -1
- package/dist/{composables-wNt7VtkF.js → composables-DJgqPrlR.js} +7 -12
- package/dist/{composables-wNt7VtkF.js.map → composables-DJgqPrlR.js.map} +1 -1
- package/dist/experiment-utils-hGXMHlAc.js +109 -0
- package/dist/experiment-utils-hGXMHlAc.js.map +1 -0
- package/dist/index.js +16 -5
- package/dist/index.js.map +1 -1
- package/dist/install.js +7 -2
- package/dist/install.js.map +1 -1
- package/dist/permissions.js +81 -0
- package/dist/permissions.js.map +1 -0
- package/dist/stores/index.js +1 -1
- package/dist/styles.css +3233 -3185
- package/dist/templates/index.js +3 -1
- package/dist/templates-Do43ZIMb.js +5065 -0
- package/dist/templates-Do43ZIMb.js.map +1 -0
- package/dist/{templates-DSbHJC4v.js → useControlSchema-0n8Bcftq.js} +10 -5335
- package/dist/useControlSchema-0n8Bcftq.js.map +1 -0
- package/dist/useDropdownState-Ben4DnjJ.js +47 -0
- package/dist/useDropdownState-Ben4DnjJ.js.map +1 -0
- package/dist/useEventListener-CfVkP9Xz.js +57 -0
- package/dist/useEventListener-CfVkP9Xz.js.map +1 -0
- package/dist/useExperimentSelector-BpZklTbV.js +469 -0
- package/dist/useExperimentSelector-BpZklTbV.js.map +1 -0
- package/dist/useFormBuilder-COfYWDuC.js +729 -0
- package/dist/useFormBuilder-COfYWDuC.js.map +1 -0
- package/dist/{useProtocolTemplates-DwBhEPPU.js → useProtocolTemplates-TUQO_F3n.js} +8 -1298
- package/dist/useProtocolTemplates-TUQO_F3n.js.map +1 -0
- package/dist/utils/pluginIcon.d.ts +29 -2
- package/package.json +5 -1
- package/src/__tests__/components/DataFrame.test.ts +37 -0
- package/src/__tests__/components/PluginIcon.test.ts +77 -0
- package/src/__tests__/composables/usePluginClient.test.ts +11 -10
- package/src/components/AppTopBar.vue +7 -6
- package/src/components/DataFrame.vue +27 -2
- package/src/components/ExperimentDataViewer.vue +5 -1
- package/src/components/PluginIcon.story.vue +31 -1
- package/src/components/PluginIcon.vue +94 -4
- package/src/composables/usePluginClient.ts +3 -12
- package/src/styles/components/dataframe.css +26 -0
- package/src/styles/components/plugin-icon.css +5 -0
- package/src/utils/pluginIcon.ts +159 -2
- package/dist/auth-BulIv_km.js.map +0 -1
- package/dist/components-DtX3LDLq.js.map +0 -1
- package/dist/templates-DSbHJC4v.js.map +0 -1
- package/dist/useProtocolTemplates-DwBhEPPU.js.map +0 -1
package/src/utils/pluginIcon.ts
CHANGED
|
@@ -1,8 +1,39 @@
|
|
|
1
|
-
export type PluginIconFormat = 'path' | 'data-url' | 'https-url' | 'fallback'
|
|
1
|
+
export type PluginIconFormat = 'path' | 'data-url' | 'https-url' | 'structured' | 'fallback'
|
|
2
|
+
|
|
3
|
+
export interface StructuredPluginIconStop {
|
|
4
|
+
offset: string
|
|
5
|
+
color: string
|
|
6
|
+
opacity?: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface StructuredPluginIconBackground {
|
|
10
|
+
fill: string
|
|
11
|
+
radius: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface StructuredPluginIconPath {
|
|
15
|
+
d: string
|
|
16
|
+
fill?: string
|
|
17
|
+
stroke?: string
|
|
18
|
+
strokeWidth?: number
|
|
19
|
+
opacity?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface StructuredPluginIcon {
|
|
23
|
+
type: 'mint-icon'
|
|
24
|
+
viewBox: string
|
|
25
|
+
background?: StructuredPluginIconBackground
|
|
26
|
+
gradient?: {
|
|
27
|
+
type: 'linear'
|
|
28
|
+
angle: number
|
|
29
|
+
stops: StructuredPluginIconStop[]
|
|
30
|
+
}
|
|
31
|
+
paths: StructuredPluginIconPath[]
|
|
32
|
+
}
|
|
2
33
|
|
|
3
34
|
export interface DetectedPluginIcon {
|
|
4
35
|
format: PluginIconFormat
|
|
5
|
-
value: string
|
|
36
|
+
value: string | StructuredPluginIcon
|
|
6
37
|
}
|
|
7
38
|
|
|
8
39
|
// MINT canonical plugin placeholder — page-with-corner-fold glyph.
|
|
@@ -10,6 +41,11 @@ export const PLUGIN_ICON_FALLBACK_PATH = 'M14 7v4a1 1 0 0 0 1 1h4M5 3h9l5 5v11a2
|
|
|
10
41
|
|
|
11
42
|
const PATH_REGEX = /^[Mm][\s,\d\-.]/
|
|
12
43
|
const RASTER_DATA_URL_REGEX = /^data:image\/(png|jpeg|jpg|gif|webp);/
|
|
44
|
+
const VIEW_BOX_REGEX = /^-?\d+(?:\.\d+)?\s+-?\d+(?:\.\d+)?\s+\d+(?:\.\d+)?\s+\d+(?:\.\d+)?$/
|
|
45
|
+
const COLOR_REGEX = /^(#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})|currentColor|white|black|none)$/i
|
|
46
|
+
const MAX_STRUCTURED_ICON_LENGTH = 8000
|
|
47
|
+
const MAX_PATHS = 8
|
|
48
|
+
const MAX_STOPS = 8
|
|
13
49
|
|
|
14
50
|
export function normalizePluginIconSource(icon?: string): string {
|
|
15
51
|
return (icon ?? '').replace(/^[\s]+/, '')
|
|
@@ -18,6 +54,8 @@ export function normalizePluginIconSource(icon?: string): string {
|
|
|
18
54
|
export function detectPluginIcon(icon?: string): DetectedPluginIcon {
|
|
19
55
|
const raw = normalizePluginIconSource(icon)
|
|
20
56
|
if (!raw) return { format: 'fallback', value: PLUGIN_ICON_FALLBACK_PATH }
|
|
57
|
+
const structured = parseStructuredPluginIcon(raw)
|
|
58
|
+
if (structured) return { format: 'structured', value: structured }
|
|
21
59
|
if (PATH_REGEX.test(raw)) return { format: 'path', value: raw }
|
|
22
60
|
if (RASTER_DATA_URL_REGEX.test(raw)) return { format: 'data-url', value: raw }
|
|
23
61
|
if (raw.startsWith('https://')) return { format: 'https-url', value: raw }
|
|
@@ -28,3 +66,122 @@ export function isPluginIconFormat(icon?: string): boolean {
|
|
|
28
66
|
const raw = normalizePluginIconSource(icon)
|
|
29
67
|
return !!raw && detectPluginIcon(raw).format !== 'fallback'
|
|
30
68
|
}
|
|
69
|
+
|
|
70
|
+
function parseStructuredPluginIcon(raw: string): StructuredPluginIcon | undefined {
|
|
71
|
+
if (!raw.startsWith('{') || raw.length > MAX_STRUCTURED_ICON_LENGTH) return undefined
|
|
72
|
+
|
|
73
|
+
let parsed: unknown
|
|
74
|
+
try {
|
|
75
|
+
parsed = JSON.parse(raw)
|
|
76
|
+
} catch {
|
|
77
|
+
return undefined
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!isRecord(parsed) || parsed.type !== 'mint-icon') return undefined
|
|
81
|
+
|
|
82
|
+
const viewBox = typeof parsed.viewBox === 'string' && VIEW_BOX_REGEX.test(parsed.viewBox)
|
|
83
|
+
? parsed.viewBox
|
|
84
|
+
: '0 0 24 24'
|
|
85
|
+
|
|
86
|
+
const paths = parseStructuredPaths(parsed.paths)
|
|
87
|
+
if (!paths.length) return undefined
|
|
88
|
+
|
|
89
|
+
const gradient = parseStructuredGradient(parsed.gradient)
|
|
90
|
+
const background = parseStructuredBackground(parsed.background, gradient)
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
type: 'mint-icon',
|
|
94
|
+
viewBox,
|
|
95
|
+
...(background ? { background } : {}),
|
|
96
|
+
...(gradient ? { gradient } : {}),
|
|
97
|
+
paths,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function parseStructuredPaths(value: unknown): StructuredPluginIconPath[] {
|
|
102
|
+
if (!Array.isArray(value)) return []
|
|
103
|
+
|
|
104
|
+
return value.slice(0, MAX_PATHS).flatMap((item) => {
|
|
105
|
+
if (!isRecord(item) || typeof item.d !== 'string' || !item.d.trim()) return []
|
|
106
|
+
const path: StructuredPluginIconPath = { d: item.d.slice(0, 2000) }
|
|
107
|
+
if (isSafeColor(item.fill)) path.fill = item.fill
|
|
108
|
+
if (isSafeColor(item.stroke)) path.stroke = item.stroke
|
|
109
|
+
if (isFiniteNumber(item.strokeWidth, 0, 24)) path.strokeWidth = item.strokeWidth
|
|
110
|
+
if (isFiniteNumber(item.opacity, 0, 1)) path.opacity = item.opacity
|
|
111
|
+
return [path]
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function parseStructuredGradient(value: unknown): StructuredPluginIcon['gradient'] | undefined {
|
|
116
|
+
if (!isRecord(value) || value.type !== 'linear') return undefined
|
|
117
|
+
|
|
118
|
+
const stops = parseStructuredStops(value.stops)
|
|
119
|
+
if (stops.length < 2 && isSafeColor(value.from) && isSafeColor(value.to)) {
|
|
120
|
+
stops.push(
|
|
121
|
+
{ offset: '0%', color: value.from },
|
|
122
|
+
{ offset: '100%', color: value.to },
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
if (stops.length < 2) return undefined
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
type: 'linear',
|
|
129
|
+
angle: isFiniteNumber(value.angle, 0, 360) ? value.angle : 135,
|
|
130
|
+
stops,
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function parseStructuredStops(value: unknown): StructuredPluginIconStop[] {
|
|
135
|
+
if (!Array.isArray(value)) return []
|
|
136
|
+
|
|
137
|
+
return value.slice(0, MAX_STOPS).flatMap((item) => {
|
|
138
|
+
if (!isRecord(item) || !isSafeColor(item.color)) return []
|
|
139
|
+
|
|
140
|
+
const offset = parseOffset(item.offset)
|
|
141
|
+
if (!offset) return []
|
|
142
|
+
|
|
143
|
+
return [{
|
|
144
|
+
offset,
|
|
145
|
+
color: item.color,
|
|
146
|
+
...(isFiniteNumber(item.opacity, 0, 1) ? { opacity: item.opacity } : {}),
|
|
147
|
+
}]
|
|
148
|
+
})
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function parseStructuredBackground(
|
|
152
|
+
value: unknown,
|
|
153
|
+
gradient: StructuredPluginIcon['gradient'] | undefined,
|
|
154
|
+
): StructuredPluginIconBackground | undefined {
|
|
155
|
+
if (!isRecord(value)) return undefined
|
|
156
|
+
|
|
157
|
+
const fill = value.fill === 'gradient' && gradient
|
|
158
|
+
? 'gradient'
|
|
159
|
+
: isSafeColor(value.fill)
|
|
160
|
+
? value.fill
|
|
161
|
+
: undefined
|
|
162
|
+
if (!fill) return undefined
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
fill,
|
|
166
|
+
radius: isFiniteNumber(value.radius, 0, 12) ? value.radius : 5,
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function parseOffset(value: unknown): string | undefined {
|
|
171
|
+
if (isFiniteNumber(value, 0, 1)) return String(value)
|
|
172
|
+
if (typeof value !== 'string') return undefined
|
|
173
|
+
if (/^(?:100|(?:[1-9]?\d)(?:\.\d+)?)%$/.test(value)) return value
|
|
174
|
+
return undefined
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
178
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function isSafeColor(value: unknown): value is string {
|
|
182
|
+
return typeof value === 'string' && COLOR_REGEX.test(value)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function isFiniteNumber(value: unknown, min: number, max: number): value is number {
|
|
186
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= min && value <= max
|
|
187
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth-BulIv_km.js","names":[],"sources":["../src/stores/settings.ts","../src/permissions.ts","../src/stores/auth.ts"],"sourcesContent":["import { defineStore } from 'pinia'\nimport { ref, watch } from 'vue'\nimport type { ThemeMode, ColorPalette, TableDensity } from '../types'\n\ndeclare global {\n interface ImportMetaEnv {\n readonly VITE_API_PREFIX?: string\n }\n\n interface ImportMeta {\n readonly env: ImportMetaEnv\n }\n}\n\nexport interface SettingsState {\n serverHost: string\n serverPort: number\n requestTimeout: number\n wsAutoReconnect: boolean\n wsReconnectInterval: number\n theme: ThemeMode\n colorPalette: ColorPalette\n tableDensity: TableDensity\n}\n\nconst STORAGE_KEY = 'mint-settings'\nconst LEGACY_STORAGE_KEY = 'mld-settings'\n\nfunction getDefaultServerHost(): string {\n if (typeof window !== 'undefined' && window.location.hostname !== 'localhost') {\n return window.location.hostname\n }\n return 'localhost'\n}\n\nfunction getDefaultServerPort(): number {\n if (typeof window !== 'undefined') {\n if (window.location.port) {\n return parseInt(window.location.port, 10)\n }\n // Standard ports: 443 for HTTPS, 80 for HTTP (browser omits from location.port)\n return window.location.protocol === 'https:' ? 443 : 80\n }\n return 8000\n}\n\nconst defaultSettings: SettingsState = {\n serverHost: getDefaultServerHost(),\n serverPort: getDefaultServerPort(),\n requestTimeout: 120000,\n wsAutoReconnect: true,\n wsReconnectInterval: 5000,\n theme: 'system',\n colorPalette: 'default',\n tableDensity: 'normal',\n}\n\nexport const colorPalettes: Record<ColorPalette, { name: string; hues: [number, number] }> = {\n default: { name: 'Default (Cyan-Pink)', hues: [180, 320] },\n colorblind: { name: 'Colorblind-friendly', hues: [45, 260] },\n viridis: { name: 'Viridis', hues: [280, 80] },\n pastel: { name: 'Pastel', hues: [200, 340] },\n}\n\nfunction loadSettings(): SettingsState {\n try {\n const stored = localStorage.getItem(STORAGE_KEY)\n if (stored) {\n const parsed = JSON.parse(stored)\n return { ...defaultSettings, ...parsed }\n }\n\n const legacyStored = localStorage.getItem(LEGACY_STORAGE_KEY)\n if (legacyStored) {\n const parsed = JSON.parse(legacyStored)\n const migrated = { ...defaultSettings, ...parsed }\n localStorage.setItem(STORAGE_KEY, JSON.stringify(migrated))\n localStorage.removeItem(LEGACY_STORAGE_KEY)\n return migrated\n }\n } catch (e) {\n console.warn('Failed to load settings from localStorage:', e)\n }\n return { ...defaultSettings }\n}\n\nfunction saveSettings(settings: SettingsState): void {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(settings))\n } catch (e) {\n console.warn('Failed to save settings to localStorage:', e)\n }\n}\n\nfunction getSystemPrefersDark(): boolean {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false\n return window.matchMedia('(prefers-color-scheme: dark)').matches\n}\n\nexport const useSettingsStore = defineStore('mint-settings', () => {\n // State\n const serverHost = ref(defaultSettings.serverHost)\n const serverPort = ref(defaultSettings.serverPort)\n const requestTimeout = ref(defaultSettings.requestTimeout)\n const wsAutoReconnect = ref(defaultSettings.wsAutoReconnect)\n const wsReconnectInterval = ref(defaultSettings.wsReconnectInterval)\n const theme = ref<ThemeMode>(defaultSettings.theme)\n const systemPrefersDark = ref(getSystemPrefersDark())\n const colorPalette = ref<ColorPalette>(defaultSettings.colorPalette)\n const tableDensity = ref<TableDensity>(defaultSettings.tableDensity)\n\n // API prefix - can be configured via env variable VITE_API_PREFIX\n const apiPrefix: string = (import.meta.env?.VITE_API_PREFIX as string | undefined) ?? '/api'\n\n function _isSameOrigin(): { same: boolean; currentHost: string; currentPort: string } {\n const currentHost = typeof window !== 'undefined' ? window.location.hostname : 'localhost'\n const currentPort = typeof window !== 'undefined' ? window.location.port : '8000'\n const effectivePort = currentPort || (window.location.protocol === 'https:' ? '443' : '80')\n const same =\n (serverHost.value === currentHost && String(serverPort.value) === effectivePort) ||\n (serverHost.value === 'localhost' && currentHost !== 'localhost')\n return { same, currentHost, currentPort }\n }\n\n function getApiBaseUrl(): string {\n const { same } = _isSameOrigin()\n if (same) return apiPrefix\n return `http://${serverHost.value}:${serverPort.value}${apiPrefix}`\n }\n\n function getWsBaseUrl(): string {\n const { same, currentHost, currentPort } = _isSameOrigin()\n const protocol = typeof window !== 'undefined' && window.location.protocol === 'https:' ? 'wss:' : 'ws:'\n if (same) return `${protocol}//${currentHost}${currentPort ? ':' + currentPort : ''}${apiPrefix}`\n return `ws://${serverHost.value}:${serverPort.value}${apiPrefix}`\n }\n\n let _initialized = false\n\n function initialize() {\n if (_initialized) return\n _initialized = true\n\n const loaded = loadSettings()\n serverHost.value = loaded.serverHost\n serverPort.value = loaded.serverPort\n requestTimeout.value = loaded.requestTimeout\n wsAutoReconnect.value = loaded.wsAutoReconnect\n wsReconnectInterval.value = loaded.wsReconnectInterval\n theme.value = loaded.theme\n colorPalette.value = loaded.colorPalette\n tableDensity.value = loaded.tableDensity\n\n applyTheme()\n }\n\n function applyTheme() {\n if (typeof document === 'undefined') return\n const dark = theme.value === 'system' ? systemPrefersDark.value : theme.value === 'dark'\n document.documentElement.classList.toggle('dark', dark)\n }\n\n watch(theme, () => {\n applyTheme()\n persistSettings()\n })\n\n if (typeof window !== 'undefined' && typeof window.matchMedia === 'function') {\n window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (event) => {\n systemPrefersDark.value = event.matches\n if (theme.value === 'system') {\n applyTheme()\n }\n })\n }\n\n function persistSettings() {\n saveSettings({\n serverHost: serverHost.value,\n serverPort: serverPort.value,\n requestTimeout: requestTimeout.value,\n wsAutoReconnect: wsAutoReconnect.value,\n wsReconnectInterval: wsReconnectInterval.value,\n theme: theme.value,\n colorPalette: colorPalette.value,\n tableDensity: tableDensity.value,\n })\n }\n\n watch([serverHost, serverPort, requestTimeout, wsAutoReconnect, wsReconnectInterval, colorPalette, tableDensity], () => {\n persistSettings()\n })\n\n function resetToDefaults() {\n serverHost.value = defaultSettings.serverHost\n serverPort.value = defaultSettings.serverPort\n requestTimeout.value = defaultSettings.requestTimeout\n wsAutoReconnect.value = defaultSettings.wsAutoReconnect\n wsReconnectInterval.value = defaultSettings.wsReconnectInterval\n theme.value = defaultSettings.theme\n colorPalette.value = defaultSettings.colorPalette\n tableDensity.value = defaultSettings.tableDensity\n }\n\n function getPaletteHues(): [number, number] {\n return colorPalettes[colorPalette.value].hues\n }\n\n function isDark(): boolean {\n return theme.value === 'system' ? systemPrefersDark.value : theme.value === 'dark'\n }\n\n return {\n serverHost,\n serverPort,\n requestTimeout,\n wsAutoReconnect,\n wsReconnectInterval,\n theme,\n systemPrefersDark,\n colorPalette,\n tableDensity,\n initialize,\n applyTheme,\n persistSettings,\n resetToDefaults,\n getPaletteHues,\n isDark,\n getApiBaseUrl,\n getWsBaseUrl,\n }\n})\n","export const ADMIN_ROLE = 'admin'\n\nexport const ADMIN_PANEL_PERMISSIONS = [\n 'users.view',\n 'users.invite',\n 'users.manage',\n 'platform.configure',\n 'platform.view_logs',\n 'plugins.configure',\n 'plugins.install',\n] as const\n\nexport type AccessAudience = 'all' | 'admin' | 'user'\nexport type AccessAudienceInput = AccessAudience | readonly AccessAudience[]\n\nexport interface RoleInfo {\n id?: number\n slug: string\n name?: string\n color?: string\n permissions?: readonly string[] | null\n project_scope?: 'all' | 'assigned' | string\n plugin_access?: readonly string[] | 'all' | null\n}\n\nexport interface PermissionUser {\n role?: string | null\n role_obj?: RoleInfo | null\n roleObj?: RoleInfo | null\n permissions?: readonly string[] | null\n}\n\nexport interface AccessPolicy {\n audience?: AccessAudienceInput\n visibleFor?: AccessAudienceInput\n requiresAuth?: boolean\n requiresAdmin?: boolean\n permissions?: readonly string[]\n anyPermissions?: readonly string[]\n plugin?: string\n}\n\nexport interface AccessControlled {\n access?: AccessPolicy\n visibleFor?: AccessAudienceInput\n requiresAdmin?: boolean\n permissions?: readonly string[]\n anyPermissions?: readonly string[]\n}\n\nexport function getRoleInfo(user: PermissionUser | null | undefined): RoleInfo | null {\n return user?.roleObj ?? user?.role_obj ?? null\n}\n\nexport function isAdminRole(role: string | null | undefined): boolean {\n return role === ADMIN_ROLE\n}\n\nexport function isAdminUser(user: PermissionUser | null | undefined): boolean {\n return isAdminRole(user?.role) || getRoleInfo(user)?.slug === ADMIN_ROLE\n}\n\nexport function getAccessAudience(user: PermissionUser | null | undefined): Exclude<AccessAudience, 'all'> {\n return isAdminUser(user) ? 'admin' : 'user'\n}\n\nexport function getUserPermissions(user: PermissionUser | null | undefined): string[] {\n const rolePermissions = getRoleInfo(user)?.permissions\n if (rolePermissions?.length) return [...rolePermissions]\n return [...(user?.permissions ?? [])]\n}\n\nexport function hasAllPermissions(\n user: PermissionUser | null | undefined,\n permissions: readonly string[] = [],\n): boolean {\n if (permissions.length === 0) return true\n if (isAdminUser(user)) return true\n const granted = new Set(getUserPermissions(user))\n return permissions.every(permission => granted.has(permission))\n}\n\nexport function hasAnyPermission(\n user: PermissionUser | null | undefined,\n permissions: readonly string[] = [],\n): boolean {\n if (permissions.length === 0) return true\n if (isAdminUser(user)) return true\n const granted = new Set(getUserPermissions(user))\n return permissions.some(permission => granted.has(permission))\n}\n\nexport function canAccessAdmin(user: PermissionUser | null | undefined): boolean {\n return isAdminUser(user) || hasAnyPermission(user, ADMIN_PANEL_PERMISSIONS)\n}\n\nexport function canAccessPlugin(\n user: PermissionUser | null | undefined,\n pluginName: string | null | undefined,\n): boolean {\n if (!pluginName) return true\n const access = getRoleInfo(user)?.plugin_access\n if (!access || access === 'all') return true\n return Array.isArray(access) && access.includes(pluginName)\n}\n\nexport function normalizeAccessPolicy(rule: AccessControlled | AccessPolicy | undefined): AccessPolicy {\n if (!rule) return {}\n const { access: nested, ...direct } = rule as AccessPolicy & { access?: AccessPolicy }\n return {\n ...(nested ?? {}),\n ...direct,\n ...('visibleFor' in rule && rule.visibleFor !== undefined ? { visibleFor: rule.visibleFor } : {}),\n ...('requiresAdmin' in rule && rule.requiresAdmin !== undefined ? { requiresAdmin: rule.requiresAdmin } : {}),\n ...('permissions' in rule && rule.permissions !== undefined ? { permissions: rule.permissions } : {}),\n ...('anyPermissions' in rule && rule.anyPermissions !== undefined ? { anyPermissions: rule.anyPermissions } : {}),\n }\n}\n\nexport function canAccessByPolicy(\n user: PermissionUser | null | undefined,\n rule: AccessControlled | AccessPolicy | undefined,\n isAuthenticated = user !== null && user !== undefined,\n): boolean {\n const policy = normalizeAccessPolicy(rule)\n if (policy.requiresAuth && !isAuthenticated) return false\n if (policy.requiresAdmin && !isAdminUser(user)) return false\n if (!audienceAllowsUser(policy.visibleFor ?? policy.audience, user)) return false\n if (!hasAllPermissions(user, policy.permissions)) return false\n if (!hasAnyPermission(user, policy.anyPermissions)) return false\n if (policy.plugin && !canAccessPlugin(user, policy.plugin)) return false\n return true\n}\n\nfunction audienceAllowsUser(\n audience: AccessAudienceInput | undefined,\n user: PermissionUser | null | undefined,\n): boolean {\n if (audience === undefined) return true\n const allowed = Array.isArray(audience) ? audience : [audience]\n if (allowed.includes('all')) return true\n return allowed.includes(getAccessAudience(user))\n}\n","import { defineStore } from 'pinia'\nimport { ref, computed } from 'vue'\nimport type { AuthConfig, UserInfo } from '../types'\nimport {\n canAccessAdmin as canUserAccessAdmin,\n canAccessPlugin as canUserAccessPlugin,\n getAccessAudience,\n getUserPermissions,\n hasAllPermissions,\n hasAnyPermission,\n isAdminUser,\n} from '../permissions'\n\nconst AUTH_TOKEN_KEY = 'mint-auth-token'\nconst AUTH_EXPIRES_KEY = 'mint-auth-expires'\n\nfunction getLocalStorage(): Storage | null {\n try {\n const storage = globalThis.localStorage\n return typeof storage?.getItem === 'function' ? storage : null\n } catch {\n return null\n }\n}\n\nfunction readStoredItem(key: string): string | null {\n try {\n return getLocalStorage()?.getItem(key) ?? null\n } catch {\n return null\n }\n}\n\nfunction writeStoredItem(key: string, value: string): void {\n try {\n getLocalStorage()?.setItem(key, value)\n } catch {\n // Keep auth usable in-memory when browser storage is blocked.\n }\n}\n\nfunction removeStoredItem(key: string): void {\n try {\n getLocalStorage()?.removeItem(key)\n } catch {\n // Keep auth cleanup usable in-memory when browser storage is blocked.\n }\n}\n\nexport const useAuthStore = defineStore('mint-auth', () => {\n // State\n const token = ref<string | null>(null)\n const tokenExpires = ref<Date | null>(null)\n const username = ref<string | null>(null)\n const userInfo = ref<UserInfo | null>(null)\n const authConfig = ref<AuthConfig>({\n authRequired: true,\n passkeyEnabled: false,\n passkeyRegistered: false,\n registrationEnabled: false,\n databaseMode: 'none',\n experimentVisibilityMode: 'open',\n })\n const isInitialized = ref(false)\n const isLoading = ref(false)\n const error = ref<string | null>(null)\n\n // Computed\n const isAuthenticated = computed(() => {\n if (!authConfig.value.authRequired) {\n return true\n }\n if (!token.value) {\n return false\n }\n if (tokenExpires.value && tokenExpires.value < new Date()) {\n return false\n }\n return true\n })\n\n const needsAuth = computed(() => {\n return authConfig.value.authRequired && !isAuthenticated.value\n })\n\n const isAdmin = computed(() => isAdminUser(userInfo.value))\n const userType = computed(() => getAccessAudience(userInfo.value))\n const permissions = computed(() => getUserPermissions(userInfo.value))\n const canAccessAdmin = computed(() => canUserAccessAdmin(userInfo.value))\n\n const canRegister = computed(() => {\n return authConfig.value.registrationEnabled\n })\n\n // Actions\n function initialize() {\n const storedToken = readStoredItem(AUTH_TOKEN_KEY)\n const storedExpires = readStoredItem(AUTH_EXPIRES_KEY)\n\n if (storedToken) {\n token.value = storedToken\n\n if (storedExpires) {\n const expires = new Date(storedExpires)\n if (expires > new Date()) {\n tokenExpires.value = expires\n } else {\n clearToken()\n }\n }\n }\n\n isInitialized.value = true\n }\n\n function setToken(accessToken: string, expiresIn: number) {\n token.value = accessToken\n const expires = new Date(Date.now() + expiresIn * 1000)\n tokenExpires.value = expires\n\n writeStoredItem(AUTH_TOKEN_KEY, accessToken)\n writeStoredItem(AUTH_EXPIRES_KEY, expires.toISOString())\n }\n\n function clearToken() {\n token.value = null\n tokenExpires.value = null\n username.value = null\n userInfo.value = null\n\n removeStoredItem(AUTH_TOKEN_KEY)\n removeStoredItem(AUTH_EXPIRES_KEY)\n }\n\n function setUserInfo(info: UserInfo) {\n userInfo.value = info\n username.value = info.username\n }\n\n function hasPermission(...perms: string[]): boolean {\n return hasAllPermissions(userInfo.value, perms)\n }\n\n function hasAnyPermissionForUser(...perms: string[]): boolean {\n return hasAnyPermission(userInfo.value, perms)\n }\n\n function canAccessPlugin(pluginName: string): boolean {\n return canUserAccessPlugin(userInfo.value, pluginName)\n }\n\n function setAuthConfig(config: AuthConfig) {\n authConfig.value = config\n }\n\n function setUsername(name: string) {\n username.value = name\n }\n\n function setError(message: string | null) {\n error.value = message\n }\n\n function setLoading(loading: boolean) {\n isLoading.value = loading\n }\n\n function logout() {\n clearToken()\n }\n\n return {\n // State\n token,\n tokenExpires,\n username,\n userInfo,\n authConfig,\n isInitialized,\n isLoading,\n error,\n\n // Computed\n isAuthenticated,\n needsAuth,\n isAdmin,\n userType,\n permissions,\n canAccessAdmin,\n canRegister,\n\n // Actions\n initialize,\n setToken,\n clearToken,\n setAuthConfig,\n setUsername,\n setUserInfo,\n setError,\n setLoading,\n hasPermission,\n hasAnyPermission: hasAnyPermissionForUser,\n canAccessPlugin,\n logout,\n }\n})\n"],"mappings":"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"}
|