@openvcs/sdk 0.2.11 → 0.2.13
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/lib/runtime/index.d.ts +1 -0
- package/lib/runtime/index.js +3 -1
- package/lib/runtime/menu.d.ts +11 -11
- package/lib/runtime/menu.js +61 -97
- package/lib/runtime/modal.d.ts +74 -0
- package/lib/runtime/modal.js +95 -0
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +1 -0
- package/lib/types/modal.d.ts +129 -0
- package/lib/types/modal.js +4 -0
- package/lib/types/plugin.d.ts +2 -0
- package/package.json +1 -1
- package/src/lib/runtime/index.ts +1 -0
- package/src/lib/runtime/menu.ts +65 -114
- package/src/lib/runtime/modal.ts +172 -0
- package/src/lib/types/index.ts +1 -0
- package/src/lib/types/modal.ts +152 -0
- package/src/lib/types/plugin.ts +2 -0
- package/test/modal.test.ts +24 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// Copyright © 2025-2026 OpenVCS Contributors
|
|
2
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
ModalButtonDefinition,
|
|
6
|
+
ModalButtonVariant,
|
|
7
|
+
ModalContentAlign,
|
|
8
|
+
ModalInputDefinition,
|
|
9
|
+
ModalInputKind,
|
|
10
|
+
ModalListDefinition,
|
|
11
|
+
ModalListRowDefinition,
|
|
12
|
+
ModalSelectDefinition,
|
|
13
|
+
ModalSelectOptionDefinition,
|
|
14
|
+
PluginModalContentItem,
|
|
15
|
+
PluginModalDefinition,
|
|
16
|
+
} from '../types/modal.js';
|
|
17
|
+
|
|
18
|
+
import { invoke } from './menu.js';
|
|
19
|
+
|
|
20
|
+
/** Describes the options accepted by `ModalBuilder.button()`. */
|
|
21
|
+
export interface ModalBuilderButtonOptions {
|
|
22
|
+
/** Stores the optional tooltip text. */
|
|
23
|
+
title?: string;
|
|
24
|
+
/** Stores the visual button variant. */
|
|
25
|
+
variant?: ModalButtonVariant;
|
|
26
|
+
/** Stores the alignment hint. */
|
|
27
|
+
align?: ModalContentAlign;
|
|
28
|
+
/** Stores a static payload merged into the action payload. */
|
|
29
|
+
payload?: Record<string, unknown>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Describes the options accepted by `ModalBuilder.text()`. */
|
|
33
|
+
export interface ModalBuilderTextOptions {
|
|
34
|
+
/** Stores the optional tooltip text. */
|
|
35
|
+
title?: string;
|
|
36
|
+
/** Stores the alignment hint. */
|
|
37
|
+
align?: ModalContentAlign;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Describes the options accepted by `ModalBuilder.input()`. */
|
|
41
|
+
export interface ModalBuilderInputOptions {
|
|
42
|
+
/** Stores the input kind. */
|
|
43
|
+
kind?: ModalInputKind;
|
|
44
|
+
/** Stores the default value. */
|
|
45
|
+
value?: string;
|
|
46
|
+
/** Stores the placeholder text. */
|
|
47
|
+
placeholder?: string;
|
|
48
|
+
/** Stores whether the field is required. */
|
|
49
|
+
required?: boolean;
|
|
50
|
+
/** Stores the alignment hint. */
|
|
51
|
+
align?: ModalContentAlign;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Describes the options accepted by `ModalBuilder.select()`. */
|
|
55
|
+
export interface ModalBuilderSelectOptions {
|
|
56
|
+
/** Stores the available options. */
|
|
57
|
+
options: ModalSelectOptionDefinition[];
|
|
58
|
+
/** Stores the default value. */
|
|
59
|
+
value?: string;
|
|
60
|
+
/** Stores the alignment hint. */
|
|
61
|
+
align?: ModalContentAlign;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Describes the options accepted by `ModalBuilder.list()`. */
|
|
65
|
+
export interface ModalBuilderListOptions {
|
|
66
|
+
/** Stores the list label. */
|
|
67
|
+
label?: string;
|
|
68
|
+
/** Stores the empty-state text. */
|
|
69
|
+
emptyText?: string;
|
|
70
|
+
/** Stores the alignment hint. */
|
|
71
|
+
align?: ModalContentAlign;
|
|
72
|
+
/** Stores the list rows. */
|
|
73
|
+
items: ModalListRowDefinition[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Builds a structured modal definition with a fluent class API. */
|
|
77
|
+
export class ModalBuilder {
|
|
78
|
+
private readonly definition: PluginModalDefinition;
|
|
79
|
+
|
|
80
|
+
/** Creates a new modal builder with the provided title. */
|
|
81
|
+
constructor(title: string) {
|
|
82
|
+
this.definition = {
|
|
83
|
+
title: String(title || '').trim(),
|
|
84
|
+
content: [],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Adds a text block to the modal body. */
|
|
89
|
+
text(content: string, options: ModalBuilderTextOptions = {}): this {
|
|
90
|
+
this.definition.content.push({
|
|
91
|
+
type: 'text',
|
|
92
|
+
content: String(content || ''),
|
|
93
|
+
...(options.title ? { title: options.title } : {}),
|
|
94
|
+
...(options.align ? { align: options.align } : {}),
|
|
95
|
+
});
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Adds a separator to the modal body. */
|
|
100
|
+
separator(): this {
|
|
101
|
+
this.definition.content.push({ type: 'separator' });
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Adds a button to the modal body. */
|
|
106
|
+
button(id: string, content: string, options: ModalBuilderButtonOptions = {}): this {
|
|
107
|
+
this.definition.content.push({
|
|
108
|
+
type: 'button',
|
|
109
|
+
id: String(id || '').trim(),
|
|
110
|
+
content: String(content || ''),
|
|
111
|
+
...(options.title ? { title: options.title } : {}),
|
|
112
|
+
...(options.variant && options.variant !== 'default' ? { variant: options.variant } : {}),
|
|
113
|
+
...(options.align ? { align: options.align } : {}),
|
|
114
|
+
...(options.payload ? { payload: options.payload } : {}),
|
|
115
|
+
});
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Adds a text input to the modal body. */
|
|
120
|
+
input(id: string, label: string, options: ModalBuilderInputOptions = {}): this {
|
|
121
|
+
this.definition.content.push({
|
|
122
|
+
type: 'input',
|
|
123
|
+
id: String(id || '').trim(),
|
|
124
|
+
label: String(label || '').trim(),
|
|
125
|
+
...(options.kind ? { kind: options.kind } : {}),
|
|
126
|
+
...(options.value !== undefined ? { value: options.value } : {}),
|
|
127
|
+
...(options.placeholder ? { placeholder: options.placeholder } : {}),
|
|
128
|
+
...(options.required ? { required: true } : {}),
|
|
129
|
+
...(options.align ? { align: options.align } : {}),
|
|
130
|
+
});
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Adds a select field to the modal body. */
|
|
135
|
+
select(id: string, label: string, options: ModalBuilderSelectOptions): this {
|
|
136
|
+
this.definition.content.push({
|
|
137
|
+
type: 'select',
|
|
138
|
+
id: String(id || '').trim(),
|
|
139
|
+
label: String(label || '').trim(),
|
|
140
|
+
options: Array.isArray(options.options) ? options.options : [],
|
|
141
|
+
...(options.value !== undefined ? { value: options.value } : {}),
|
|
142
|
+
...(options.align ? { align: options.align } : {}),
|
|
143
|
+
});
|
|
144
|
+
return this;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Adds a list block to the modal body. */
|
|
148
|
+
list(id: string, options: ModalBuilderListOptions): this {
|
|
149
|
+
this.definition.content.push({
|
|
150
|
+
type: 'list',
|
|
151
|
+
id: String(id || '').trim(),
|
|
152
|
+
...(options.label ? { label: options.label } : {}),
|
|
153
|
+
...(options.emptyText ? { emptyText: options.emptyText } : {}),
|
|
154
|
+
...(options.align ? { align: options.align } : {}),
|
|
155
|
+
items: Array.isArray(options.items) ? options.items : [],
|
|
156
|
+
});
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Returns the serialized modal payload. */
|
|
161
|
+
build(): PluginModalDefinition {
|
|
162
|
+
return {
|
|
163
|
+
title: this.definition.title,
|
|
164
|
+
content: this.definition.content.map((item) => ({ ...item })) as PluginModalContentItem[],
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Requests the host to open the modal with the current definition. */
|
|
169
|
+
async open(): Promise<void> {
|
|
170
|
+
await invoke('open_plugin_modal', { modal: this.build() });
|
|
171
|
+
}
|
|
172
|
+
}
|
package/src/lib/types/index.ts
CHANGED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Copyright © 2025-2026 OpenVCS Contributors
|
|
2
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
/** Describes horizontal alignment hints for modal content. */
|
|
5
|
+
export type ModalContentAlign = 'left' | 'centered' | 'right';
|
|
6
|
+
|
|
7
|
+
/** Describes visual emphasis for modal buttons. */
|
|
8
|
+
export type ModalButtonVariant = 'default' | 'primary' | 'danger';
|
|
9
|
+
|
|
10
|
+
/** Describes the text input kinds supported by plugin modals. */
|
|
11
|
+
export type ModalInputKind = 'text' | 'search' | 'password' | 'url' | 'number';
|
|
12
|
+
|
|
13
|
+
/** Describes one button option rendered inside a modal. */
|
|
14
|
+
export interface ModalButtonDefinition {
|
|
15
|
+
/** Stores the action id triggered when the button is pressed. */
|
|
16
|
+
id: string;
|
|
17
|
+
/** Stores the button label. */
|
|
18
|
+
content: string;
|
|
19
|
+
/** Stores the optional tooltip text. */
|
|
20
|
+
title?: string;
|
|
21
|
+
/** Stores the visual button variant. */
|
|
22
|
+
variant?: ModalButtonVariant;
|
|
23
|
+
/** Stores the button alignment hint. */
|
|
24
|
+
align?: ModalContentAlign;
|
|
25
|
+
/** Stores a static payload merged into the action payload. */
|
|
26
|
+
payload?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Describes one text block rendered inside a modal. */
|
|
30
|
+
export interface ModalTextDefinition {
|
|
31
|
+
/** Always stores `text`. */
|
|
32
|
+
type: 'text';
|
|
33
|
+
/** Stores the block text. */
|
|
34
|
+
content: string;
|
|
35
|
+
/** Stores an optional tooltip. */
|
|
36
|
+
title?: string;
|
|
37
|
+
/** Stores the alignment hint. */
|
|
38
|
+
align?: ModalContentAlign;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Describes one separator rendered inside a modal. */
|
|
42
|
+
export interface ModalSeparatorDefinition {
|
|
43
|
+
/** Always stores `separator`. */
|
|
44
|
+
type: 'separator';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Describes one button rendered inside a modal body. */
|
|
48
|
+
export interface ModalButtonItemDefinition extends ModalButtonDefinition {
|
|
49
|
+
/** Always stores `button`. */
|
|
50
|
+
type: 'button';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Describes one text input rendered inside a modal. */
|
|
54
|
+
export interface ModalInputDefinition {
|
|
55
|
+
/** Always stores `input`. */
|
|
56
|
+
type: 'input';
|
|
57
|
+
/** Stores the input field id. */
|
|
58
|
+
id: string;
|
|
59
|
+
/** Stores the label text. */
|
|
60
|
+
label: string;
|
|
61
|
+
/** Stores the input kind. */
|
|
62
|
+
kind?: ModalInputKind;
|
|
63
|
+
/** Stores the default value. */
|
|
64
|
+
value?: string;
|
|
65
|
+
/** Stores the placeholder text. */
|
|
66
|
+
placeholder?: string;
|
|
67
|
+
/** Stores whether the field is required. */
|
|
68
|
+
required?: boolean;
|
|
69
|
+
/** Stores the alignment hint. */
|
|
70
|
+
align?: ModalContentAlign;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Describes one select option rendered inside a modal. */
|
|
74
|
+
export interface ModalSelectOptionDefinition {
|
|
75
|
+
/** Stores the option label. */
|
|
76
|
+
label: string;
|
|
77
|
+
/** Stores the option value. */
|
|
78
|
+
value: string;
|
|
79
|
+
/** Stores whether the option is selected by default. */
|
|
80
|
+
selected?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Describes one select field rendered inside a modal. */
|
|
84
|
+
export interface ModalSelectDefinition {
|
|
85
|
+
/** Always stores `select`. */
|
|
86
|
+
type: 'select';
|
|
87
|
+
/** Stores the field id. */
|
|
88
|
+
id: string;
|
|
89
|
+
/** Stores the label text. */
|
|
90
|
+
label: string;
|
|
91
|
+
/** Stores the available options. */
|
|
92
|
+
options: ModalSelectOptionDefinition[];
|
|
93
|
+
/** Stores the default value. */
|
|
94
|
+
value?: string;
|
|
95
|
+
/** Stores the alignment hint. */
|
|
96
|
+
align?: ModalContentAlign;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Describes one row-level action rendered in a list item. */
|
|
100
|
+
export interface ModalListActionDefinition extends ModalButtonDefinition {
|
|
101
|
+
/** Always stores `button`. */
|
|
102
|
+
type: 'button';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Describes one list row rendered inside a modal list. */
|
|
106
|
+
export interface ModalListRowDefinition {
|
|
107
|
+
/** Stores the row id. */
|
|
108
|
+
id: string;
|
|
109
|
+
/** Stores the row title. */
|
|
110
|
+
title: string;
|
|
111
|
+
/** Stores a short status label. */
|
|
112
|
+
status?: string;
|
|
113
|
+
/** Stores a secondary metadata label. */
|
|
114
|
+
meta?: string;
|
|
115
|
+
/** Stores longer descriptive text. */
|
|
116
|
+
description?: string;
|
|
117
|
+
/** Stores the row actions. */
|
|
118
|
+
actions?: ModalListActionDefinition[];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Describes one list block rendered inside a modal. */
|
|
122
|
+
export interface ModalListDefinition {
|
|
123
|
+
/** Always stores `list`. */
|
|
124
|
+
type: 'list';
|
|
125
|
+
/** Stores the list id. */
|
|
126
|
+
id: string;
|
|
127
|
+
/** Stores the list label. */
|
|
128
|
+
label?: string;
|
|
129
|
+
/** Stores the empty-state text. */
|
|
130
|
+
emptyText?: string;
|
|
131
|
+
/** Stores the alignment hint. */
|
|
132
|
+
align?: ModalContentAlign;
|
|
133
|
+
/** Stores the list rows. */
|
|
134
|
+
items: ModalListRowDefinition[];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Describes one plugin modal content item. */
|
|
138
|
+
export type PluginModalContentItem =
|
|
139
|
+
| ModalTextDefinition
|
|
140
|
+
| ModalSeparatorDefinition
|
|
141
|
+
| ModalButtonItemDefinition
|
|
142
|
+
| ModalInputDefinition
|
|
143
|
+
| ModalSelectDefinition
|
|
144
|
+
| ModalListDefinition;
|
|
145
|
+
|
|
146
|
+
/** Describes the structured payload used to render a plugin modal. */
|
|
147
|
+
export interface PluginModalDefinition {
|
|
148
|
+
/** Stores the modal title. */
|
|
149
|
+
title: string;
|
|
150
|
+
/** Stores the ordered list of modal content items. */
|
|
151
|
+
content: PluginModalContentItem[];
|
|
152
|
+
}
|
package/src/lib/types/plugin.ts
CHANGED
|
@@ -32,6 +32,8 @@ export type PluginSettingsValue = Record<string, unknown>;
|
|
|
32
32
|
export interface PluginHandleActionParams extends RequestParams {
|
|
33
33
|
/** Stores the action id selected by the user. */
|
|
34
34
|
action_id?: string;
|
|
35
|
+
/** Stores an optional payload supplied by the triggering UI. */
|
|
36
|
+
payload?: Record<string, unknown>;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
/** Describes the params shape for plugin settings callbacks. */
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Copyright © 2025-2026 OpenVCS Contributors
|
|
2
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
|
+
|
|
4
|
+
import assert from 'node:assert/strict';
|
|
5
|
+
import { describe, it } from 'node:test';
|
|
6
|
+
|
|
7
|
+
import { ModalBuilder } from '../src/lib/runtime/index.js';
|
|
8
|
+
|
|
9
|
+
describe('ModalBuilder', () => {
|
|
10
|
+
it('builds a structured modal definition', () => {
|
|
11
|
+
const modal = new ModalBuilder('Manage Submodules')
|
|
12
|
+
.text('Hello, World!')
|
|
13
|
+
.button('new-button', 'Test button, push me!', { align: 'centered' })
|
|
14
|
+
.build();
|
|
15
|
+
|
|
16
|
+
assert.deepStrictEqual(modal, {
|
|
17
|
+
title: 'Manage Submodules',
|
|
18
|
+
content: [
|
|
19
|
+
{ type: 'text', content: 'Hello, World!' },
|
|
20
|
+
{ type: 'button', id: 'new-button', content: 'Test button, push me!', align: 'centered' },
|
|
21
|
+
],
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|