@pgcorp/ui-kit 0.1.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.
Files changed (251) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +157 -0
  3. package/docs/accessibility.md +27 -0
  4. package/docs/getting-started.md +80 -0
  5. package/docs/licensing.md +30 -0
  6. package/docs/public-api.md +39 -0
  7. package/docs/security.md +21 -0
  8. package/docs/theming.md +35 -0
  9. package/docs/versioning.md +23 -0
  10. package/index.ts +3 -0
  11. package/package.json +592 -0
  12. package/src/components/icons/_internal/generatedSputnigUiIconMarkup.ts +971 -0
  13. package/src/components/icons/sputnigUiIcons.ts +530 -0
  14. package/src/components/layout/SAppShell.vue +232 -0
  15. package/src/components/layout/SDockRegion.vue +200 -0
  16. package/src/components/layout/SResizeHandle.vue +251 -0
  17. package/src/components/layout/SSideMenuButton.vue +104 -0
  18. package/src/components/layout/SSideMenuRail.vue +47 -0
  19. package/src/components/layout/SStack.css +25 -0
  20. package/src/components/layout/SStack.vue +57 -0
  21. package/src/components/layout/SStatusBarShell.css +15 -0
  22. package/src/components/layout/SStatusBarShell.vue +26 -0
  23. package/src/components/layout/SWorkbenchLayout.vue +53 -0
  24. package/src/components/layout/SWorkbenchPanelShell.css +161 -0
  25. package/src/components/layout/SWorkbenchPanelShell.vue +286 -0
  26. package/src/components/layout/_internal/SAppBrand.css +45 -0
  27. package/src/components/layout/_internal/SAppBrand.vue +37 -0
  28. package/src/components/layout/_internal/SAppBrandContent.vue +30 -0
  29. package/src/components/layout/types.ts +28 -0
  30. package/src/components/shared/_internal/SDialogSurface.css +127 -0
  31. package/src/components/shared/_internal/SDialogSurface.vue +117 -0
  32. package/src/components/shared/_internal/SDisclosureSummary.css +92 -0
  33. package/src/components/shared/_internal/SDisclosureSummary.vue +121 -0
  34. package/src/components/shared/_internal/SListboxGroup.css +18 -0
  35. package/src/components/shared/_internal/SListboxGroup.vue +24 -0
  36. package/src/components/shared/_internal/SMenuItem.css +112 -0
  37. package/src/components/shared/_internal/SMenuItem.vue +213 -0
  38. package/src/components/shared/_internal/SMenuLayer.css +21 -0
  39. package/src/components/shared/_internal/SMenuLayer.vue +48 -0
  40. package/src/components/shared/_internal/SMenuSeparator.css +5 -0
  41. package/src/components/shared/_internal/SMenuSeparator.vue +13 -0
  42. package/src/components/shared/_internal/SToastItem.css +65 -0
  43. package/src/components/shared/_internal/SToastItem.vue +58 -0
  44. package/src/components/shared/_internal/STreeNode.vue +761 -0
  45. package/src/components/shared/_internal/dialogSurface.ts +105 -0
  46. package/src/components/shared/_internal/focusTrap.ts +40 -0
  47. package/src/components/shared/_internal/listboxContext.ts +16 -0
  48. package/src/components/shared/_internal/markerContract.ts +10 -0
  49. package/src/components/shared/_internal/menuContext.ts +23 -0
  50. package/src/components/shared/_internal/selectionContract.ts +239 -0
  51. package/src/components/shared/_internal/sidebarGroupContext.ts +31 -0
  52. package/src/components/shared/_internal/tabsContext.ts +57 -0
  53. package/src/components/shared/_internal/treeRuntime.ts +203 -0
  54. package/src/components/shared/_internal/useSelectionRoving.ts +162 -0
  55. package/src/components/shared/codeLanguages.ts +168 -0
  56. package/src/components/shared/complex/SKanbanCard.css +8 -0
  57. package/src/components/shared/complex/SKanbanCard.vue +76 -0
  58. package/src/components/shared/complex/SKanbanLane.vue +82 -0
  59. package/src/components/shared/complex/STree.css +24 -0
  60. package/src/components/shared/complex/STree.vue +288 -0
  61. package/src/components/shared/complex/treeAdapter.ts +210 -0
  62. package/src/components/shared/complex/types.ts +102 -0
  63. package/src/components/shared/containers/SCloudChrome.css +103 -0
  64. package/src/components/shared/containers/SCloudChrome.vue +87 -0
  65. package/src/components/shared/containers/SCloudChromeEmpty.css +10 -0
  66. package/src/components/shared/containers/SCloudChromeEmpty.vue +18 -0
  67. package/src/components/shared/containers/SCloudChromeRow.css +78 -0
  68. package/src/components/shared/containers/SCloudChromeRow.vue +108 -0
  69. package/src/components/shared/containers/SCollapsiblePanel.css +57 -0
  70. package/src/components/shared/containers/SCollapsiblePanel.vue +360 -0
  71. package/src/components/shared/containers/SDrawer.css +60 -0
  72. package/src/components/shared/containers/SDrawer.vue +86 -0
  73. package/src/components/shared/containers/SFormGrid.css +26 -0
  74. package/src/components/shared/containers/SFormGrid.vue +46 -0
  75. package/src/components/shared/containers/SFormGridItem.css +18 -0
  76. package/src/components/shared/containers/SFormGridItem.vue +29 -0
  77. package/src/components/shared/containers/SLeftSidebar.vue +74 -0
  78. package/src/components/shared/containers/SModal.css +27 -0
  79. package/src/components/shared/containers/SModal.vue +105 -0
  80. package/src/components/shared/containers/SPageHeader.css +66 -0
  81. package/src/components/shared/containers/SPageHeader.vue +45 -0
  82. package/src/components/shared/containers/SPanel.css +134 -0
  83. package/src/components/shared/containers/SPanel.vue +169 -0
  84. package/src/components/shared/containers/SPopover.css +35 -0
  85. package/src/components/shared/containers/SPopover.vue +551 -0
  86. package/src/components/shared/containers/SSidebarGroup.css +12 -0
  87. package/src/components/shared/containers/SSidebarGroup.vue +443 -0
  88. package/src/components/shared/containers/SSidebarSection.css +41 -0
  89. package/src/components/shared/containers/SSidebarSection.vue +268 -0
  90. package/src/components/shared/containers/disclosureSummary.ts +48 -0
  91. package/src/components/shared/containers/popover.ts +72 -0
  92. package/src/components/shared/containers/sidebar.ts +206 -0
  93. package/src/components/shared/controls/SButton.css +274 -0
  94. package/src/components/shared/controls/SButton.vue +211 -0
  95. package/src/components/shared/controls/SCheckbox.css +65 -0
  96. package/src/components/shared/controls/SCheckbox.vue +130 -0
  97. package/src/components/shared/controls/SCheckboxGroup.vue +134 -0
  98. package/src/components/shared/controls/SChoiceCards.css +29 -0
  99. package/src/components/shared/controls/SChoiceCards.vue +145 -0
  100. package/src/components/shared/controls/SColorSelect.vue +146 -0
  101. package/src/components/shared/controls/SComboboxTrigger.css +35 -0
  102. package/src/components/shared/controls/SComboboxTrigger.vue +122 -0
  103. package/src/components/shared/controls/SContextMenu.css +11 -0
  104. package/src/components/shared/controls/SContextMenu.vue +186 -0
  105. package/src/components/shared/controls/SContextToggleButton.vue +64 -0
  106. package/src/components/shared/controls/SDragHandle.vue +100 -0
  107. package/src/components/shared/controls/SDropdownMenu.css +5 -0
  108. package/src/components/shared/controls/SDropdownMenu.vue +188 -0
  109. package/src/components/shared/controls/SField.css +79 -0
  110. package/src/components/shared/controls/SField.vue +89 -0
  111. package/src/components/shared/controls/SFieldGroup.css +58 -0
  112. package/src/components/shared/controls/SFieldGroup.vue +117 -0
  113. package/src/components/shared/controls/SFieldLabel.css +12 -0
  114. package/src/components/shared/controls/SFieldLabel.vue +51 -0
  115. package/src/components/shared/controls/SInlineTokenEditor.css +61 -0
  116. package/src/components/shared/controls/SInlineTokenEditor.vue +1081 -0
  117. package/src/components/shared/controls/SInputText.css +66 -0
  118. package/src/components/shared/controls/SInputText.vue +321 -0
  119. package/src/components/shared/controls/SInteractiveSurface.css +72 -0
  120. package/src/components/shared/controls/SInteractiveSurface.vue +326 -0
  121. package/src/components/shared/controls/SLink.css +114 -0
  122. package/src/components/shared/controls/SLink.ts +85 -0
  123. package/src/components/shared/controls/SLink.vue +114 -0
  124. package/src/components/shared/controls/SListbox.css +31 -0
  125. package/src/components/shared/controls/SListbox.vue +388 -0
  126. package/src/components/shared/controls/SListboxOption.css +63 -0
  127. package/src/components/shared/controls/SListboxOption.vue +85 -0
  128. package/src/components/shared/controls/SMarker.css +19 -0
  129. package/src/components/shared/controls/SMarker.vue +33 -0
  130. package/src/components/shared/controls/SMenuSurface.css +20 -0
  131. package/src/components/shared/controls/SMenuSurface.vue +333 -0
  132. package/src/components/shared/controls/SSegmentedControl.css +38 -0
  133. package/src/components/shared/controls/SSegmentedControl.vue +134 -0
  134. package/src/components/shared/controls/SSelect.css +12 -0
  135. package/src/components/shared/controls/SSelect.vue +338 -0
  136. package/src/components/shared/controls/STextarea.css +31 -0
  137. package/src/components/shared/controls/STextarea.vue +316 -0
  138. package/src/components/shared/controls/_internal/SInlineTokenSurface.vue +224 -0
  139. package/src/components/shared/controls/menu.ts +178 -0
  140. package/src/components/shared/data-display/SActionCard.css +6 -0
  141. package/src/components/shared/data-display/SActionCard.vue +49 -0
  142. package/src/components/shared/data-display/SActionList.css +15 -0
  143. package/src/components/shared/data-display/SActionList.vue +34 -0
  144. package/src/components/shared/data-display/SActionListItem.css +114 -0
  145. package/src/components/shared/data-display/SActionListItem.vue +230 -0
  146. package/src/components/shared/data-display/SBadge.css +73 -0
  147. package/src/components/shared/data-display/SBadge.vue +52 -0
  148. package/src/components/shared/data-display/SCodeBlock.css +56 -0
  149. package/src/components/shared/data-display/SCodeBlock.vue +188 -0
  150. package/src/components/shared/data-display/SCodeEditor.css +178 -0
  151. package/src/components/shared/data-display/SCodeEditor.vue +274 -0
  152. package/src/components/shared/data-display/SCodeSearchPanel.vue +327 -0
  153. package/src/components/shared/data-display/SCopyField.vue +77 -0
  154. package/src/components/shared/data-display/SDocBlock.css +136 -0
  155. package/src/components/shared/data-display/SDocBlock.vue +358 -0
  156. package/src/components/shared/data-display/SEmptyState.css +42 -0
  157. package/src/components/shared/data-display/SEmptyState.vue +79 -0
  158. package/src/components/shared/data-display/SExpandableText.vue +314 -0
  159. package/src/components/shared/data-display/SJsonTree.css +6 -0
  160. package/src/components/shared/data-display/SJsonTree.vue +111 -0
  161. package/src/components/shared/data-display/SKeyValueGrid.css +37 -0
  162. package/src/components/shared/data-display/SKeyValueGrid.vue +277 -0
  163. package/src/components/shared/data-display/SLinkedSystemsList.vue +270 -0
  164. package/src/components/shared/data-display/SMessage.css +88 -0
  165. package/src/components/shared/data-display/SMessage.vue +152 -0
  166. package/src/components/shared/data-display/SMetricCard.css +32 -0
  167. package/src/components/shared/data-display/SMetricCard.vue +117 -0
  168. package/src/components/shared/data-display/SProgressBar.css +84 -0
  169. package/src/components/shared/data-display/SProgressBar.vue +114 -0
  170. package/src/components/shared/data-display/SProgressIndicator.css +59 -0
  171. package/src/components/shared/data-display/SProgressIndicator.vue +104 -0
  172. package/src/components/shared/data-display/SSectionHeader.css +84 -0
  173. package/src/components/shared/data-display/SSectionHeader.vue +58 -0
  174. package/src/components/shared/data-display/SStatus.css +90 -0
  175. package/src/components/shared/data-display/SStatus.vue +79 -0
  176. package/src/components/shared/data-display/STable.css +83 -0
  177. package/src/components/shared/data-display/STable.vue +339 -0
  178. package/src/components/shared/data-display/SToastContainer.css +53 -0
  179. package/src/components/shared/data-display/SToastContainer.vue +31 -0
  180. package/src/components/shared/data-display/STooltip.css +114 -0
  181. package/src/components/shared/data-display/STooltip.vue +550 -0
  182. package/src/components/shared/data-display/STooltipAnchor.css +21 -0
  183. package/src/components/shared/data-display/STooltipAnchor.vue +119 -0
  184. package/src/components/shared/data-display/SVirtualList.css +24 -0
  185. package/src/components/shared/data-display/SVirtualList.ts +19 -0
  186. package/src/components/shared/data-display/SVirtualList.vue +361 -0
  187. package/src/components/shared/data-display/json.ts +136 -0
  188. package/src/components/shared/data-display/table.ts +76 -0
  189. package/src/components/shared/database/SDataGrid.css +3 -0
  190. package/src/components/shared/database/SDataGrid.vue +464 -0
  191. package/src/components/shared/database/SSqlEditor.vue +363 -0
  192. package/src/components/shared/database/dataGrid.ts +61 -0
  193. package/src/components/shared/feedback/SAsyncState.css +26 -0
  194. package/src/components/shared/feedback/SAsyncState.vue +190 -0
  195. package/src/components/shared/graph/SGraphViewport.ts +1 -0
  196. package/src/components/shared/graph/SGraphViewport.vue +858 -0
  197. package/src/components/shared/graph/_internal/graphViewportContract.ts +362 -0
  198. package/src/components/shared/markdown.ts +169 -0
  199. package/src/components/shared/navigation/SBottomNav.vue +692 -0
  200. package/src/components/shared/navigation/SBreadcrumbs.css +26 -0
  201. package/src/components/shared/navigation/SBreadcrumbs.vue +155 -0
  202. package/src/components/shared/navigation/SCatalogNavigator.css +100 -0
  203. package/src/components/shared/navigation/SCatalogNavigator.vue +308 -0
  204. package/src/components/shared/navigation/STab.vue +363 -0
  205. package/src/components/shared/navigation/STabList.vue +442 -0
  206. package/src/components/shared/navigation/STabPanel.vue +165 -0
  207. package/src/components/shared/navigation/STabPanels.vue +33 -0
  208. package/src/components/shared/navigation/STabs.vue +617 -0
  209. package/src/components/shared/navigation/SWizardSteps.css +69 -0
  210. package/src/components/shared/navigation/SWizardSteps.vue +171 -0
  211. package/src/components/shared/navigation/_internal/SCatalogNavigatorGroup.vue +63 -0
  212. package/src/components/shared/persona/SPersonaProjectPicker.css +36 -0
  213. package/src/components/shared/persona/SPersonaProjectPicker.vue +361 -0
  214. package/src/components/shared/persona/SPersonaRunWorkbench.css +109 -0
  215. package/src/components/shared/persona/SPersonaRunWorkbench.vue +549 -0
  216. package/src/components/shared/persona/_internal/SPersonaRunConsole.css +40 -0
  217. package/src/components/shared/persona/_internal/SPersonaRunConsole.vue +298 -0
  218. package/src/components/shared/persona/_internal/inventory.ts +110 -0
  219. package/src/components/shared/persona/_internal/labels.ts +79 -0
  220. package/src/components/shared/persona/presentation.ts +371 -0
  221. package/src/components/shared/persona/types.ts +256 -0
  222. package/src/composables/uiPreferencesReset.ts +52 -0
  223. package/src/composables/useClipboard.ts +37 -0
  224. package/src/composables/useDialogOverlay.ts +85 -0
  225. package/src/composables/useDisclosure.ts +40 -0
  226. package/src/composables/useFloatingPosition.ts +245 -0
  227. package/src/composables/useNotifier.ts +26 -0
  228. package/src/composables/useSidebarPanelState.ts +610 -0
  229. package/src/internal/codeEditorContract.ts +110 -0
  230. package/src/internal/codeEditorExtensions.ts +27 -0
  231. package/src/internal/feedbackPresentation.ts +136 -0
  232. package/src/internal/fieldSurface.ts +59 -0
  233. package/src/internal/focusNavigation.ts +69 -0
  234. package/src/internal/inlineTokenEditorContract.ts +136 -0
  235. package/src/internal/interactiveElement.ts +33 -0
  236. package/src/internal/layerStack.ts +202 -0
  237. package/src/internal/linkTarget.ts +256 -0
  238. package/src/internal/ownedAttrs.ts +464 -0
  239. package/src/internal/passiveContentContract.ts +129 -0
  240. package/src/internal/pointerInteractionLease.ts +81 -0
  241. package/src/internal/runtimeContract.ts +194 -0
  242. package/src/internal/runtimeTheme.ts +159 -0
  243. package/src/internal/semanticSizing.ts +60 -0
  244. package/src/internal/useMenuLayer.ts +129 -0
  245. package/src/stores/useNotifierStore.ts +69 -0
  246. package/src/styles/reference.css +4 -0
  247. package/src/styles/style.css +151 -0
  248. package/src/styles/tokens.css +836 -0
  249. package/src/theme.ts +449 -0
  250. package/src/types/highlight-js-lib.d.ts +76 -0
  251. package/src/variants.ts +15 -0
@@ -0,0 +1,298 @@
1
+ <script setup lang="ts">
2
+ import { computed } from 'vue'
3
+
4
+ import SButton from '../../controls/SButton.vue'
5
+ import SActionListItem from '../../data-display/SActionListItem.vue'
6
+ import SCodeBlock from '../../data-display/SCodeBlock.vue'
7
+ import SEmptyState from '../../data-display/SEmptyState.vue'
8
+ import SMessage from '../../data-display/SMessage.vue'
9
+ import SSectionHeader from '../../data-display/SSectionHeader.vue'
10
+ import SVirtualList from '../../data-display/SVirtualList.vue'
11
+ import SKeyValueGrid from '../../data-display/SKeyValueGrid.vue'
12
+ import SAsyncState from '../../feedback/SAsyncState.vue'
13
+ import {
14
+ assertPersonaInventory,
15
+ findExactPersonaInventoryItem,
16
+ resolvePersonaInventoryAsyncState,
17
+ } from './inventory'
18
+ import { personaDistinctMessage } from '../presentation'
19
+ import { getPersonaRunConsoleFactKey } from '../types'
20
+ import type {
21
+ PersonaPaginatedInventoryState,
22
+ PersonaRunConsoleCapabilities,
23
+ PersonaRunConsoleDensity,
24
+ PersonaRunConsoleEvent,
25
+ PersonaRunConsoleItem,
26
+ PersonaRunConsoleLabels,
27
+ PersonaRunConsoleOperation,
28
+ PersonaRunConsoleRun,
29
+ } from '../types'
30
+
31
+ defineOptions({ inheritAttrs: false })
32
+
33
+ const props = withDefaults(
34
+ defineProps<{
35
+ runsState: PersonaPaginatedInventoryState<PersonaRunConsoleRun>
36
+ itemsState: PersonaPaginatedInventoryState<PersonaRunConsoleItem>
37
+ eventsState: PersonaPaginatedInventoryState<PersonaRunConsoleEvent>
38
+ capabilities: PersonaRunConsoleCapabilities
39
+ operation: PersonaRunConsoleOperation
40
+ resolvedLabels: PersonaRunConsoleLabels
41
+ selectedRunId?: string | null
42
+ reportSummary?: string | null
43
+ density?: PersonaRunConsoleDensity
44
+ listHeight?: number
45
+ itemsHeight?: number
46
+ eventsHeight?: number
47
+ }>(),
48
+ {
49
+ selectedRunId: null,
50
+ reportSummary: null,
51
+ density: 'comfortable',
52
+ listHeight: 560,
53
+ itemsHeight: 250,
54
+ eventsHeight: 430,
55
+ },
56
+ )
57
+
58
+ const emit = defineEmits<{
59
+ 'select-run': [runId: string]
60
+ 'load-more-runs': []
61
+ 'refresh-events': []
62
+ 'cancel-run': []
63
+ 'load-more-items': []
64
+ 'append-events': []
65
+ 'load-more-events': []
66
+ }>()
67
+ const runs = computed(() => props.runsState.status === 'ready'
68
+ ? assertPersonaInventory('SPersonaRunConsole.runsState', props.runsState.items)
69
+ : [])
70
+ const items = computed(() => props.itemsState.status === 'ready'
71
+ ? assertPersonaInventory('SPersonaRunConsole.itemsState', props.itemsState.items)
72
+ : [])
73
+ const events = computed(() => props.eventsState.status === 'ready'
74
+ ? assertPersonaInventory('SPersonaRunConsole.eventsState', props.eventsState.items)
75
+ : [])
76
+ const runsAsyncState = computed(() => resolvePersonaInventoryAsyncState(
77
+ 'SPersonaRunConsole.runsState',
78
+ props.runsState,
79
+ {
80
+ loadingMessage: 'Загрузка запусков',
81
+ emptyMessage: props.resolvedLabels.noRuns,
82
+ },
83
+ ))
84
+ const itemsAsyncState = computed(() => resolvePersonaInventoryAsyncState(
85
+ 'SPersonaRunConsole.itemsState',
86
+ props.itemsState,
87
+ {
88
+ loadingMessage: 'Загрузка рабочих элементов',
89
+ emptyMessage: props.resolvedLabels.noItems,
90
+ },
91
+ ))
92
+ const eventsAsyncState = computed(() => resolvePersonaInventoryAsyncState(
93
+ 'SPersonaRunConsole.eventsState',
94
+ props.eventsState,
95
+ {
96
+ loadingMessage: 'Загрузка событий',
97
+ emptyMessage: props.resolvedLabels.noEvents,
98
+ },
99
+ ))
100
+ const selectedRun = computed(() => findExactPersonaInventoryItem(
101
+ 'SPersonaRunConsole.runsState',
102
+ runs.value,
103
+ props.selectedRunId,
104
+ ))
105
+ const selectedFacts = computed(() => selectedRun.value?.facts ?? [])
106
+ const virtualRowSize = 96
107
+ const runsViewportHeight = computed(() => Math.min(props.listHeight, runs.value.length * virtualRowSize))
108
+ const itemsViewportHeight = computed(() => Math.min(props.itemsHeight, items.value.length * virtualRowSize))
109
+ const eventsViewportHeight = computed(() => Math.min(props.eventsHeight, events.value.length * virtualRowSize))
110
+
111
+ function eventDetail(event: PersonaRunConsoleEvent): string | null {
112
+ return personaDistinctMessage(event.title, event.message)
113
+ }
114
+ </script>
115
+
116
+ <template>
117
+ <div class="s-persona-run-console-shell">
118
+ <div class="s-persona-run-console" :data-density="density">
119
+ <section class="s-persona-run-console__section">
120
+ <SSectionHeader :title="resolvedLabels.runListTitle" size="sm" />
121
+ <SAsyncState :state="runsAsyncState">
122
+ <SVirtualList
123
+ :items="runs"
124
+ :item-size="virtualRowSize"
125
+ :height="runsViewportHeight"
126
+ :item-key="(run) => run.id"
127
+ :accessible-label="resolvedLabels.runListTitle"
128
+ :item-label="(run) => run.title"
129
+ >
130
+ <template #default="{ item }">
131
+ <SActionListItem
132
+ role="presentation"
133
+ :title="item.title"
134
+ :description="item.meta"
135
+ :detail="item.detail"
136
+ :selected="selectedRunId === item.id"
137
+ :status-label="item.statusLabel"
138
+ :status-severity="item.statusSeverity"
139
+ trailing-layout="inline"
140
+ height-mode="fill"
141
+ :surface="{ mode: 'action', accessibleLabel: `Открыть запуск ${item.title}` }"
142
+ @activate="emit('select-run', item.id)"
143
+ />
144
+ </template>
145
+ </SVirtualList>
146
+ </SAsyncState>
147
+ <div
148
+ v-if="runsState.status === 'ready' && runsState.pagination !== 'complete'"
149
+ class="s-persona-run-console__actions-line"
150
+ >
151
+ <SButton
152
+ size="sm"
153
+ severity="secondary"
154
+ :loading="runsState.pagination === 'loading-more'"
155
+ @click="emit('load-more-runs')"
156
+ >
157
+ {{ resolvedLabels.loadMoreRuns }}
158
+ </SButton>
159
+ </div>
160
+ </section>
161
+
162
+ <section class="s-persona-run-console__section">
163
+ <SSectionHeader :title="resolvedLabels.selectedRunTitle" size="sm" />
164
+ <div v-if="selectedRun" class="s-persona-run-console__detail">
165
+ <SSectionHeader :title="selectedRun.title" :description="selectedRun.meta">
166
+ <template #actions>
167
+ <SButton
168
+ size="sm"
169
+ severity="secondary"
170
+ :loading="operation === 'refreshing-events'"
171
+ @click="emit('refresh-events')"
172
+ >
173
+ {{ resolvedLabels.refreshEvents }}
174
+ </SButton>
175
+ <SButton
176
+ v-if="capabilities.cancelRun === 'enabled'"
177
+ size="sm"
178
+ severity="danger"
179
+ :loading="operation === 'cancelling-run'"
180
+ @click="emit('cancel-run')"
181
+ >
182
+ {{ resolvedLabels.cancelRun }}
183
+ </SButton>
184
+ </template>
185
+ </SSectionHeader>
186
+
187
+ <SMessage v-if="selectedRun.artifactLabel" severity="info" title="Артефакты">
188
+ {{ selectedRun.artifactLabel }}
189
+ </SMessage>
190
+
191
+ <SKeyValueGrid
192
+ v-if="selectedFacts.length > 0"
193
+ :items="selectedFacts"
194
+ :get-item-key="getPersonaRunConsoleFactKey"
195
+ variant="inline"
196
+ :columns="1"
197
+ />
198
+
199
+ <SSectionHeader :title="resolvedLabels.itemsTitle" size="sm" />
200
+ <SAsyncState :state="itemsAsyncState">
201
+ <SVirtualList
202
+ :items="items"
203
+ :item-size="virtualRowSize"
204
+ :height="itemsViewportHeight"
205
+ :item-key="(workItem) => workItem.id"
206
+ :accessible-label="resolvedLabels.itemsTitle"
207
+ :item-label="(workItem) => workItem.title"
208
+ >
209
+ <template #default="{ item }">
210
+ <SActionListItem
211
+ role="presentation"
212
+ :title="item.title"
213
+ :description="item.meta"
214
+ :detail="item.detail"
215
+ :status-label="item.statusLabel"
216
+ :status-severity="item.statusSeverity"
217
+ :badges="item.badges"
218
+ :surface="{ mode: 'static' }"
219
+ trailing-layout="inline"
220
+ height-mode="fill"
221
+ />
222
+ </template>
223
+ </SVirtualList>
224
+ </SAsyncState>
225
+ <div
226
+ v-if="itemsState.status === 'ready' && itemsState.pagination !== 'complete'"
227
+ class="s-persona-run-console__actions-line"
228
+ >
229
+ <SButton
230
+ size="sm"
231
+ severity="secondary"
232
+ :loading="itemsState.pagination === 'loading-more'"
233
+ @click="emit('load-more-items')"
234
+ >
235
+ {{ resolvedLabels.loadMoreItems }}
236
+ </SButton>
237
+ </div>
238
+
239
+ <SCodeBlock
240
+ v-if="reportSummary"
241
+ :code="reportSummary"
242
+ :language="{ id: 'plaintext' }"
243
+ :show-header="false"
244
+ :show-line-numbers="false"
245
+ viewport="compact"
246
+ :aria-label="resolvedLabels.reportSummary"
247
+ />
248
+
249
+ <SSectionHeader :title="resolvedLabels.eventsTitle" size="sm" />
250
+ <SAsyncState :state="eventsAsyncState">
251
+ <SVirtualList
252
+ :items="events"
253
+ :item-size="virtualRowSize"
254
+ :height="eventsViewportHeight"
255
+ :item-key="(event) => event.id"
256
+ :accessible-label="resolvedLabels.eventsTitle"
257
+ :item-label="(event) => event.title"
258
+ >
259
+ <template #default="{ item }">
260
+ <SActionListItem
261
+ role="presentation"
262
+ :title="item.title"
263
+ :description="item.meta"
264
+ :detail="eventDetail(item)"
265
+ :badges="item.badges"
266
+ :surface="{ mode: 'static' }"
267
+ height-mode="fill"
268
+ />
269
+ </template>
270
+ </SVirtualList>
271
+ </SAsyncState>
272
+ <div v-if="eventsState.status === 'ready'" class="s-persona-run-console__actions-line">
273
+ <SButton
274
+ size="sm"
275
+ severity="secondary"
276
+ :loading="operation === 'appending-events'"
277
+ @click="emit('append-events')"
278
+ >
279
+ {{ resolvedLabels.appendEvents }}
280
+ </SButton>
281
+ <SButton
282
+ v-if="eventsState.status === 'ready' && eventsState.pagination !== 'complete'"
283
+ size="sm"
284
+ severity="secondary"
285
+ :loading="eventsState.pagination === 'loading-more'"
286
+ @click="emit('load-more-events')"
287
+ >
288
+ {{ resolvedLabels.loadMoreEvents }}
289
+ </SButton>
290
+ </div>
291
+ </div>
292
+ <SEmptyState v-else :title="resolvedLabels.emptyRun" size="sm" />
293
+ </section>
294
+ </div>
295
+ </div>
296
+ </template>
297
+
298
+ <style lang="postcss" src="./SPersonaRunConsole.css" scoped></style>
@@ -0,0 +1,110 @@
1
+ import type { SAsyncStateContract } from '../../feedback/SAsyncState.vue'
2
+ import type {
3
+ PersonaInventoryState,
4
+ PersonaPaginatedInventoryState,
5
+ PersonaProjectPickerState,
6
+ } from '../types'
7
+
8
+ export interface PersonaStableInventoryItem {
9
+ readonly id: string
10
+ }
11
+
12
+ export interface PersonaAsyncPresentation {
13
+ readonly loadingMessage: string
14
+ readonly emptyMessage: string
15
+ }
16
+
17
+ export function assertPersonaInventory<Item extends PersonaStableInventoryItem>(
18
+ owner: string,
19
+ items: Item[],
20
+ ): Item[] {
21
+ if (!Array.isArray(items)) {
22
+ throw new TypeError(
23
+ `${owner}: inventory должен быть массивом. / ${owner}: inventory must be an array.`,
24
+ )
25
+ }
26
+
27
+ const identities = new Set<string>()
28
+ for (const [index, item] of items.entries()) {
29
+ const candidate: unknown = item
30
+ if (typeof candidate !== 'object' || candidate === null || Array.isArray(candidate)) {
31
+ throw new TypeError(
32
+ `${owner}: inventory[${index}] должен быть объектом со стабильным id. `
33
+ + `/ ${owner}: inventory[${index}] must be an object with a stable id.`,
34
+ )
35
+ }
36
+ const id = (candidate as { id?: unknown }).id
37
+ if (typeof id !== 'string' || id.length === 0) {
38
+ throw new TypeError(
39
+ `${owner}: inventory[${index}].id должен быть непустой строкой. `
40
+ + `/ ${owner}: inventory[${index}].id must be a non-empty string.`,
41
+ )
42
+ }
43
+ if (identities.has(id)) {
44
+ throw new TypeError(
45
+ `${owner}: inventory содержит duplicate id ${JSON.stringify(id)}. `
46
+ + `/ ${owner}: inventory contains duplicate id ${JSON.stringify(id)}.`,
47
+ )
48
+ }
49
+ identities.add(id)
50
+ }
51
+ return items
52
+ }
53
+
54
+ export function findExactPersonaInventoryItem<Item extends PersonaStableInventoryItem>(
55
+ owner: string,
56
+ items: Item[],
57
+ selectedId: string | null,
58
+ ): Item | null {
59
+ const inventory = assertPersonaInventory(owner, items)
60
+ if (selectedId === null) return null
61
+ if (typeof selectedId !== 'string' || selectedId.length === 0) {
62
+ throw new TypeError(
63
+ `${owner}: selectedId должен быть непустой строкой или null. `
64
+ + `/ ${owner}: selectedId must be a non-empty string or null.`,
65
+ )
66
+ }
67
+ const selectedItem = inventory.find((item) => item.id === selectedId)
68
+ if (selectedItem !== undefined) return selectedItem
69
+ throw new Error(
70
+ `${owner}: selectedId ${JSON.stringify(selectedId)} отсутствует в inventory. `
71
+ + `/ ${owner}: selectedId ${JSON.stringify(selectedId)} is absent from the inventory.`,
72
+ )
73
+ }
74
+
75
+ /**
76
+ * Проецирует доменный inventory contract в единственный canonical async-state contract.
77
+ * Projects a domain inventory contract onto the sole canonical async-state contract.
78
+ */
79
+ export function resolvePersonaInventoryAsyncState<Item extends PersonaStableInventoryItem>(
80
+ owner: string,
81
+ state: PersonaInventoryState<Item> | PersonaPaginatedInventoryState<Item>,
82
+ presentation: PersonaAsyncPresentation,
83
+ ): SAsyncStateContract {
84
+ if (state.status === 'loading') {
85
+ return { status: 'loading', message: state.message ?? presentation.loadingMessage }
86
+ }
87
+ if (state.status === 'error') return state
88
+
89
+ const inventory = assertPersonaInventory(owner, state.items)
90
+ return inventory.length === 0
91
+ ? { status: 'empty', message: presentation.emptyMessage }
92
+ : { status: 'ready' }
93
+ }
94
+
95
+ /**
96
+ * Проецирует picker state в canonical async-state, сохраняя ready listing как доменный payload.
97
+ * Projects picker state onto canonical async-state while preserving the ready listing payload.
98
+ */
99
+ export function resolvePersonaProjectPickerAsyncState(
100
+ state: PersonaProjectPickerState,
101
+ presentation: PersonaAsyncPresentation,
102
+ ): SAsyncStateContract {
103
+ if (state.status === 'loading') {
104
+ return { status: 'loading', message: state.message ?? presentation.loadingMessage }
105
+ }
106
+ if (state.status === 'error') return state
107
+ return state.listing === null
108
+ ? { status: 'empty', message: presentation.emptyMessage }
109
+ : { status: 'ready' }
110
+ }
@@ -0,0 +1,79 @@
1
+ import { validateNonEmptyString } from '../../../../internal/runtimeContract'
2
+
3
+ import type { PersonaRunWorkbenchLabels } from '../types'
4
+
5
+ const PERSONA_RUN_WORKBENCH_DEFAULT_LABELS = Object.freeze({
6
+ projectTitle: 'Проект',
7
+ discoveryTitle: 'Discovery',
8
+ catalogTitle: 'Каталог тестов',
9
+ structureTitle: 'Структура сценария',
10
+ selectedTestTitle: 'Выбранный тест',
11
+ runSelected: 'Запустить тест',
12
+ runAll: 'Запустить каталог',
13
+ discover: 'Обнаружить',
14
+ watch: 'Наблюдать',
15
+ searchPlaceholder: 'Поиск по тестам',
16
+ noCatalogTests: 'Тесты не найдены.',
17
+ emptyStructure: 'Структура сценария не сохранена.',
18
+ runListTitle: 'Запуски',
19
+ selectedRunTitle: 'События запуска',
20
+ itemsTitle: 'Рабочие элементы',
21
+ eventsTitle: 'События',
22
+ loadMoreRuns: 'Загрузить ещё',
23
+ loadMoreItems: 'Догрузить рабочие элементы',
24
+ appendEvents: 'Догрузить новые',
25
+ loadMoreEvents: 'Догрузить историю',
26
+ refreshEvents: 'Обновить события',
27
+ cancelRun: 'Отменить',
28
+ reportSummary: 'Сводка отчёта',
29
+ emptyRun: 'Выберите запуск для просмотра событий и артефактов.',
30
+ noRuns: 'Запуски не найдены.',
31
+ noItems: 'Рабочие элементы ещё не появились.',
32
+ noEvents: 'События запуска ещё не поступили.',
33
+ } satisfies PersonaRunWorkbenchLabels)
34
+
35
+ type PersonaRunWorkbenchLabelKey = keyof PersonaRunWorkbenchLabels
36
+
37
+ function resolveLabel(
38
+ overrides: Readonly<Partial<PersonaRunWorkbenchLabels>>,
39
+ key: PersonaRunWorkbenchLabelKey,
40
+ ): string {
41
+ const override = overrides[key]
42
+ return override === undefined
43
+ ? PERSONA_RUN_WORKBENCH_DEFAULT_LABELS[key]
44
+ : validateNonEmptyString('SPersonaRunWorkbench', `labels.${key}`, override)
45
+ }
46
+
47
+ export function resolvePersonaRunWorkbenchLabels(
48
+ overrides: Readonly<Partial<PersonaRunWorkbenchLabels>> = {},
49
+ ): PersonaRunWorkbenchLabels {
50
+ return {
51
+ projectTitle: resolveLabel(overrides, 'projectTitle'),
52
+ discoveryTitle: resolveLabel(overrides, 'discoveryTitle'),
53
+ catalogTitle: resolveLabel(overrides, 'catalogTitle'),
54
+ structureTitle: resolveLabel(overrides, 'structureTitle'),
55
+ selectedTestTitle: resolveLabel(overrides, 'selectedTestTitle'),
56
+ runSelected: resolveLabel(overrides, 'runSelected'),
57
+ runAll: resolveLabel(overrides, 'runAll'),
58
+ discover: resolveLabel(overrides, 'discover'),
59
+ watch: resolveLabel(overrides, 'watch'),
60
+ searchPlaceholder: resolveLabel(overrides, 'searchPlaceholder'),
61
+ noCatalogTests: resolveLabel(overrides, 'noCatalogTests'),
62
+ emptyStructure: resolveLabel(overrides, 'emptyStructure'),
63
+ runListTitle: resolveLabel(overrides, 'runListTitle'),
64
+ selectedRunTitle: resolveLabel(overrides, 'selectedRunTitle'),
65
+ itemsTitle: resolveLabel(overrides, 'itemsTitle'),
66
+ eventsTitle: resolveLabel(overrides, 'eventsTitle'),
67
+ loadMoreRuns: resolveLabel(overrides, 'loadMoreRuns'),
68
+ loadMoreItems: resolveLabel(overrides, 'loadMoreItems'),
69
+ appendEvents: resolveLabel(overrides, 'appendEvents'),
70
+ loadMoreEvents: resolveLabel(overrides, 'loadMoreEvents'),
71
+ refreshEvents: resolveLabel(overrides, 'refreshEvents'),
72
+ cancelRun: resolveLabel(overrides, 'cancelRun'),
73
+ reportSummary: resolveLabel(overrides, 'reportSummary'),
74
+ emptyRun: resolveLabel(overrides, 'emptyRun'),
75
+ noRuns: resolveLabel(overrides, 'noRuns'),
76
+ noItems: resolveLabel(overrides, 'noItems'),
77
+ noEvents: resolveLabel(overrides, 'noEvents'),
78
+ }
79
+ }