Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sisilicon
GitHub Repository: sisilicon/worldedit-be
Path: blob/master/src/editor/pane/builder.ts
1784 views
1
import { Vector3 } from "@minecraft/server";
2
import {
3
IBoolPropertyItemOptions,
4
IButtonPropertyItemOptions,
5
IComboBoxPropertyItemOptions,
6
IDropdownPropertyItem,
7
IDropdownPropertyItemEntry,
8
IDropdownPropertyItemOptions,
9
IModalOverlayPane,
10
IModalTool,
11
INumberPropertyItemOptions,
12
IObservable,
13
IPlayerUISession,
14
IProgressIndicatorPropertyItemOptions,
15
IPropertyItemBase,
16
IPropertyPane,
17
IRootPropertyPane,
18
IStringPropertyItemOptions,
19
ISubPanePropertyItem,
20
ISubPanePropertyItemOptions,
21
ITextPropertyItemOptions,
22
IToggleGroupPropertyItemOptions,
23
IVector3PropertyItemOptions,
24
LocalizedString,
25
makeObservable,
26
} from "@minecraft/server-editor";
27
import { generateId } from "@notbeer-api";
28
import { getSession } from "server/sessions";
29
30
type MayObservable<T> = IObservable<T> | T;
31
32
interface BasePaneItem {
33
type: string;
34
uniqueId?: string;
35
}
36
37
interface ObservablePaneItem<T> extends BasePaneItem {
38
validator?: (value: T) => T | undefined;
39
}
40
41
interface DividerPaneItem extends BasePaneItem {
42
type: "divider";
43
}
44
45
interface ButtonPaneItem extends BasePaneItem, IButtonPropertyItemOptions {
46
type: "button";
47
pressed: () => void;
48
}
49
50
interface ProgressPaneItem extends ObservablePaneItem<number>, IProgressIndicatorPropertyItemOptions {
51
type: "progress";
52
}
53
54
interface TogglePaneItem extends ObservablePaneItem<boolean>, IBoolPropertyItemOptions {
55
type: "toggle";
56
value: MayObservable<boolean>;
57
}
58
59
interface SliderPaneItem extends ObservablePaneItem<number>, INumberPropertyItemOptions {
60
type: "slider";
61
value: MayObservable<number>;
62
}
63
64
interface DropdownPaneItem extends ObservablePaneItem<number>, IDropdownPropertyItemOptions {
65
type: "dropdown";
66
value: MayObservable<number>;
67
}
68
69
interface ComboBoxPaneItem extends ObservablePaneItem<string>, IComboBoxPropertyItemOptions {
70
type: "combo_box";
71
value: MayObservable<string>;
72
}
73
74
interface ToggleGroupPaneItem extends ObservablePaneItem<number>, IToggleGroupPropertyItemOptions {
75
type: "toggle_group";
76
value: MayObservable<number>;
77
}
78
79
interface Vector3PaneItem extends ObservablePaneItem<Vector3>, IVector3PropertyItemOptions {
80
type: "vector3";
81
value: MayObservable<Vector3>;
82
}
83
84
interface TextAreaPaneItem extends ObservablePaneItem<string>, IStringPropertyItemOptions {
85
type: "text_area";
86
value: MayObservable<string>;
87
}
88
89
interface LabelPaneItem extends ObservablePaneItem<string>, ITextPropertyItemOptions {
90
type: "label";
91
text: MayObservable<string>;
92
}
93
94
interface SubPane extends BasePaneItem, ISubPanePropertyItemOptions {
95
type: "subpane";
96
items: PaneItem[] | { build: (pane: UIPane) => void };
97
}
98
99
export type PaneItem =
100
| DividerPaneItem
101
| ButtonPaneItem
102
| ProgressPaneItem
103
| SliderPaneItem
104
| TogglePaneItem
105
| DropdownPaneItem
106
| ComboBoxPaneItem
107
| ToggleGroupPaneItem
108
| Vector3PaneItem
109
| TextAreaPaneItem
110
| LabelPaneItem
111
| SubPane;
112
113
export type PaneBuilder = { build: (pane: UIPane) => void };
114
115
export interface PaneLayout extends ISubPanePropertyItemOptions {
116
items: PaneItem[] | PaneBuilder;
117
}
118
119
export interface ModalPaneLayout extends PaneLayout {
120
title?: LocalizedString;
121
}
122
123
export class UIPane {
124
private pane: IPropertyPane;
125
private readonly subPanes: Record<string | number, UIPane> = {};
126
private readonly modals: Record<string, IModalOverlayPane> = {};
127
private readonly observables: Record<string | number, IObservable<number | boolean | Vector3 | LocalizedString>> = {};
128
private readonly properties: Record<string | number, IPropertyItemBase> = {};
129
private readonly mainPane: IPropertyPane;
130
131
constructor(
132
private readonly session: IPlayerUISession,
133
layout: PaneLayout,
134
basePane?: IPropertyPane
135
) {
136
this.mainPane = basePane ?? session.createPropertyPane({ title: layout.title });
137
if (Array.isArray(layout.items)) this.changeItems(layout.items);
138
else layout.items.build(this);
139
}
140
141
get player() {
142
return this.session.extensionContext.player;
143
}
144
145
get worldedit() {
146
return getSession(this.player);
147
}
148
149
get propertyPane() {
150
return this.pane;
151
}
152
153
get title() {
154
return this.mainPane.getTitle();
155
}
156
157
set title(value: LocalizedString | undefined) {
158
this.mainPane.setTitle(value);
159
}
160
161
get visible() {
162
return this.mainPane.visible;
163
}
164
165
set visible(value: boolean) {
166
if (value) this.mainPane.show();
167
else this.mainPane.hide();
168
}
169
170
show() {
171
this.visible = true;
172
}
173
174
hide() {
175
this.visible = false;
176
}
177
178
getValue(id: string | number) {
179
return this.observables[id]?.value;
180
}
181
182
setValue(id: string | number, value: number | boolean | Vector3 | LocalizedString) {
183
this.observables[id].set(value);
184
}
185
186
setVisibility(id: string | number, visible: boolean) {
187
this.properties[id].visible = visible;
188
}
189
190
setEnabled(id: string | number, enabled: boolean) {
191
this.properties[id].enable = enabled;
192
}
193
194
updateEntries(id: string | number, entries: IDropdownPropertyItemEntry[], newValue?: number) {
195
(this.properties[id] as IDropdownPropertyItem).updateEntries(entries, newValue);
196
}
197
198
addSubPane(layout: PaneLayout) {
199
return this.createSubPane(generateId(), layout, this.pane.createSubPane(layout)) as string;
200
}
201
202
getSubPane(id: string | number) {
203
return this.subPanes[id];
204
}
205
206
getAllSubPanes() {
207
return { ...this.subPanes };
208
}
209
210
removeSubPane(id: string) {
211
if (!(id in this.subPanes)) return;
212
this.pane.removeSubPane(this.subPanes[id].mainPane);
213
delete this.subPanes[id];
214
}
215
216
createModalPane(layout: ModalPaneLayout) {
217
const id = generateId();
218
this.modals[id] = (this.mainPane as IRootPropertyPane).createModalOverlayPane({ title: layout.title });
219
return this.createSubPane(id, layout, this.modals[id].contentPane);
220
}
221
222
showModalPane(id: string) {
223
this.modals[id].show();
224
}
225
226
hideModalPane(id: string) {
227
this.modals[id].hide();
228
}
229
230
changeItems(items: PaneItem[]) {
231
if (this.pane) this.mainPane.removeSubPane(this.pane);
232
this.pane = this.mainPane.createSubPane({ hasExpander: false, hasMargins: false });
233
this.pane.beginConstruct();
234
for (let i = 0; i < items.length; i++) {
235
const item = items[i];
236
const id = item.uniqueId ?? i;
237
switch (item.type) {
238
case "divider":
239
this.properties[id] = this.pane.addDivider();
240
break;
241
case "button":
242
this.properties[id] = this.pane.addButton(item.pressed, item);
243
break;
244
case "progress":
245
this.properties[id] = this.pane.addProgressIndicator({ ...item, progress: this.makeObservable((item.progress as number) ?? 0, id, item.validator) });
246
break;
247
case "label":
248
this.properties[id] = this.pane.addText(this.makeObservable(item.text, id, item.validator), item);
249
break;
250
case "text_area":
251
this.properties[id] = this.pane.addString(this.makeObservable(item.value, id, item.validator), item);
252
break;
253
case "toggle":
254
this.properties[id] = this.pane.addBool(this.makeObservable(item.value, id, item.validator), item);
255
break;
256
case "slider":
257
this.properties[id] = this.pane.addNumber(this.makeObservable(item.value, id, item.validator), item);
258
break;
259
case "dropdown":
260
this.properties[id] = this.pane.addDropdown(this.makeObservable(item.value, id, item.validator), item);
261
break;
262
case "combo_box":
263
this.properties[id] = this.pane.addComboBox(this.makeObservable(item.value, id, item.validator), item);
264
break;
265
case "toggle_group":
266
this.properties[id] = this.pane.addToggleGroup(this.makeObservable(item.value, id, item.validator), item);
267
break;
268
case "vector3":
269
this.properties[id] = this.pane.addVector3(this.makeObservable(item.value, id, item.validator), item);
270
break;
271
case "subpane":
272
delete item.uniqueId;
273
this.createSubPane(id, item, this.pane.createSubPane(item));
274
break;
275
default:
276
this.pane;
277
}
278
}
279
this.pane.endConstruct();
280
}
281
282
bindToTool(tool: IModalTool) {
283
tool.bindPropertyPane(this.mainPane as IRootPropertyPane);
284
}
285
286
private createSubPane<T extends string | number>(id: T, layout: PaneLayout, pane: ISubPanePropertyItem): T {
287
this.subPanes[id] = new UIPane(this.session, layout, pane);
288
return id;
289
}
290
291
private makeObservable<T extends number | boolean | Vector3 | LocalizedString>(value: MayObservable<T>, id: string | number, validator?: (value: T) => T | undefined) {
292
const observable = typeof value === "object" && "value" in value ? value : makeObservable(value, validator ? { validate: validator } : undefined);
293
this.observables[id] = observable;
294
return observable;
295
}
296
}
297
298