@openforge-app/plugin-sdk 0.1.0 → 0.2.4

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 (101) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +26 -0
  3. package/dist/backend.d.ts +2 -2
  4. package/dist/browserSurfaces.d.ts +78 -0
  5. package/dist/browserSurfaces.js +17 -0
  6. package/dist/browserSurfacesTesting.d.ts +60 -0
  7. package/dist/browserSurfacesTesting.js +240 -0
  8. package/dist/domain.d.ts +74 -15
  9. package/dist/domain.js +5 -1
  10. package/dist/fileIcons.d.ts +6 -0
  11. package/dist/fileIcons.js +84 -0
  12. package/dist/frontend.d.ts +5 -2
  13. package/dist/frontend.js +1 -0
  14. package/dist/index.d.ts +5 -3
  15. package/dist/index.js +2 -1
  16. package/dist/manifest.d.ts +80 -2
  17. package/dist/manifest.js +55 -1
  18. package/dist/markdown.d.ts +5 -1
  19. package/dist/markdown.js +67 -18
  20. package/dist/openforgePackageMetadataSchema.json +80 -4
  21. package/dist/pluginIconContract.d.ts +3 -0
  22. package/dist/pluginIconContract.js +12 -0
  23. package/dist/pluginIcons.d.ts +7 -0
  24. package/dist/pluginIcons.js +99 -0
  25. package/dist/publicUiExports.d.mts +16 -0
  26. package/dist/publicUiExports.mjs +60 -0
  27. package/dist/svelteHostRuntimeContract.d.mts +1 -0
  28. package/dist/svelteHostRuntimeContract.mjs +16 -0
  29. package/dist/testing/backendServicesFake.d.ts +19 -0
  30. package/dist/testing/backendServicesFake.js +87 -0
  31. package/dist/testing/commonApiFake.d.ts +23 -0
  32. package/dist/testing/commonApiFake.js +306 -0
  33. package/dist/testing/contracts.d.ts +236 -0
  34. package/dist/testing/contracts.js +1 -0
  35. package/dist/testing/frontendContributionFake.d.ts +36 -0
  36. package/dist/testing/frontendContributionFake.js +204 -0
  37. package/dist/testing/registryFake.d.ts +40 -0
  38. package/dist/testing/registryFake.js +124 -0
  39. package/dist/testing/support.d.ts +40 -0
  40. package/dist/testing/support.js +247 -0
  41. package/dist/testing.d.ts +3 -223
  42. package/dist/testing.js +2 -625
  43. package/dist/types.d.ts +251 -9
  44. package/dist/types.js +47 -0
  45. package/dist/ui/Button.svelte +49 -0
  46. package/dist/ui/Checkbox.svelte +137 -0
  47. package/dist/ui/FileTypeIcon.svelte +37 -0
  48. package/dist/ui/MarkdownContent.svelte +71 -11
  49. package/dist/ui/Modal.svelte +157 -0
  50. package/dist/ui/PluginPageHeader.svelte +26 -0
  51. package/dist/ui/PluginSidebarLink.svelte +54 -0
  52. package/dist/ui/PluginViewState.svelte +76 -0
  53. package/dist/ui/ResizablePanel.svelte +10 -0
  54. package/dist/ui/icons/README.md +9 -0
  55. package/dist/ui/icons/c.svg +1 -0
  56. package/dist/ui/icons/console.svg +1 -0
  57. package/dist/ui/icons/cpp.svg +1 -0
  58. package/dist/ui/icons/csharp.svg +1 -0
  59. package/dist/ui/icons/css.svg +3 -0
  60. package/dist/ui/icons/database.svg +1 -0
  61. package/dist/ui/icons/docker.svg +1 -0
  62. package/dist/ui/icons/document.svg +4 -0
  63. package/dist/ui/icons/file.svg +1 -0
  64. package/dist/ui/icons/folder-open.svg +1 -0
  65. package/dist/ui/icons/folder.svg +1 -0
  66. package/dist/ui/icons/git.svg +3 -0
  67. package/dist/ui/icons/go.svg +1 -0
  68. package/dist/ui/icons/graphql.svg +20 -0
  69. package/dist/ui/icons/h.svg +3 -0
  70. package/dist/ui/icons/html.svg +3 -0
  71. package/dist/ui/icons/image.svg +3 -0
  72. package/dist/ui/icons/java.svg +6 -0
  73. package/dist/ui/icons/javascript.svg +3 -0
  74. package/dist/ui/icons/json.svg +3 -0
  75. package/dist/ui/icons/kotlin.svg +1 -0
  76. package/dist/ui/icons/lock.svg +3 -0
  77. package/dist/ui/icons/markdown.svg +6 -0
  78. package/dist/ui/icons/nodejs.svg +6 -0
  79. package/dist/ui/icons/npm.svg +3 -0
  80. package/dist/ui/icons/pdf.svg +1 -0
  81. package/dist/ui/icons/php.svg +1 -0
  82. package/dist/ui/icons/python.svg +1 -0
  83. package/dist/ui/icons/react.svg +8 -0
  84. package/dist/ui/icons/react_ts.svg +8 -0
  85. package/dist/ui/icons/readme.svg +4 -0
  86. package/dist/ui/icons/ruby.svg +1 -0
  87. package/dist/ui/icons/rust.svg +1 -0
  88. package/dist/ui/icons/sass.svg +3 -0
  89. package/dist/ui/icons/settings.svg +4 -0
  90. package/dist/ui/icons/svelte.svg +1 -0
  91. package/dist/ui/icons/svg.svg +3 -0
  92. package/dist/ui/icons/swift.svg +1 -0
  93. package/dist/ui/icons/tune.svg +11 -0
  94. package/dist/ui/icons/typescript-def.svg +6 -0
  95. package/dist/ui/icons/typescript.svg +3 -0
  96. package/dist/ui/icons/vue.svg +1 -0
  97. package/dist/ui/icons/xml.svg +1 -0
  98. package/dist/ui/icons/yaml.svg +3 -0
  99. package/dist/ui/icons/zip.svg +3 -0
  100. package/dist/vite.js +5 -2
  101. package/package.json +35 -7
@@ -0,0 +1,37 @@
1
+ <script lang="ts">
2
+ import { getFileIconName, getFolderIconName } from '../fileIcons'
3
+
4
+ interface Props {
5
+ filename?: string
6
+ folder?: boolean
7
+ open?: boolean
8
+ class?: string
9
+ }
10
+
11
+ let { filename, folder = false, open = false, class: className = '' }: Props = $props()
12
+
13
+ // Curated, vendored set only (~45 files) — eager raw is fine and avoids runtime asset URLs.
14
+ const icons = import.meta.glob('./icons/*.svg', {
15
+ query: '?raw',
16
+ import: 'default',
17
+ eager: true,
18
+ }) as Record<string, string>
19
+
20
+ let iconName = $derived(folder ? getFolderIconName(open) : getFileIconName(filename ?? ''))
21
+ let svg = $derived(icons[`./icons/${iconName}.svg`] ?? icons['./icons/file.svg'] ?? '')
22
+ </script>
23
+
24
+ <span
25
+ class="file-type-icon inline-flex items-center justify-center shrink-0 {className}"
26
+ data-icon={iconName}
27
+ aria-hidden="true"
28
+ >
29
+ {@html svg}
30
+ </span>
31
+
32
+ <style>
33
+ .file-type-icon :global(svg) {
34
+ width: 100%;
35
+ height: 100%;
36
+ }
37
+ </style>
@@ -1,30 +1,90 @@
1
1
  <script lang="ts">
2
- import { renderMarkdownHtml } from '../markdown'
2
+ import { onDestroy } from 'svelte'
3
+ import { getMarkdownRepositoryLinkSuffix, renderMarkdownHtml, resolveMarkdownRepositoryPath } from '../markdown'
3
4
 
4
5
  interface Props {
5
6
  content: string
6
7
  imageBaseUrl?: string | null
8
+ markdownFilePath?: string | null
9
+ resolveRepositoryImage?: (repositoryPath: string) => Promise<string | null>
10
+ onOpenRepositoryPath?: (repositoryPath: string, suffix: string) => void | Promise<void>
7
11
  onOpenUrl?: (url: string) => void | Promise<void>
8
12
  }
9
13
 
10
- let { content, imageBaseUrl = null, onOpenUrl }: Props = $props()
14
+ let {
15
+ content,
16
+ imageBaseUrl = null,
17
+ markdownFilePath = null,
18
+ resolveRepositoryImage,
19
+ onOpenRepositoryPath,
20
+ onOpenUrl,
21
+ }: Props = $props()
11
22
 
12
- let html = $derived(renderMarkdownHtml(content, { imageBaseUrl }))
23
+ let root = $state<HTMLDivElement | null>(null)
24
+ let imageResolutionId = 0
25
+ let html = $derived(renderMarkdownHtml(content, {
26
+ imageBaseUrl: resolveRepositoryImage ? null : imageBaseUrl,
27
+ markdownFilePath,
28
+ deferRepositoryImages: Boolean(resolveRepositoryImage),
29
+ }))
13
30
 
14
- function handleClick(e: MouseEvent) {
15
- if (!(e.target instanceof Element)) return
31
+ async function resolveRepositoryImages(runId: number) {
32
+ if (!root || !markdownFilePath || !resolveRepositoryImage) return
16
33
 
17
- const anchor = e.target.closest('a')
18
- if (anchor?.href) {
19
- e.preventDefault()
20
- if (onOpenUrl) {
21
- void onOpenUrl(anchor.href)
34
+ const images = Array.from(root.querySelectorAll('img[data-markdown-repository-path]'))
35
+ await Promise.all(images.map(async (image) => {
36
+ const repositoryPath = image.getAttribute('data-markdown-repository-path')
37
+ if (!repositoryPath) return
38
+
39
+ try {
40
+ const resolvedSrc = await resolveRepositoryImage(repositoryPath)
41
+ if (runId !== imageResolutionId || !resolvedSrc) return
42
+ image.setAttribute('src', resolvedSrc)
43
+ image.removeAttribute('data-markdown-repository-path')
44
+ } catch {
45
+ // Leave the image inert when an asset is missing or cannot be previewed.
46
+ }
47
+ }))
48
+ }
49
+
50
+ $effect(() => {
51
+ const runId = ++imageResolutionId
52
+ void html
53
+ if (!root || !markdownFilePath || !resolveRepositoryImage) return
54
+
55
+ void resolveRepositoryImages(runId)
56
+ })
57
+
58
+ onDestroy(() => {
59
+ imageResolutionId++
60
+ })
61
+
62
+ function handleClick(event: MouseEvent) {
63
+ if (!(event.target instanceof Element)) return
64
+
65
+ const anchor = event.target.closest('a')
66
+ const href = anchor?.getAttribute('href')
67
+ if (!anchor || !href) return
68
+
69
+ if (markdownFilePath) {
70
+ const repositoryPath = resolveMarkdownRepositoryPath(href, markdownFilePath)
71
+ if (repositoryPath) {
72
+ event.preventDefault()
73
+ void onOpenRepositoryPath?.(repositoryPath, getMarkdownRepositoryLinkSuffix(href))
74
+ return
22
75
  }
23
76
  }
77
+
78
+ if (href.startsWith('#')) return
79
+
80
+ event.preventDefault()
81
+ if (onOpenUrl && anchor.href) {
82
+ void onOpenUrl(href.startsWith('//') ? `https:${href}` : anchor.href)
83
+ }
24
84
  }
25
85
  </script>
26
86
 
27
87
  <!-- svelte-ignore a11y_click_events_have_key_events -->
28
- <div role="presentation" class="markdown-body" onclick={handleClick}>
88
+ <div bind:this={root} role="presentation" class="markdown-body" onclick={handleClick}>
29
89
  {@html html}
30
90
  </div>
@@ -0,0 +1,157 @@
1
+ <script lang="ts">
2
+ import { onDestroy, tick } from 'svelte'
3
+ import type { Snippet } from 'svelte'
4
+
5
+ export type ModalInitialFocus = HTMLElement | string | (() => HTMLElement | null | undefined) | null | undefined
6
+
7
+ interface Props {
8
+ onClose: () => void
9
+ maxWidth?: string
10
+ overflowVisible?: boolean
11
+ initialFocus?: ModalInitialFocus
12
+ ariaLabel?: string
13
+ showHeader?: boolean
14
+ closeLabel?: string
15
+ closeDisabled?: boolean
16
+ onKeydown?: (event: KeyboardEvent) => boolean | void
17
+ testId?: string
18
+ modalClass?: string
19
+ boxClass?: string
20
+ header?: Snippet
21
+ children: Snippet
22
+ }
23
+
24
+ let { onClose, maxWidth = '500px', overflowVisible = false, initialFocus, ariaLabel, showHeader = true, closeLabel = 'Close dialog', closeDisabled = false, onKeydown, testId, modalClass = '', boxClass = '', header, children }: Props = $props()
25
+
26
+ let modalElement: HTMLDivElement | null = $state(null)
27
+ let hasAppliedInitialFocus = false
28
+ const returnFocusTarget = typeof document !== 'undefined'
29
+ && document.activeElement instanceof HTMLElement
30
+ && document.activeElement !== document.body
31
+ ? document.activeElement
32
+ : null
33
+
34
+ onDestroy(() => {
35
+ if (!returnFocusTarget?.isConnected) return
36
+
37
+ const activeElement = document.activeElement
38
+ const focusRemainedInModal = activeElement === document.body
39
+ || (activeElement instanceof HTMLElement && !activeElement.isConnected)
40
+ || (activeElement instanceof Node && modalElement?.contains(activeElement))
41
+
42
+ if (focusRemainedInModal) returnFocusTarget.focus()
43
+ })
44
+
45
+ function resolveInitialFocusTarget(): HTMLElement | null {
46
+ if (!modalElement) return null
47
+
48
+ if (typeof initialFocus === 'function') {
49
+ return initialFocus() ?? modalElement
50
+ }
51
+
52
+ if (typeof initialFocus === 'string') {
53
+ return modalElement.querySelector<HTMLElement>(initialFocus) ?? modalElement
54
+ }
55
+
56
+ return initialFocus ?? modalElement
57
+ }
58
+
59
+ function focusInitialTarget() {
60
+ const target = resolveInitialFocusTarget()
61
+ target?.focus()
62
+
63
+ if (initialFocus !== undefined) {
64
+ void tick().then(() => {
65
+ const active = document.activeElement
66
+ if (active === document.body || active === modalElement || (active && !modalElement?.contains(active))) {
67
+ resolveInitialFocusTarget()?.focus()
68
+ }
69
+ })
70
+ }
71
+ }
72
+
73
+ $effect(() => {
74
+ if (!modalElement || hasAppliedInitialFocus) return
75
+
76
+ hasAppliedInitialFocus = true
77
+ void focusInitialTarget()
78
+ })
79
+
80
+ function getFocusableElements(): HTMLElement[] {
81
+ if (!modalElement) return []
82
+
83
+ const selector = [
84
+ 'a[href]',
85
+ 'button:not([disabled])',
86
+ 'textarea:not([disabled])',
87
+ 'input:not([disabled])',
88
+ 'select:not([disabled])',
89
+ '[tabindex]:not([tabindex="-1"])',
90
+ ].join(',')
91
+
92
+ return Array.from(modalElement.querySelectorAll<HTMLElement>(selector))
93
+ .filter((element) => !element.hasAttribute('disabled') && element.getAttribute('aria-hidden') !== 'true')
94
+ }
95
+
96
+ function keepFocusInsideModal(e: KeyboardEvent) {
97
+ const focusable = getFocusableElements()
98
+ if (focusable.length === 0) {
99
+ e.preventDefault()
100
+ modalElement?.focus()
101
+ return
102
+ }
103
+
104
+ const first = focusable[0]
105
+ const last = focusable[focusable.length - 1]
106
+ const active = document.activeElement
107
+
108
+ if (e.shiftKey && (active === first || active === modalElement || !modalElement?.contains(active))) {
109
+ e.preventDefault()
110
+ last.focus()
111
+ } else if (!e.shiftKey && active === last) {
112
+ e.preventDefault()
113
+ first.focus()
114
+ }
115
+ }
116
+
117
+ function handleKeydown(e: KeyboardEvent) {
118
+ if (onKeydown?.(e)) {
119
+ e.stopPropagation()
120
+ return
121
+ }
122
+
123
+ if (e.metaKey || e.ctrlKey || e.altKey) return
124
+
125
+ e.stopPropagation()
126
+
127
+ if (e.key === 'Escape') {
128
+ if (!closeDisabled) onClose()
129
+ } else if (e.key === 'Tab') {
130
+ keepFocusInsideModal(e)
131
+ }
132
+ }
133
+
134
+ function handleOverlayClick(e: MouseEvent) {
135
+ if (!closeDisabled && e.target === e.currentTarget) {
136
+ onClose()
137
+ }
138
+ }
139
+
140
+ function handleCloseButtonClick() {
141
+ if (!closeDisabled) onClose()
142
+ }
143
+ </script>
144
+
145
+ <div bind:this={modalElement} class="modal modal-open {modalClass}" data-testid={testId} onclick={handleOverlayClick} onkeydown={handleKeydown} role="dialog" aria-modal="true" aria-label={ariaLabel} tabindex="-1">
146
+ <div class="modal-box bg-base-100 shadow-xl p-0 flex flex-col max-h-[90vh] {overflowVisible ? 'overflow-visible' : ''} {boxClass}" style="max-width: {maxWidth}">
147
+ {#if showHeader}
148
+ <div class="flex items-center justify-between px-5 py-4 border-b border-base-300">
149
+ {#if header}
150
+ {@render header()}
151
+ {/if}
152
+ <button class="btn btn-ghost btn-xs shrink-0" aria-label={closeLabel} onclick={handleCloseButtonClick} type="button" disabled={closeDisabled}>✕</button>
153
+ </div>
154
+ {/if}
155
+ {@render children()}
156
+ </div>
157
+ </div>
@@ -0,0 +1,26 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+
4
+ interface Props {
5
+ title: string
6
+ subtitle?: string | null
7
+ surface?: 'default' | 'subtle'
8
+ actions?: Snippet
9
+ }
10
+
11
+ let { title, subtitle = null, surface = 'subtle', actions }: Props = $props()
12
+ </script>
13
+
14
+ <header class="flex items-center justify-between gap-4 border-b border-base-300 px-6 py-3 shrink-0 {surface === 'default' ? 'bg-base-100' : 'bg-base-200'}">
15
+ <div class="min-w-0">
16
+ <h2 class="text-[22px] font-semibold text-base-content tracking-tight m-0 truncate">{title}</h2>
17
+ {#if subtitle}
18
+ <p class="text-[13px] text-secondary mt-0.5 m-0 truncate">{subtitle}</p>
19
+ {/if}
20
+ </div>
21
+ {#if actions}
22
+ <div class="shrink-0">
23
+ {@render actions()}
24
+ </div>
25
+ {/if}
26
+ </header>
@@ -0,0 +1,54 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+
4
+ interface Props {
5
+ accessibleName: string
6
+ active: boolean
7
+ collapsed: boolean
8
+ onActivate: () => void
9
+ leading?: Snippet
10
+ label?: Snippet
11
+ trailing?: Snippet
12
+ class?: string
13
+ }
14
+
15
+ let {
16
+ accessibleName,
17
+ active,
18
+ collapsed,
19
+ onActivate,
20
+ leading,
21
+ label,
22
+ trailing,
23
+ class: className,
24
+ }: Props = $props()
25
+
26
+ function handleKeydown(event: KeyboardEvent): void {
27
+ if (event.key !== 'Enter' && event.key !== ' ') return
28
+ event.preventDefault()
29
+ onActivate()
30
+ }
31
+ </script>
32
+
33
+ <button
34
+ type="button"
35
+ class={[
36
+ 'relative mx-2 flex min-h-11 w-auto items-center rounded-lg gap-3 py-2.5 cursor-pointer transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary',
37
+ collapsed ? 'justify-center px-0' : 'px-3',
38
+ active ? 'bg-primary/10 text-primary' : 'text-base-content/55 hover:bg-base-200 hover:text-base-content',
39
+ className,
40
+ ]}
41
+ title={collapsed ? accessibleName : undefined}
42
+ aria-label={accessibleName}
43
+ aria-current={active ? 'page' : undefined}
44
+ onclick={onActivate}
45
+ onkeydown={handleKeydown}
46
+ >
47
+ {#if leading}
48
+ <span class="relative shrink-0">{@render leading()}</span>
49
+ {/if}
50
+ {#if !collapsed}
51
+ {#if label}<span class="min-w-0 text-sm font-medium">{@render label()}</span>{/if}
52
+ {#if trailing}<span class="ml-auto shrink-0">{@render trailing()}</span>{/if}
53
+ {/if}
54
+ </button>
@@ -0,0 +1,76 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte'
3
+
4
+ interface Props {
5
+ loading?: boolean
6
+ loadingLabel?: string
7
+ error?: string | null
8
+ errorTitle?: string
9
+ empty?: boolean
10
+ emptyTitle?: string
11
+ emptyDescription?: string | null
12
+ retryLabel?: string
13
+ retryDisabled?: boolean
14
+ onRetry?: () => void
15
+ errorActions?: Snippet
16
+ emptyActions?: Snippet
17
+ children?: Snippet
18
+ }
19
+
20
+ let {
21
+ loading = false,
22
+ loadingLabel = 'Loading…',
23
+ error = null,
24
+ errorTitle = 'Unable to load',
25
+ empty = false,
26
+ emptyTitle = 'Nothing to show',
27
+ emptyDescription = null,
28
+ retryLabel = 'Retry',
29
+ retryDisabled = false,
30
+ onRetry,
31
+ errorActions,
32
+ emptyActions,
33
+ children,
34
+ }: Props = $props()
35
+
36
+ function handleRetry() {
37
+ if (retryDisabled) return
38
+ onRetry?.()
39
+ }
40
+ </script>
41
+
42
+ {#if loading}
43
+ <div class="flex h-full min-h-0 flex-1 flex-col items-center justify-center gap-3 p-6 text-center text-sm text-base-content/70" role="status" aria-live="polite">
44
+ <span class="loading loading-spinner loading-md text-primary" aria-label={loadingLabel}></span>
45
+ <span>{loadingLabel}</span>
46
+ </div>
47
+ {:else if error !== null}
48
+ <div class="flex h-full min-h-0 flex-1 flex-col items-center justify-center gap-3 p-6 text-center text-sm text-base-content/70" role="alert" aria-live="assertive">
49
+ <div class="badge badge-error badge-lg">Issue</div>
50
+ <h3 class="m-0 text-xl font-semibold text-base-content">{errorTitle}</h3>
51
+ {#if error}
52
+ <p class="m-0 max-w-md break-words text-base-content/70">{error}</p>
53
+ {/if}
54
+ {#if errorActions}
55
+ <div class="flex flex-wrap items-center justify-center gap-2 pt-1">
56
+ {@render errorActions()}
57
+ </div>
58
+ {:else if onRetry}
59
+ <button class="btn btn-primary btn-sm" type="button" onclick={handleRetry} disabled={retryDisabled}>{retryLabel}</button>
60
+ {/if}
61
+ </div>
62
+ {:else if empty}
63
+ <div class="flex h-full min-h-0 flex-1 flex-col items-center justify-center gap-3 p-6 text-center text-sm text-base-content/70" role="status" aria-live="polite">
64
+ <h3 class="m-0 text-xl font-semibold text-base-content">{emptyTitle}</h3>
65
+ {#if emptyDescription}
66
+ <p class="m-0 max-w-md text-base-content/70">{emptyDescription}</p>
67
+ {/if}
68
+ {#if emptyActions}
69
+ <div class="flex flex-wrap items-center justify-center gap-2 pt-1">
70
+ {@render emptyActions()}
71
+ </div>
72
+ {/if}
73
+ </div>
74
+ {:else}
75
+ {@render children?.()}
76
+ {/if}
@@ -8,6 +8,7 @@
8
8
  minWidth?: number
9
9
  maxWidth?: number
10
10
  side?: 'left' | 'right'
11
+ label?: string
11
12
  children?: Snippet
12
13
  }
13
14
 
@@ -17,6 +18,7 @@
17
18
  minWidth = 120,
18
19
  maxWidth = 600,
19
20
  side = 'left',
21
+ label = side === 'left' ? 'left' : 'right',
20
22
  children,
21
23
  }: Props = $props()
22
24
 
@@ -119,6 +121,10 @@
119
121
  style="cursor: col-resize"
120
122
  role="separator"
121
123
  aria-orientation="vertical"
124
+ aria-label="Resize {label} panel"
125
+ aria-valuemin={minWidth}
126
+ aria-valuemax={maxWidth}
127
+ aria-valuenow={width}
122
128
  tabindex="0"
123
129
  onmousedown={onMouseDown}
124
130
  ondblclick={onDblClick}
@@ -137,6 +143,10 @@
137
143
  style="cursor: col-resize"
138
144
  role="separator"
139
145
  aria-orientation="vertical"
146
+ aria-label="Resize {label} panel"
147
+ aria-valuemin={minWidth}
148
+ aria-valuemax={maxWidth}
149
+ aria-valuenow={width}
140
150
  tabindex="0"
141
151
  onmousedown={onMouseDown}
142
152
  ondblclick={onDblClick}
@@ -0,0 +1,9 @@
1
+ # Vendored file-type icons
2
+
3
+ These SVGs are copied from the MIT-licensed `vscode-material-icons` package
4
+ (Material Icon Theme). Do not edit by hand.
5
+
6
+ Regenerate after changing the icon maps / `BUNDLED_ICON_NAMES` in
7
+ `../../fileIcons.ts` (and keep the `NAMES` list in the generator in sync):
8
+
9
+ pnpm --filter @openforge-app/plugin-sdk run icons:generate
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M19.56348,22A5.56981,5.56981,0,0,1,14,16.43652v-2.873A5.56977,5.56977,0,0,1,19.56348,8H24V2H19.56348A11.56349,11.56349,0,0,0,8,13.56348v2.873A11.56354,11.56354,0,0,0,19.56348,28H24V22Z" style="fill:#0288d1"/></svg>
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M20 19V7H4v12h16m0-16a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16m-7 14v-2h5v2h-5m-3.42-4L5.57 9H8.4l3.3 3.3c.39.39.39 1.03 0 1.42L8.42 17H5.59z" fill="#ff7043"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><polygon points="28 14 28 10 26 10 26 14 20 14 20 10 18 10 18 14 14 14 14 16 18 16 18 20 20 20 20 16 26 16 26 20 28 20 28 16 32 16 32 14 28 14" style="fill:#0288d1"/><path d="M13.56348,22A5.56981,5.56981,0,0,1,8,16.43652v-2.873A5.56977,5.56977,0,0,1,13.56348,8H18V2H13.56348A11.56349,11.56349,0,0,0,2,13.56348v2.873A11.56354,11.56354,0,0,0,13.56348,28H18V22Z" style="fill:#0288d1"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M30,14V12H28V8H26v4H24V8H22v4H20v2h2v2H20v2h2v4h2V18h2v4h2V18h2V16H28V14Zm-4,2H24V14h2Z" style="fill:#0288d1"/><path d="M13.56348,22A5.56981,5.56981,0,0,1,8,16.43652v-2.873A5.56977,5.56977,0,0,1,13.56348,8H18V2H13.56348A11.56349,11.56349,0,0,0,2,13.56348v2.873A11.56354,11.56354,0,0,0,13.56348,28H18V22Z" style="fill:#0288d1"/></svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <polygon points="29.18 4 25.61 22.36 25.28 24 20.54 25.57 17.26 26.66 13.21 28 2.87 24.05 4.05 18 8.25 18 7.81 20.85 14.15 23.27 14.93 23.01 21.45 20.85 21.62 20.02 22.41 16 4.44 16 5.18 12.24 5.23 12 23.19 12 23.97 8 6 8 6.78 4 29.18 4" style="fill: #42a5f5"/>
3
+ </svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M16,24c-5.525,0-10-.9-10-2v4c0,1.1,4.475,2,10,2s10-.9,10-2V22C26,23.1,21.525,24,16,24Z" style="fill:#ffca28"/><path d="M16,16c-5.525,0-10-.9-10-2v4c0,1.1,4.475,2,10,2s10-.9,10-2V14C26,15.1,21.525,16,16,16Z" style="fill:#ffca28"/><path d="M16,4C10.47717,4,6,4.89539,6,6v4c0,1.1,4.475,2,10,2s10-.9,10-2V6C26,4.89539,21.52283,4,16,4Z" style="fill:#ffca28"/></svg>
@@ -0,0 +1 @@
1
+ <svg version="1.1" viewBox="0 0 24 24" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m21.81 10.25c-0.06-0.04-0.56-0.43-1.64-0.43-0.28 0-0.56 0.03-0.84 0.08-0.21-1.4-1.38-2.11-1.43-2.14l-0.29-0.17-0.18 0.27c-0.24 0.36-0.43 0.77-0.51 1.19-0.2 0.8-0.08 1.56 0.33 2.21-0.49 0.28-1.29 0.35-1.46 0.35h-13.17c-0.34 0-0.62 0.28-0.62 0.63 0 1.15 0.18 2.3 0.58 3.38 0.45 1.19 1.13 2.07 2 2.61 0.98 0.6 2.59 0.94 4.42 0.94 0.79 0 1.61-0.07 2.42-0.22 1.12-0.2 2.2-0.59 3.19-1.16 0.82-0.47 1.55-1.07 2.17-1.79 1.05-1.17 1.67-2.5 2.12-3.65h0.19c1.14 0 1.85-0.46 2.24-0.85 0.26-0.24 0.45-0.53 0.59-0.87l0.08-0.24-0.19-0.14m-17.96 0.99h1.76c0.08 0 0.16-0.07 0.16-0.16v-1.58c0-0.08-0.07-0.16-0.16-0.16h-1.76c-0.09 0-0.16 0.07-0.16 0.16v1.58c0.01 0.09 0.07 0.16 0.16 0.16m2.43 0h1.76c0.08 0 0.16-0.07 0.16-0.16v-1.58c0-0.08-0.07-0.16-0.16-0.16h-1.76c-0.09 0-0.16 0.07-0.16 0.16v1.58c0.01 0.09 0.07 0.16 0.16 0.16m2.47 0h1.75c0.1 0 0.17-0.07 0.17-0.16v-1.58c0-0.08-0.06-0.16-0.17-0.16h-1.75c-0.08 0-0.15 0.07-0.15 0.16v1.58c0 0.09 0.06 0.16 0.15 0.16m2.44 0h1.77c0.08 0 0.15-0.07 0.15-0.16v-1.58c0-0.08-0.06-0.16-0.15-0.16h-1.77c-0.08 0-0.15 0.07-0.15 0.16v1.58c0 0.09 0.07 0.16 0.15 0.16m-4.91-2.24h1.76c0.08 0 0.16-0.09 0.16-0.18v-1.57c0-0.09-0.07-0.16-0.16-0.16h-1.76c-0.09 0-0.16 0.06-0.16 0.16v1.57c0.01 0.09 0.07 0.18 0.16 0.18m2.47 0h1.75c0.1 0 0.17-0.09 0.17-0.18v-1.57c0-0.09-0.06-0.16-0.17-0.16h-1.75c-0.08 0-0.15 0.06-0.15 0.16v1.57c0 0.09 0.06 0.18 0.15 0.18m2.44 0h1.77c0.08 0 0.15-0.09 0.15-0.18v-1.57c0-0.09-0.07-0.16-0.15-0.16h-1.77c-0.08 0-0.15 0.06-0.15 0.16v1.57c0 0.09 0.07 0.18 0.15 0.18m0-2.28h1.77c0.08 0 0.15-0.07 0.15-0.16v-1.56c0-0.1-0.07-0.17-0.15-0.17h-1.77c-0.08 0-0.15 0.06-0.15 0.17v1.56c0 0.08 0.07 0.16 0.15 0.16m2.46 4.52h1.76c0.09 0 0.16-0.07 0.16-0.16v-1.58c0-0.08-0.07-0.16-0.16-0.16h-1.76c-0.08 0-0.15 0.07-0.15 0.16v1.58c0 0.09 0.07 0.16 0.15 0.16" fill="#0087c9"/></svg>
@@ -0,0 +1,4 @@
1
+ <svg width="24px" height="24px" version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M0 0h24v24H0V0z" fill="none"/>
3
+ <path d="M8 16h8v2H8zm0-4h8v2H8zm6-10H6c-1.1 0-2 .9-2 2v16c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11z" fill="#42a5f5"/>
4
+ </svg>
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m5 2H6v16h12v-9h-7V4z" fill="#90a4ae" /></svg>
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="M28.96692,12H9.44152a2,2,0,0,0-1.89737,1.36754L4,24V10H28a2,2,0,0,0-2-2H15.1241a2,2,0,0,1-1.28038-.46357L12.5563,6.46357A2,2,0,0,0,11.27592,6H4A2,2,0,0,0,2,8V24a2,2,0,0,0,2,2H26l4.80523-11.21213A2,2,0,0,0,28.96692,12Z" fill="#90a4ae" /></svg>
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="M13.84376,7.53645l-1.28749-1.0729A2,2,0,0,0,11.27591,6H4A2,2,0,0,0,2,8V24a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V10a2,2,0,0,0-2-2H15.12412A2,2,0,0,1,13.84376,7.53645Z" fill="#90a4ae" /></svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <path d="M13.17157,2.82843,11.78,4.22l1.91,1.91,2,2A2.98634,2.98634,0,0,1,20,10.81a3.24762,3.24762,0,0,1-.31,1.31l2.06,2a2.68305,2.68305,0,0,1,3.37.57A2.86047,2.86047,0,0,1,26,16.80725a3.01491,3.01491,0,0,1-.856,2.10855A2.89268,2.89268,0,0,1,23,19.81a2.92912,2.92912,0,0,1-2.13-.87,2.69441,2.69441,0,0,1-.56-3.38l-2-2.06a2.54662,2.54662,0,0,1-.31.12V20a3.02384,3.02384,0,0,1,1.44,1.09A2.92105,2.92105,0,0,1,20,22.81a2.88288,2.88288,0,0,1-.878,2.128,2.98224,2.98224,0,0,1-2.04815.87125A2.98125,2.98125,0,0,1,14.56,21.09,3.02384,3.02384,0,0,1,16,20V13.62a2.9626,2.9626,0,0,1-1.44-1.09A2.89859,2.89859,0,0,1,14,10.81a2.86805,2.86805,0,0,1,.31-1.31l-3.9-3.9L2.83113,13.17157a4,4,0,0,0-.00135,5.65821L13.17157,29.17157a4,4,0,0,0,5.65686,0L29.17157,18.82843a4,4,0,0,0,0-5.65686L18.82843,2.82843A4,4,0,0,0,13.17157,2.82843Z" style="fill: #e64a19"/>
3
+ </svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect x="2" y="12" width="4" height="2" style="fill:#00acc1"/><rect y="16" width="6" height="2" style="fill:#00acc1"/><rect x="4" y="20" width="2" height="2" style="fill:#00acc1"/><path d="M20.9541,15H14v3H17.239a4.41613,4.41613,0,0,1-3.531,2,2.64933,2.64933,0,0,1-2.05329-.85828,2.85507,2.85507,0,0,1-.62756-2.27887A4.51451,4.51451,0,0,1,15.29205,13a2.72468,2.72468,0,0,1,1.74872.58368L20.0025,12.399A5.60478,5.60478,0,0,0,15.29205,10a7.52569,7.52569,0,0,0-7.24292,6.5A5.61427,5.61427,0,0,0,13.708,23a7.52569,7.52569,0,0,0,7.24292-6.5A6.40392,6.40392,0,0,0,20.9541,15Z" style="fill:#00acc1"/><path d="M26.29205,10a7.52569,7.52569,0,0,0-7.24292,6.5A5.61427,5.61427,0,0,0,24.708,23a7.5258,7.5258,0,0,0,7.243-6.5A5.61428,5.61428,0,0,0,26.29205,10ZM28.973,16.13708A4.51463,4.51463,0,0,1,24.708,20a2.64906,2.64906,0,0,1-2.05323-.85822,2.85508,2.85508,0,0,1-.62762-2.27893A4.51451,4.51451,0,0,1,26.29205,13a2.64919,2.64919,0,0,1,2.05329.85822A2.855,2.855,0,0,1,28.973,16.13708Z" style="fill:#00acc1"/></svg>
@@ -0,0 +1,20 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <rect x="6" y="20" width="20" height="2" style="fill: #ec407a"/>
3
+ <circle cx="7" cy="21" r="3" style="fill: #ec407a"/>
4
+ <circle cx="16" cy="27" r="3" style="fill: #ec407a"/>
5
+ <circle cx="25" cy="21" r="3" style="fill: #ec407a"/>
6
+ <rect x="6" y="10" width="20" height="2" style="fill: #ec407a"/>
7
+ <circle cx="7" cy="11" r="3" style="fill: #ec407a"/>
8
+ <circle cx="16" cy="5" r="3" style="fill: #ec407a"/>
9
+ <circle cx="25" cy="11" r="3" style="fill: #ec407a"/>
10
+ <rect x="6" y="12" width="2" height="10" style="fill: #ec407a"/>
11
+ <rect x="24" y="10" width="2" height="12" style="fill: #ec407a"/>
12
+ <rect x="3.57231" y="22.70513" width="13.54352" height="2" transform="translate(13.44481 -1.97206) rotate(30.46034)" style="fill: #ec407a"/>
13
+ <rect x="13.57231" y="23.29487" width="13.54352" height="2" transform="translate(50.19633 34.92331) rotate(149.53966)" style="fill: #ec407a"/>
14
+ <rect x="4.59753" y="6.57997" width="13.54352" height="2" transform="translate(-2.29249 7.3106) rotate(-32.56204)" style="fill: #ec407a"/>
15
+ <rect x="13.85895" y="6.57997" width="13.54352" height="2" transform="translate(33.93883 25.07214) rotate(-147.43796)" style="fill: #ec407a"/>
16
+ <rect x="4.59753" y="23.42003" width="13.54352" height="2" transform="translate(14.93032 -2.28049) rotate(32.56204)" style="fill: #ec407a"/>
17
+ <rect x="13.85895" y="23.42003" width="13.54352" height="2" transform="translate(51.16163 33.89775) rotate(147.43796)" style="fill: #ec407a"/>
18
+ <rect x="1.10394" y="12.6161" width="21.47138" height="2" transform="translate(-5.75891 17.59934) rotate(-61.7454)" style="fill: #ec407a"/>
19
+ <rect x="9.42467" y="12.6161" width="21.47138" height="2" transform="translate(17.71031 37.82014) rotate(-118.2546)" style="fill: #ec407a"/>
20
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <path d="M18.5,11A5.48994,5.48994,0,0,0,14,13.34375V4H8V28h6V17a2,2,0,0,1,4,0V28h6V16.5A5.5,5.5,0,0,0,18.5,11Z" style="fill: #0288d1"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <path d="M4,4,6,26l10,2,10-2L28,4Zm19.72,7H11.28l.29,3H23.43l-.80243,9.335L15.99,25,9.35522,23.3537,8.93,19h3.02l.19,2,3.86.77L19.84,21l.29-4H8.84L8,8H24Z" style="fill: #e44d26"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg version="1.1" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="m13 9h5.5l-5.5-5.5v5.5m-7-7h8l6 6v12a2 2 0 0 1-2 2h-12c-1.11 0-2-0.9-2-2v-16c0-1.11 0.89-2 2-2m0 18h9 3v-8l-4 4-2-2-6 6m2-11a2 2 0 0 0-2 2 2 2 0 0 0 2 2 2 2 0 0 0 2-2 2 2 0 0 0-2-2z" fill="#26a69f"/>
3
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <g>
3
+ <rect x="4" y="26" width="24" height="2" style="fill: #f44336"/>
4
+ <path d="M28,4H7A1,1,0,0,0,6,5V18a4,4,0,0,0,4,4H20a4,4,0,0,0,4-4V14h4a2,2,0,0,0,2-2V6A2,2,0,0,0,28,4Zm0,8H24V6h4Z" style="fill: #f44336"/>
5
+ </g>
6
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="16" height="16" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="m2 2h12v12h-12v-12m3.1533 10.027c0.26667 0.56667 0.79333 1.0333 1.6933 1.0333 1 0 1.6867-0.53333 1.6867-1.7v-3.8533h-1.1333v3.8267c0 0.57333-0.23333 0.72-0.6 0.72-0.38667 0-0.54667-0.26667-0.72667-0.58l-0.92 0.55333m3.9867-0.12c0.33333 0.65333 1.0067 1.1533 2.06 1.1533 1.0667 0 1.8667-0.55333 1.8667-1.5733 0-0.94-0.54-1.36-1.5-1.7733l-0.28-0.12c-0.48667-0.20667-0.69333-0.34667-0.69333-0.68 0-0.27333 0.20667-0.48667 0.54-0.48667 0.32 0 0.53333 0.14 0.72667 0.48667l0.87333-0.58c-0.36667-0.64-0.88667-0.88667-1.6-0.88667-1.0067 0-1.6533 0.64-1.6533 1.4867 0 0.92 0.54 1.3533 1.3533 1.7l0.28 0.12c0.52 0.22667 0.82667 0.36667 0.82667 0.75333 0 0.32-0.3 0.55333-0.76667 0.55333-0.55333 0-0.87333-0.28667-1.1133-0.68667z" fill="#ffca28" stroke-width=".66667"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="24" height="24" version="1.1" viewBox="0 -960 960 960" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M560-160v-80h120q17 0 28.5-11.5T720-280v-80q0-38 22-69t58-44v-14q-36-13-58-44t-22-69v-80q0-17-11.5-28.5T680-720H560v-80h120q50 0 85 35t35 85v80q0 17 11.5 28.5T840-560h40v160h-40q-17 0-28.5 11.5T800-360v80q0 50-35 85t-85 35H560Zm-280 0q-50 0-85-35t-35-85v-80q0-17-11.5-28.5T120-400H80v-160h40q17 0 28.5-11.5T160-600v-80q0-50 35-85t85-35h120v80H280q-17 0-28.5 11.5T240-680v80q0 38-22 69t-58 44v14q36 13 58 44t22 69v80q0 17 11.5 28.5T280-240h120v80H280Z" fill="#f9a825"/>
3
+ </svg>
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="a" x1="1.725" x2="22.185" y1="22.67" y2="1.982" gradientTransform="translate(1.306 1.129) scale(.89324)" gradientUnits="userSpaceOnUse"><stop stop-color="#0296d8" offset="0"/><stop stop-color="#8371d9" offset="1"/></linearGradient><linearGradient id="b" x1="1.869" x2="22.798" y1="22.382" y2="3.377" gradientTransform="translate(1.323 1.129) scale(.89324)" gradientUnits="userSpaceOnUse"><stop stop-color="#cb55c0" offset="0"/><stop stop-color="#f28e0e" offset="1"/></linearGradient></defs><path d="M2.975 2.976v18.048h18.05v-.03l-4.478-4.511-4.48-4.515 4.48-4.515 4.443-4.477z" fill="url(#a)"/><path d="m12.223 2.976-9.23 9.23v8.818h.083l9.032-9.032-.024-.024 4.48-4.515 4.443-4.477h-8.784z" fill="url(#b)"/></svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <path d="M25,12H22V8A6,6,0,0,0,10,8v4H7a1,1,0,0,0-1,1V29a1,1,0,0,0,1,1H25a1,1,0,0,0,1-1V13A1,1,0,0,0,25,12ZM14,8a2,2,0,0,1,4,0v4H14Zm2,17a4,4,0,1,1,4-4A4,4,0,0,1,16,25Z" style="fill: #ffd54f"/>
3
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <g>
3
+ <polygon points="14 10 10 13.5 6 10 4 10 4 22 8 22 8 16 10 18 12 16 12 22 16 22 16 10 14 10" style="fill: #42a5f5"/>
4
+ <polygon points="26 16 26 10 22 10 22 16 18 16 24 24 30 16 26 16" style="fill: #42a5f5"/>
5
+ </g>
6
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <g>
3
+ <path d="M16,20.00258v2h4a2,2,0,0,0,2-2v-2a2,2,0,0,0-2-2H18v-2h4v-2H18a2,2,0,0,0-2,2v2a2,2,0,0,0,2,2h2v2Z" style="fill: #8bc34a"/>
4
+ <path d="M16,3.00258l-12,7v14l4,2h6v-13.5a.5.5,0,0,0-.5-.5h-1a.5.5,0,0,0-.5.5v11.5H7.99986L6,22.96875V11.15132L16,5.318l10,5.83331V22.85384L16,28.68715l-1.74542-1.022L13,29.25258l3,1.75,12-7v-14Z" style="fill: #8bc34a"/>
5
+ </g>
6
+ </svg>