@pgcorp/ui-kit 0.8.1 → 0.10.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 +332 -9
- package/docs/getting-started.md +135 -0
- package/docs/public-api.md +53 -4
- package/docs/rich-document.md +216 -0
- package/docs/scroll-viewport.md +167 -0
- package/docs/theming.md +17 -0
- package/package.json +108 -50
- package/src/components/layout/SAppShell.vue +3 -1
- package/src/components/layout/SDockRegion.vue +5 -3
- package/src/components/layout/SResizeHandle.vue +45 -5
- package/src/components/layout/SWorkbenchPanelShell.vue +4 -2
- package/src/components/shared/_internal/SDialogSurface.css +23 -0
- package/src/components/shared/_internal/SDialogSurface.vue +9 -2
- package/src/components/shared/_internal/SToastItem.css +4 -0
- package/src/components/shared/_internal/SToastItem.vue +44 -8
- package/src/components/shared/_internal/STreeNode.vue +24 -11
- package/src/components/shared/_internal/dialogSurface.ts +7 -1
- package/src/components/shared/complex/SKanbanLane.vue +4 -2
- package/src/components/shared/containers/SCloudChromeRow.vue +3 -2
- package/src/components/shared/containers/SDrawer.css +11 -2
- package/src/components/shared/containers/SDrawer.vue +48 -9
- package/src/components/shared/containers/SModal.vue +5 -2
- package/src/components/shared/containers/SPanel.vue +4 -2
- package/src/components/shared/containers/SPopover.vue +7 -3
- package/src/components/shared/containers/SUiKitLocaleProvider.vue +57 -0
- package/src/components/shared/controls/SButton.css +14 -10
- package/src/components/shared/controls/SCombobox.vue +8 -6
- package/src/components/shared/controls/SComboboxTrigger.css +7 -4
- package/src/components/shared/controls/SComboboxTrigger.vue +5 -2
- package/src/components/shared/controls/SContextToggleButton.vue +6 -4
- package/src/components/shared/controls/SDragHandle.vue +8 -4
- package/src/components/shared/controls/SDropdownMenu.vue +7 -4
- package/src/components/shared/controls/SInputText.css +5 -4
- package/src/components/shared/controls/SListbox.vue +4 -2
- package/src/components/shared/controls/SSelect.vue +4 -2
- package/src/components/shared/controls/STextarea.ts +13 -0
- package/src/components/shared/controls/STextarea.vue +391 -37
- package/src/components/shared/controls/_internal/SInlineTokenSurface.vue +4 -2
- package/src/components/shared/data-display/SChart.vue +7 -5
- package/src/components/shared/data-display/SChip.vue +3 -1
- package/src/components/shared/data-display/SCodeBlock.css +4 -0
- package/src/components/shared/data-display/SCodeBlock.vue +46 -22
- package/src/components/shared/data-display/SCodeEditor.css +17 -0
- package/src/components/shared/data-display/SCodeEditor.vue +173 -8
- package/src/components/shared/data-display/SCodeSearchPanel.vue +48 -33
- package/src/components/shared/data-display/SCodeWorkspaceEditor.css +13 -0
- package/src/components/shared/data-display/SCodeWorkspaceEditor.vue +355 -0
- package/src/components/shared/data-display/SControlledToastViewport.css +89 -0
- package/src/components/shared/data-display/SControlledToastViewport.vue +205 -0
- package/src/components/shared/data-display/SCopyField.vue +12 -10
- package/src/components/shared/data-display/SDiffViewer.css +122 -0
- package/src/components/shared/data-display/SDiffViewer.vue +499 -0
- package/src/components/shared/data-display/SDocBlock.vue +99 -71
- package/src/components/shared/data-display/SExpandableText.vue +10 -8
- package/src/components/shared/data-display/SJsonTree.vue +4 -2
- package/src/components/shared/data-display/SLinkedSystemsList.vue +4 -2
- package/src/components/shared/data-display/SMessage.vue +6 -6
- package/src/components/shared/data-display/SRichDocument.css +8 -0
- package/src/components/shared/data-display/SRichDocument.vue +65 -0
- package/src/components/shared/data-display/SScrollViewport.css +53 -0
- package/src/components/shared/data-display/SScrollViewport.vue +470 -0
- package/src/components/shared/data-display/STable.vue +38 -19
- package/src/components/shared/data-display/SToastContainer.vue +22 -17
- package/src/components/shared/data-display/SVirtualList.ts +33 -0
- package/src/components/shared/data-display/SVirtualList.vue +321 -36
- package/src/components/shared/data-display/_internal/SCodeTokenLine.css +72 -0
- package/src/components/shared/data-display/_internal/SCodeTokenLine.vue +79 -0
- package/src/components/shared/data-display/_internal/SRichBlockNode.css +85 -0
- package/src/components/shared/data-display/_internal/SRichBlockNode.vue +282 -0
- package/src/components/shared/data-display/_internal/SRichInlineNode.css +6 -0
- package/src/components/shared/data-display/_internal/SRichInlineNode.vue +76 -0
- package/src/components/shared/data-display/_internal/SRichMath.css +42 -0
- package/src/components/shared/data-display/_internal/SRichMath.vue +102 -0
- package/src/components/shared/data-display/codeEditor.ts +200 -0
- package/src/components/shared/data-display/codeWorkspace.ts +233 -0
- package/src/components/shared/data-display/diff.ts +541 -0
- package/src/components/shared/data-display/richDocument.ts +876 -0
- package/src/components/shared/data-display/scrollViewport.ts +47 -0
- package/src/components/shared/data-display/toast.ts +194 -0
- package/src/components/shared/database/SDataGrid.vue +7 -4
- package/src/components/shared/database/SSqlEditor.vue +39 -39
- package/src/components/shared/feedback/SAsyncState.vue +3 -1
- package/src/components/shared/graph/SGraphViewport.vue +11 -9
- package/src/components/shared/navigation/SBreadcrumbs.vue +8 -5
- package/src/components/shared/navigation/SCatalogNavigator.vue +26 -20
- package/src/components/shared/navigation/STabList.vue +10 -6
- package/src/components/shared/navigation/STabs.vue +15 -11
- package/src/components/shared/navigation/SWizardSteps.vue +4 -2
- package/src/components/shared/persona/SPersonaProjectPicker.vue +31 -27
- package/src/components/shared/persona/SPersonaRunWorkbench.vue +39 -33
- package/src/components/shared/persona/_internal/SPersonaRunConsole.vue +8 -6
- package/src/components/shared/persona/_internal/labels.ts +66 -57
- package/src/components/shared/persona/presentation.ts +140 -110
- package/src/composables/useFloatingPosition.ts +39 -2
- package/src/composables/useUiKitLocale.ts +17 -0
- package/src/internal/codeHighlight.ts +110 -0
- package/src/internal/diffEngine.ts +464 -0
- package/src/internal/diffRuntime.ts +189 -0
- package/src/internal/feedbackPresentation.ts +33 -14
- package/src/internal/localeContext.ts +22 -0
- package/src/internal/ownedAttrs.ts +8 -0
- package/src/internal/runtimeContract.ts +11 -9
- package/src/internal/semanticSizing.ts +13 -0
- package/src/locales/catalog.ts +634 -0
- package/src/locales/locale.ts +472 -0
- package/src/locales/messages.generated.ts +1826 -0
- package/src/locales/messages.ts +150 -0
- package/src/stores/useNotifierStore.ts +61 -44
- package/src/styles/tokens.css +23 -2
- package/src/theme.ts +59 -32
- package/src/workers/diff.worker.ts +73 -0
- package/src/workers/diffProtocol.ts +155 -0
- package/src/components/shared/data-display/SToastContainer.css +0 -53
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { validateExactString } from '../../../internal/runtimeContract'
|
|
2
|
+
import type { UiKitLocale, UiKitStaticMessageKey, UiKitTranslate } from '../../../locales/locale'
|
|
3
|
+
import { createUiKitTranslate, UI_KIT_MESSAGES } from '../../../locales/messages'
|
|
4
|
+
|
|
5
|
+
const defaultTranslate = createUiKitTranslate(() => UI_KIT_MESSAGES.ru)
|
|
6
|
+
type PersonaStaticMessageKey = Extract<UiKitStaticMessageKey, `persona.${string}`>
|
|
2
7
|
|
|
3
8
|
export type PersonaStatusSeverity = 'secondary' | 'success' | 'info' | 'warn' | 'danger'
|
|
4
9
|
|
|
@@ -31,18 +36,23 @@ type PersonaDiagnosticOverviewItem = {
|
|
|
31
36
|
|
|
32
37
|
export function personaDiagnosticPresentation(
|
|
33
38
|
input: PersonaDiagnosticPresentationInput,
|
|
39
|
+
t: UiKitTranslate = defaultTranslate,
|
|
34
40
|
): PersonaDiagnosticPresentation {
|
|
35
41
|
const isError = input.severity === 'error' || input.severity === 'blocking'
|
|
36
|
-
const statusLabel = isError
|
|
42
|
+
const statusLabel = isError
|
|
43
|
+
? t('persona.diagnosticError')
|
|
44
|
+
: input.severity === 'warning'
|
|
45
|
+
? t('persona.diagnosticWarning')
|
|
46
|
+
: t('persona.diagnosticInformation')
|
|
37
47
|
const statusSeverity: PersonaStatusSeverity = isError ? 'danger' : input.severity === 'warning' ? 'warn' : 'info'
|
|
38
48
|
|
|
39
49
|
if (input.context === 'native_scenarios') {
|
|
40
50
|
return {
|
|
41
51
|
category: 'scenario_import',
|
|
42
|
-
title: '
|
|
43
|
-
ownerLabel: '
|
|
44
|
-
impactLabel: '
|
|
45
|
-
remediation: '
|
|
52
|
+
title: t('persona.scenarioNotImported'),
|
|
53
|
+
ownerLabel: t('persona.fixInTestProject'),
|
|
54
|
+
impactLabel: t('persona.scenarioUnavailable'),
|
|
55
|
+
remediation: t('persona.restoreScenarioImport'),
|
|
46
56
|
statusLabel,
|
|
47
57
|
statusSeverity,
|
|
48
58
|
}
|
|
@@ -50,23 +60,23 @@ export function personaDiagnosticPresentation(
|
|
|
50
60
|
if (input.context === 'components') {
|
|
51
61
|
return {
|
|
52
62
|
category: 'component_analysis',
|
|
53
|
-
title: '
|
|
54
|
-
ownerLabel: '
|
|
55
|
-
impactLabel: '
|
|
56
|
-
remediation: '
|
|
63
|
+
title: t('persona.componentNotAnalyzed'),
|
|
64
|
+
ownerLabel: t('persona.fixInTestProject'),
|
|
65
|
+
impactLabel: t('persona.relatedStructureIncomplete'),
|
|
66
|
+
remediation: t('persona.restoreComponentImport'),
|
|
57
67
|
statusLabel,
|
|
58
68
|
statusSeverity,
|
|
59
69
|
}
|
|
60
70
|
}
|
|
61
71
|
if (input.context === 'support_quality') {
|
|
62
72
|
const remediation = input.reason === 'page_support_import_non_maintained'
|
|
63
|
-
? '
|
|
64
|
-
: '
|
|
73
|
+
? t('persona.movePageObject')
|
|
74
|
+
: t('persona.giveStableName')
|
|
65
75
|
return {
|
|
66
76
|
category: 'support_quality',
|
|
67
|
-
title: '
|
|
68
|
-
ownerLabel: '
|
|
69
|
-
impactLabel: '
|
|
77
|
+
title: t('persona.pageObjectQuality'),
|
|
78
|
+
ownerLabel: t('persona.fixInTestProject'),
|
|
79
|
+
impactLabel: t('persona.doesNotBlockScenarios'),
|
|
70
80
|
remediation,
|
|
71
81
|
statusLabel,
|
|
72
82
|
statusSeverity,
|
|
@@ -74,10 +84,10 @@ export function personaDiagnosticPresentation(
|
|
|
74
84
|
}
|
|
75
85
|
return {
|
|
76
86
|
category: 'unclassified',
|
|
77
|
-
title: '
|
|
78
|
-
ownerLabel: '
|
|
79
|
-
impactLabel: '
|
|
80
|
-
remediation: '
|
|
87
|
+
title: t('persona.unclassifiedPersonaMessage'),
|
|
88
|
+
ownerLabel: t('persona.personaClassificationRequired'),
|
|
89
|
+
impactLabel: t('persona.impactUnspecified'),
|
|
90
|
+
remediation: t('persona.personaMustClassify'),
|
|
81
91
|
statusLabel,
|
|
82
92
|
statusSeverity,
|
|
83
93
|
}
|
|
@@ -87,6 +97,7 @@ export function personaDiagnosticOverviewMessage(
|
|
|
87
97
|
availableTests: number,
|
|
88
98
|
unavailableTests: number,
|
|
89
99
|
diagnostics: readonly PersonaDiagnosticOverviewItem[],
|
|
100
|
+
t: UiKitTranslate = defaultTranslate,
|
|
90
101
|
): string {
|
|
91
102
|
const count = (category: PersonaDiagnosticOverviewItem['category']) => (
|
|
92
103
|
diagnostics.filter((diagnostic) => diagnostic.category === category).length
|
|
@@ -96,12 +107,12 @@ export function personaDiagnosticOverviewMessage(
|
|
|
96
107
|
const componentAnalysis = count('component_analysis')
|
|
97
108
|
const supportQuality = count('support_quality')
|
|
98
109
|
const parts = [
|
|
99
|
-
|
|
100
|
-
scenarioImports > 0 ?
|
|
110
|
+
t('persona.overviewAvailable', { available: availableTests, total: totalTests }),
|
|
111
|
+
scenarioImports > 0 ? t('persona.overviewScenarioImports', { count: scenarioImports }) : null,
|
|
101
112
|
componentAnalysis > 0
|
|
102
|
-
?
|
|
113
|
+
? t('persona.overviewComponentAnalysis', { count: componentAnalysis })
|
|
103
114
|
: null,
|
|
104
|
-
supportQuality > 0 ?
|
|
115
|
+
supportQuality > 0 ? t('persona.overviewSupportQuality', { count: supportQuality }) : null,
|
|
105
116
|
]
|
|
106
117
|
return parts.filter((part): part is string => Boolean(part)).join(' ')
|
|
107
118
|
}
|
|
@@ -126,24 +137,24 @@ const PERSONA_RUN_STATUSES = [
|
|
|
126
137
|
] as const
|
|
127
138
|
type PersonaRunStatus = typeof PERSONA_RUN_STATUSES[number]
|
|
128
139
|
const PERSONA_RUN_STATUS_PRESENTATIONS = {
|
|
129
|
-
cancelled: {
|
|
130
|
-
cancelling: {
|
|
131
|
-
completed: {
|
|
132
|
-
degraded: {
|
|
133
|
-
error: {
|
|
134
|
-
failed: {
|
|
135
|
-
passed: {
|
|
136
|
-
paused: {
|
|
137
|
-
queued: {
|
|
138
|
-
running: {
|
|
139
|
-
skipped: {
|
|
140
|
-
timed_out: {
|
|
141
|
-
timeout: {
|
|
142
|
-
info: {
|
|
143
|
-
warn: {
|
|
144
|
-
warning: {
|
|
140
|
+
cancelled: { labelKey: 'persona.statusCancelled', severity: 'secondary' },
|
|
141
|
+
cancelling: { labelKey: 'persona.statusCancelling', severity: 'warn' },
|
|
142
|
+
completed: { labelKey: 'persona.statusCompleted', severity: 'success' },
|
|
143
|
+
degraded: { labelKey: 'persona.statusDegraded', severity: 'warn' },
|
|
144
|
+
error: { labelKey: 'persona.statusError', severity: 'danger' },
|
|
145
|
+
failed: { labelKey: 'persona.statusError', severity: 'danger' },
|
|
146
|
+
passed: { labelKey: 'persona.statusPassed', severity: 'success' },
|
|
147
|
+
paused: { labelKey: 'persona.statusPaused', severity: 'info' },
|
|
148
|
+
queued: { labelKey: 'persona.statusQueued', severity: 'warn' },
|
|
149
|
+
running: { labelKey: 'persona.statusRunning', severity: 'info' },
|
|
150
|
+
skipped: { labelKey: 'persona.statusSkipped', severity: 'warn' },
|
|
151
|
+
timed_out: { labelKey: 'persona.statusTimedOut', severity: 'danger' },
|
|
152
|
+
timeout: { labelKey: 'persona.statusTimedOut', severity: 'danger' },
|
|
153
|
+
info: { labelKey: 'persona.diagnosticInformation', severity: 'info' },
|
|
154
|
+
warn: { labelKey: 'persona.diagnosticWarning', severity: 'warn' },
|
|
155
|
+
warning: { labelKey: 'persona.diagnosticWarning', severity: 'warn' },
|
|
145
156
|
} as const satisfies Record<PersonaRunStatus, {
|
|
146
|
-
|
|
157
|
+
labelKey: PersonaStaticMessageKey
|
|
147
158
|
severity: PersonaStatusSeverity
|
|
148
159
|
}>
|
|
149
160
|
|
|
@@ -151,8 +162,8 @@ function resolvePersonaRunStatus(status: unknown): PersonaRunStatus {
|
|
|
151
162
|
return validateExactString('Persona presentation', 'run status', status, PERSONA_RUN_STATUSES)
|
|
152
163
|
}
|
|
153
164
|
|
|
154
|
-
export function personaRunStatusLabel(status: unknown): string {
|
|
155
|
-
return PERSONA_RUN_STATUS_PRESENTATIONS[resolvePersonaRunStatus(status)].
|
|
165
|
+
export function personaRunStatusLabel(status: unknown, t: UiKitTranslate = defaultTranslate): string {
|
|
166
|
+
return t(PERSONA_RUN_STATUS_PRESENTATIONS[resolvePersonaRunStatus(status)].labelKey)
|
|
156
167
|
}
|
|
157
168
|
|
|
158
169
|
export function personaStatusSeverity(status: unknown): PersonaStatusSeverity {
|
|
@@ -180,26 +191,26 @@ const PERSONA_SEMANTIC_KINDS = [
|
|
|
180
191
|
] as const
|
|
181
192
|
type PersonaSemanticKind = typeof PERSONA_SEMANTIC_KINDS[number]
|
|
182
193
|
const PERSONA_SEMANTIC_KIND_LABELS = {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
} as const satisfies Record<PersonaSemanticKind,
|
|
194
|
+
Action: 'persona.semanticAction',
|
|
195
|
+
CombinedStep: 'persona.semanticCombinedStep',
|
|
196
|
+
Expectation: 'persona.semanticExpectation',
|
|
197
|
+
Fact: 'persona.semanticFact',
|
|
198
|
+
Goal: 'persona.semanticGoal',
|
|
199
|
+
Ops: 'persona.semanticOps',
|
|
200
|
+
Step: 'persona.semanticStep',
|
|
201
|
+
action: 'persona.semanticAction',
|
|
202
|
+
check: 'persona.semanticExpectation',
|
|
203
|
+
combined_step: 'persona.semanticCombinedStep',
|
|
204
|
+
expectation: 'persona.semanticExpectation',
|
|
205
|
+
fact: 'persona.semanticFact',
|
|
206
|
+
get: 'persona.semanticGet',
|
|
207
|
+
goal: 'persona.semanticGoal',
|
|
208
|
+
make: 'persona.semanticAction',
|
|
209
|
+
persona_call: 'persona.semanticPersonaCall',
|
|
210
|
+
step: 'persona.semanticStep',
|
|
211
|
+
} as const satisfies Record<PersonaSemanticKind, PersonaStaticMessageKey>
|
|
201
212
|
|
|
202
|
-
export function personaSemanticKindLabel(kind: unknown): string {
|
|
213
|
+
export function personaSemanticKindLabel(kind: unknown, t: UiKitTranslate = defaultTranslate): string {
|
|
203
214
|
if (kind === null || kind === undefined) return ''
|
|
204
215
|
const resolvedKind = validateExactString(
|
|
205
216
|
'Persona presentation',
|
|
@@ -207,30 +218,33 @@ export function personaSemanticKindLabel(kind: unknown): string {
|
|
|
207
218
|
kind,
|
|
208
219
|
PERSONA_SEMANTIC_KINDS,
|
|
209
220
|
)
|
|
210
|
-
return PERSONA_SEMANTIC_KIND_LABELS[resolvedKind]
|
|
221
|
+
return t(PERSONA_SEMANTIC_KIND_LABELS[resolvedKind])
|
|
211
222
|
}
|
|
212
223
|
|
|
213
|
-
export function personaParameterSummary(
|
|
224
|
+
export function personaParameterSummary(
|
|
225
|
+
parameters: readonly PersonaParameterPresentation[],
|
|
226
|
+
t: UiKitTranslate = defaultTranslate,
|
|
227
|
+
): string {
|
|
214
228
|
return parameters.map((parameter) => {
|
|
215
229
|
const type = parameter.type ? `: ${parameter.type}` : ''
|
|
216
230
|
const hasPrimitiveDefault = typeof parameter.default === 'string'
|
|
217
231
|
|| typeof parameter.default === 'number'
|
|
218
232
|
|| typeof parameter.default === 'boolean'
|
|
219
233
|
const defaultValue = hasPrimitiveDefault ? ` = ${String(parameter.default)}` : ''
|
|
220
|
-
return `${parameter.name}${type}${defaultValue}${parameter.required ?
|
|
234
|
+
return `${parameter.name}${type}${defaultValue}${parameter.required ? ` (${t('persona.requiredParameter')})` : ''}`
|
|
221
235
|
}).join(', ')
|
|
222
236
|
}
|
|
223
237
|
|
|
224
238
|
const PERSONA_SEVERITIES = ['blocker', 'critical', 'minor', 'normal', 'trivial'] as const
|
|
225
239
|
type PersonaSeverity = typeof PERSONA_SEVERITIES[number]
|
|
226
240
|
const PERSONA_SEVERITY_PRESENTATIONS = {
|
|
227
|
-
blocker: {
|
|
228
|
-
critical: {
|
|
229
|
-
minor: {
|
|
230
|
-
normal: {
|
|
231
|
-
trivial: {
|
|
241
|
+
blocker: { labelKey: 'persona.severityBlocker', severity: 'danger' },
|
|
242
|
+
critical: { labelKey: 'persona.severityCritical', severity: 'danger' },
|
|
243
|
+
minor: { labelKey: 'persona.severityMinor', severity: 'secondary' },
|
|
244
|
+
normal: { labelKey: 'persona.severityNormal', severity: 'info' },
|
|
245
|
+
trivial: { labelKey: 'persona.severityTrivial', severity: 'secondary' },
|
|
232
246
|
} as const satisfies Record<PersonaSeverity, {
|
|
233
|
-
|
|
247
|
+
labelKey: PersonaStaticMessageKey
|
|
234
248
|
severity: PersonaStatusSeverity
|
|
235
249
|
}>
|
|
236
250
|
|
|
@@ -238,8 +252,8 @@ function resolvePersonaSeverity(value: unknown): PersonaSeverity {
|
|
|
238
252
|
return validateExactString('Persona presentation', 'severity', value, PERSONA_SEVERITIES)
|
|
239
253
|
}
|
|
240
254
|
|
|
241
|
-
export function personaSeverityLabel(value: unknown): string {
|
|
242
|
-
return PERSONA_SEVERITY_PRESENTATIONS[resolvePersonaSeverity(value)].
|
|
255
|
+
export function personaSeverityLabel(value: unknown, t: UiKitTranslate = defaultTranslate): string {
|
|
256
|
+
return t(PERSONA_SEVERITY_PRESENTATIONS[resolvePersonaSeverity(value)].labelKey)
|
|
243
257
|
}
|
|
244
258
|
|
|
245
259
|
export function personaSeverityBadge(value: unknown): PersonaStatusSeverity {
|
|
@@ -249,20 +263,20 @@ export function personaSeverityBadge(value: unknown): PersonaStatusSeverity {
|
|
|
249
263
|
const PERSONA_REPORT_STATUSES = ['broken', 'failed', 'passed', 'skipped'] as const
|
|
250
264
|
type PersonaReportStatus = typeof PERSONA_REPORT_STATUSES[number]
|
|
251
265
|
const PERSONA_REPORT_STATUS_LABELS = {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
} as const satisfies Record<PersonaReportStatus,
|
|
266
|
+
broken: 'persona.reportBroken',
|
|
267
|
+
failed: 'persona.statusError',
|
|
268
|
+
passed: 'persona.statusPassed',
|
|
269
|
+
skipped: 'persona.statusSkipped',
|
|
270
|
+
} as const satisfies Record<PersonaReportStatus, PersonaStaticMessageKey>
|
|
257
271
|
|
|
258
|
-
export function personaReportStatusLabel(status: unknown): string {
|
|
272
|
+
export function personaReportStatusLabel(status: unknown, t: UiKitTranslate = defaultTranslate): string {
|
|
259
273
|
const resolvedStatus = validateExactString(
|
|
260
274
|
'Persona presentation',
|
|
261
275
|
'report status',
|
|
262
276
|
status,
|
|
263
277
|
PERSONA_REPORT_STATUSES,
|
|
264
278
|
)
|
|
265
|
-
return PERSONA_REPORT_STATUS_LABELS[resolvedStatus]
|
|
279
|
+
return t(PERSONA_REPORT_STATUS_LABELS[resolvedStatus])
|
|
266
280
|
}
|
|
267
281
|
|
|
268
282
|
const PERSONA_LIFECYCLE_STAGES = [
|
|
@@ -273,20 +287,20 @@ const PERSONA_LIFECYCLE_STAGES = [
|
|
|
273
287
|
] as const
|
|
274
288
|
type PersonaLifecycleStage = typeof PERSONA_LIFECYCLE_STAGES[number]
|
|
275
289
|
const PERSONA_LIFECYCLE_STAGE_LABELS = {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
} as const satisfies Record<PersonaLifecycleStage,
|
|
290
|
+
run_after_scenario: 'persona.lifecycleAfterScenario',
|
|
291
|
+
run_after_test: 'persona.lifecycleAfterTest',
|
|
292
|
+
run_before_scenario: 'persona.lifecycleBeforeScenario',
|
|
293
|
+
run_before_test: 'persona.lifecycleBeforeTest',
|
|
294
|
+
} as const satisfies Record<PersonaLifecycleStage, PersonaStaticMessageKey>
|
|
281
295
|
|
|
282
|
-
export function personaLifecycleStageLabel(stage: unknown): string {
|
|
296
|
+
export function personaLifecycleStageLabel(stage: unknown, t: UiKitTranslate = defaultTranslate): string {
|
|
283
297
|
const resolvedStage = validateExactString(
|
|
284
298
|
'Persona presentation',
|
|
285
299
|
'lifecycle stage',
|
|
286
300
|
stage,
|
|
287
301
|
PERSONA_LIFECYCLE_STAGES,
|
|
288
302
|
)
|
|
289
|
-
return PERSONA_LIFECYCLE_STAGE_LABELS[resolvedStage]
|
|
303
|
+
return t(PERSONA_LIFECYCLE_STAGE_LABELS[resolvedStage])
|
|
290
304
|
}
|
|
291
305
|
|
|
292
306
|
const PERSONA_EVENT_TYPES = [
|
|
@@ -308,31 +322,31 @@ const PERSONA_EVENT_TYPES = [
|
|
|
308
322
|
] as const
|
|
309
323
|
type PersonaEventType = typeof PERSONA_EVENT_TYPES[number]
|
|
310
324
|
const PERSONA_EVENT_TYPE_LABELS = {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
} as const satisfies Record<PersonaEventType,
|
|
325
|
+
cancelled: 'persona.eventRunCancelled',
|
|
326
|
+
completed: 'persona.eventRunCompleted',
|
|
327
|
+
failed: 'persona.eventRunFailed',
|
|
328
|
+
log: 'persona.eventRuntimeMessage',
|
|
329
|
+
queued: 'persona.eventRunQueued',
|
|
330
|
+
running: 'persona.eventRunStarted',
|
|
331
|
+
started: 'persona.eventRunStarted',
|
|
332
|
+
timeout: 'persona.eventRunTimedOut',
|
|
333
|
+
'report.runtime_log': 'persona.eventRuntimeMessage',
|
|
334
|
+
'scenario.accepted': 'persona.eventTestQueued',
|
|
335
|
+
'scenario.cancelled': 'persona.eventTestCancelled',
|
|
336
|
+
'scenario.finished': 'persona.eventTestFinished',
|
|
337
|
+
'scenario.started': 'persona.eventTestStarted',
|
|
338
|
+
'worker.started': 'persona.eventWorkerStarted',
|
|
339
|
+
'worker.stopped': 'persona.eventWorkerStopped',
|
|
340
|
+
} as const satisfies Record<PersonaEventType, PersonaStaticMessageKey>
|
|
327
341
|
|
|
328
|
-
export function personaEventTypeLabel(eventType: unknown): string {
|
|
342
|
+
export function personaEventTypeLabel(eventType: unknown, t: UiKitTranslate = defaultTranslate): string {
|
|
329
343
|
const resolvedEventType = validateExactString(
|
|
330
344
|
'Persona presentation',
|
|
331
345
|
'event type',
|
|
332
346
|
eventType,
|
|
333
347
|
PERSONA_EVENT_TYPES,
|
|
334
348
|
)
|
|
335
|
-
return PERSONA_EVENT_TYPE_LABELS[resolvedEventType]
|
|
349
|
+
return t(PERSONA_EVENT_TYPE_LABELS[resolvedEventType])
|
|
336
350
|
}
|
|
337
351
|
|
|
338
352
|
export function personaPayloadMessage(payload: Record<string, unknown> | null): string {
|
|
@@ -344,20 +358,36 @@ export function personaPayloadMessage(payload: Record<string, unknown> | null):
|
|
|
344
358
|
return ''
|
|
345
359
|
}
|
|
346
360
|
|
|
347
|
-
export function personaDistinctMessage(
|
|
361
|
+
export function personaDistinctMessage(
|
|
362
|
+
title: string,
|
|
363
|
+
message: string | null | undefined,
|
|
364
|
+
locale: UiKitLocale = 'ru',
|
|
365
|
+
): string | null {
|
|
348
366
|
const normalizedMessage = message?.trim()
|
|
349
367
|
if (!normalizedMessage) return null
|
|
350
|
-
const comparable = (value: string) => value.toLocaleLowerCase(
|
|
368
|
+
const comparable = (value: string) => value.toLocaleLowerCase(locale).replace(/[.!?]+$/u, '').trim()
|
|
351
369
|
return comparable(title) === comparable(normalizedMessage) ? null : normalizedMessage
|
|
352
370
|
}
|
|
353
371
|
|
|
354
|
-
export function personaDurationLabel(
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
372
|
+
export function personaDurationLabel(
|
|
373
|
+
durationMs: number | null | undefined,
|
|
374
|
+
t: UiKitTranslate = defaultTranslate,
|
|
375
|
+
locale: UiKitLocale = 'ru',
|
|
376
|
+
): string {
|
|
377
|
+
if (durationMs === null || durationMs === undefined) return t('persona.durationUnknown')
|
|
378
|
+
if (durationMs < 1000) return t('persona.durationMilliseconds', { value: durationMs })
|
|
379
|
+
return t('persona.durationSeconds', {
|
|
380
|
+
value: (durationMs / 1000).toLocaleString(locale, { maximumFractionDigits: 2 }),
|
|
381
|
+
})
|
|
358
382
|
}
|
|
359
383
|
|
|
360
|
-
export function personaCountLabel(
|
|
384
|
+
export function personaCountLabel(
|
|
385
|
+
count: number,
|
|
386
|
+
forms: [string, string, string],
|
|
387
|
+
locale: UiKitLocale = 'ru',
|
|
388
|
+
): string {
|
|
389
|
+
if (locale === 'en') return `${count} ${count === 1 ? forms[0] : forms[2]}`
|
|
390
|
+
if (locale === 'zh-CN' || locale === 'zh-TW') return `${count} ${forms[2]}`
|
|
361
391
|
const remainder100 = count % 100
|
|
362
392
|
const remainder10 = count % 10
|
|
363
393
|
const form = remainder100 >= 11 && remainder100 <= 14
|
|
@@ -15,6 +15,7 @@ export interface FloatingPositionOptions {
|
|
|
15
15
|
boundaryPadding?: () => number
|
|
16
16
|
flip?: () => boolean
|
|
17
17
|
shift?: () => boolean
|
|
18
|
+
constrainMainAxis?: () => boolean
|
|
18
19
|
widthMatch?: () => FloatingWidthMatch
|
|
19
20
|
zIndex?: () => string
|
|
20
21
|
scrollStrategy?: 'reposition' | 'close'
|
|
@@ -141,9 +142,44 @@ export function useFloatingPosition(options: FloatingPositionOptions): FloatingP
|
|
|
141
142
|
}
|
|
142
143
|
}
|
|
143
144
|
|
|
145
|
+
let positionedPanelWidth = panelWidth
|
|
146
|
+
let positionedPanelHeight = panelHeight
|
|
147
|
+
const constrainedStyle: Record<string, string> = {}
|
|
148
|
+
if (options.constrainMainAxis?.() ?? false) {
|
|
149
|
+
if (side === 'top' || side === 'bottom') {
|
|
150
|
+
const availableHeight = side === 'top'
|
|
151
|
+
? anchor.top - offset - padding
|
|
152
|
+
: viewportHeight - padding - anchor.bottom - offset
|
|
153
|
+
positionedPanelHeight = Math.min(panelHeight, Math.max(0, Math.floor(availableHeight)))
|
|
154
|
+
constrainedStyle.maxHeight = `${positionedPanelHeight}px`
|
|
155
|
+
constrainedStyle.overflowY = 'auto'
|
|
156
|
+
} else {
|
|
157
|
+
const availableWidth = side === 'left'
|
|
158
|
+
? anchor.left - offset - padding
|
|
159
|
+
: viewportWidth - padding - anchor.right - offset
|
|
160
|
+
positionedPanelWidth = Math.min(panelWidth, Math.max(0, Math.floor(availableWidth)))
|
|
161
|
+
constrainedStyle.maxWidth = `${positionedPanelWidth}px`
|
|
162
|
+
constrainedStyle.overflowX = 'auto'
|
|
163
|
+
}
|
|
164
|
+
coordinates = coordinatesFor(
|
|
165
|
+
side,
|
|
166
|
+
alignment,
|
|
167
|
+
anchor,
|
|
168
|
+
positionedPanelWidth,
|
|
169
|
+
positionedPanelHeight,
|
|
170
|
+
offset,
|
|
171
|
+
)
|
|
172
|
+
}
|
|
173
|
+
|
|
144
174
|
if (options.shift?.() ?? true) {
|
|
145
|
-
coordinates.left = Math.max(
|
|
146
|
-
|
|
175
|
+
coordinates.left = Math.max(
|
|
176
|
+
padding,
|
|
177
|
+
Math.min(coordinates.left, viewportWidth - positionedPanelWidth - padding),
|
|
178
|
+
)
|
|
179
|
+
coordinates.top = Math.max(
|
|
180
|
+
padding,
|
|
181
|
+
Math.min(coordinates.top, viewportHeight - positionedPanelHeight - padding),
|
|
182
|
+
)
|
|
147
183
|
}
|
|
148
184
|
|
|
149
185
|
const placement = `${side}-${alignment}` as FloatingPlacement
|
|
@@ -158,6 +194,7 @@ export function useFloatingPosition(options: FloatingPositionOptions): FloatingP
|
|
|
158
194
|
left: `${resolvedCoordinates.value.left}px`,
|
|
159
195
|
top: `${resolvedCoordinates.value.top}px`,
|
|
160
196
|
zIndex: options.zIndex?.() ?? 'var(--s-layer-root-floating)',
|
|
197
|
+
...constrainedStyle,
|
|
161
198
|
...(widthMatch === 'min' ? { minWidth: `${Math.round(anchor.width)}px` } : {}),
|
|
162
199
|
...(widthMatch === 'exact' ? { width: `${Math.round(anchor.width)}px` } : {}),
|
|
163
200
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { resolveUiKitLocaleContext, type UiKitLocaleContext } from '../internal/localeContext'
|
|
2
|
+
|
|
3
|
+
/** Returns the immutable locale contract scoped to the current Vue application subtree. */
|
|
4
|
+
export function useUiKitLocale(): UiKitLocaleContext {
|
|
5
|
+
return resolveUiKitLocaleContext()
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type {
|
|
9
|
+
UiKitLocale,
|
|
10
|
+
UiKitMessageKey,
|
|
11
|
+
UiKitMessageParameters,
|
|
12
|
+
UiKitMessages,
|
|
13
|
+
UiKitMessagesOverride,
|
|
14
|
+
UiKitTranslate,
|
|
15
|
+
} from '../locales/locale'
|
|
16
|
+
export { UI_KIT_LOCALES } from '../locales/locale'
|
|
17
|
+
export { UI_KIT_MESSAGES, UI_KIT_MESSAGE_KEYS } from '../locales/messages'
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import hljs from 'highlight.js/lib/core'
|
|
2
|
+
import bash from 'highlight.js/lib/languages/bash'
|
|
3
|
+
import dockerfile from 'highlight.js/lib/languages/dockerfile'
|
|
4
|
+
import javascript from 'highlight.js/lib/languages/javascript'
|
|
5
|
+
import makefile from 'highlight.js/lib/languages/makefile'
|
|
6
|
+
import nginx from 'highlight.js/lib/languages/nginx'
|
|
7
|
+
import powershell from 'highlight.js/lib/languages/powershell'
|
|
8
|
+
import python from 'highlight.js/lib/languages/python'
|
|
9
|
+
import typescript from 'highlight.js/lib/languages/typescript'
|
|
10
|
+
import xml from 'highlight.js/lib/languages/xml'
|
|
11
|
+
import yaml from 'highlight.js/lib/languages/yaml'
|
|
12
|
+
|
|
13
|
+
import type { CodeEditorLanguageContract } from './codeEditorContract'
|
|
14
|
+
import { resolveCodeLanguageKey } from './codeLanguageIdentity'
|
|
15
|
+
|
|
16
|
+
export type CodeHighlightToken = string | {
|
|
17
|
+
readonly scope: string | null
|
|
18
|
+
readonly children: readonly CodeHighlightToken[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const HIGHLIGHT_LANGUAGE_ALIASES: Readonly<Record<string, string>> = {
|
|
22
|
+
shell: 'bash',
|
|
23
|
+
text: 'plaintext',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const SAFE_SCOPE_PATTERN = /^[a-z][a-z0-9_.-]*$/u
|
|
27
|
+
|
|
28
|
+
hljs.registerLanguage('bash', bash)
|
|
29
|
+
hljs.registerLanguage('dockerfile', dockerfile)
|
|
30
|
+
hljs.registerLanguage('javascript', javascript)
|
|
31
|
+
hljs.registerLanguage('makefile', makefile)
|
|
32
|
+
hljs.registerLanguage('nginx', nginx)
|
|
33
|
+
hljs.registerLanguage('powershell', powershell)
|
|
34
|
+
hljs.registerLanguage('python', python)
|
|
35
|
+
hljs.registerLanguage('typescript', typescript)
|
|
36
|
+
hljs.registerLanguage('xml', xml)
|
|
37
|
+
hljs.registerLanguage('yaml', yaml)
|
|
38
|
+
|
|
39
|
+
function escapeHighlightedCode(code: string): string {
|
|
40
|
+
return code
|
|
41
|
+
.replace(/&/gu, '&')
|
|
42
|
+
.replace(/</gu, '<')
|
|
43
|
+
.replace(/>/gu, '>')
|
|
44
|
+
.replace(/"/gu, '"')
|
|
45
|
+
.replace(/'/gu, ''')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function resolveHighlightLanguage(language: CodeEditorLanguageContract): string {
|
|
49
|
+
const languageKey = resolveCodeLanguageKey(language)
|
|
50
|
+
return HIGHLIGHT_LANGUAGE_ALIASES[languageKey] ?? languageKey
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
54
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function readTokenNode(value: unknown, coordinate: string): CodeHighlightToken {
|
|
58
|
+
if (typeof value === 'string') return value
|
|
59
|
+
if (!isRecord(value)) throw new TypeError(`codeHighlight: ${coordinate} must be a token node or string.`)
|
|
60
|
+
const unexpected = Object.keys(value).filter((key) => !['scope', 'children', 'language'].includes(key))
|
|
61
|
+
if (unexpected.length > 0) {
|
|
62
|
+
throw new TypeError(`codeHighlight: ${coordinate} contains unknown fields ${unexpected.join(', ')}.`)
|
|
63
|
+
}
|
|
64
|
+
const rawScope = value.scope
|
|
65
|
+
if (rawScope !== undefined && (typeof rawScope !== 'string' || !SAFE_SCOPE_PATTERN.test(rawScope))) {
|
|
66
|
+
throw new TypeError(`codeHighlight: ${coordinate}.scope is invalid.`)
|
|
67
|
+
}
|
|
68
|
+
if (!Array.isArray(value.children)) {
|
|
69
|
+
throw new TypeError(`codeHighlight: ${coordinate}.children must be an array.`)
|
|
70
|
+
}
|
|
71
|
+
return Object.freeze({
|
|
72
|
+
scope: rawScope ?? null,
|
|
73
|
+
children: Object.freeze(value.children.map((child, index) => readTokenNode(child, `${coordinate}.children[${index}]`))),
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function highlightResult(code: string, language: CodeEditorLanguageContract) {
|
|
78
|
+
const resolvedLanguage = resolveHighlightLanguage(language)
|
|
79
|
+
if (!hljs.getLanguage(resolvedLanguage)) return null
|
|
80
|
+
return hljs.highlight(code, { language: resolvedLanguage, ignoreIllegals: true })
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function isCodeHighlightLanguageSupported(language: CodeEditorLanguageContract): boolean {
|
|
84
|
+
return Boolean(hljs.getLanguage(resolveHighlightLanguage(language)))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Returns trusted library markup only for the legacy sanitized-document pipeline. */
|
|
88
|
+
export function highlightCodeToHtml(code: string, language: CodeEditorLanguageContract): string {
|
|
89
|
+
return highlightResult(code, language)?.value ?? escapeHighlightedCode(code)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Converts the Highlight.js emitter boundary into a closed, validated token tree without HTML rendering. */
|
|
93
|
+
export function highlightCodeToTokens(
|
|
94
|
+
code: string,
|
|
95
|
+
language: CodeEditorLanguageContract,
|
|
96
|
+
): readonly CodeHighlightToken[] {
|
|
97
|
+
const result = highlightResult(code, language)
|
|
98
|
+
if (result === null) return Object.freeze([code])
|
|
99
|
+
const root = Reflect.get(result._emitter, 'root')
|
|
100
|
+
const validated = readTokenNode(root, 'root')
|
|
101
|
+
if (typeof validated === 'string') {
|
|
102
|
+
throw new TypeError('codeHighlight: emitter root must be a token node.')
|
|
103
|
+
}
|
|
104
|
+
return validated.children
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function highlightScopeClass(scope: string): string {
|
|
108
|
+
if (!SAFE_SCOPE_PATTERN.test(scope)) throw new TypeError(`codeHighlight: invalid scope ${JSON.stringify(scope)}.`)
|
|
109
|
+
return `hljs-${scope.replace(/\./gu, '_')}`
|
|
110
|
+
}
|