@kubohiroya/turbowarp-plus-editor-project-model 0.1.1 → 0.1.2

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.
@@ -1,6 +1,14 @@
1
+ type VariableType = '' | 'list';
1
2
  export type SpriteVariableEdit = {
2
3
  type: 'create';
3
4
  name: string;
5
+ variableType?: '';
6
+ isCloud?: boolean;
7
+ } | {
8
+ type: 'create';
9
+ name: string;
10
+ variableType: 'list';
11
+ isCloud?: false;
4
12
  } | {
5
13
  type: 'rename';
6
14
  id: string;
@@ -9,7 +17,7 @@ export type SpriteVariableEdit = {
9
17
  type: 'delete';
10
18
  id: string;
11
19
  };
12
- export type SpriteVariableEditError = 'emptyName' | 'duplicateName' | 'inUse' | 'unavailable';
20
+ export type SpriteVariableEditError = 'emptyName' | 'duplicateName' | 'inUse' | 'unavailable' | 'cloudLimit';
13
21
  type Variable = {
14
22
  name: string;
15
23
  type: string;
@@ -29,9 +37,9 @@ type VariableEvent = {
29
37
  type: 'var_create';
30
38
  varId: string;
31
39
  varName: string;
32
- varType: '';
40
+ varType: VariableType;
33
41
  isLocal: boolean;
34
- isCloud: false;
42
+ isCloud: boolean;
35
43
  } | {
36
44
  type: 'var_rename';
37
45
  varId: string;
@@ -58,9 +66,78 @@ type VariableProject = {
58
66
  targets: readonly VariableTarget[];
59
67
  getEditingTarget(): VariableTarget | null;
60
68
  getTargetForStage(): VariableTarget | null;
69
+ canAddCloudVariable?(): boolean;
61
70
  };
62
71
  refreshWorkspace(): void;
63
72
  };
73
+ export type SpriteListItem = string | number | boolean;
74
+ export type SpriteVariableValue = string | number | readonly SpriteListItem[];
75
+ type VariableValue = {
76
+ id: string;
77
+ name: string;
78
+ type: string;
79
+ value: unknown;
80
+ isCloud?: boolean;
81
+ _monitorUpToDate?: boolean;
82
+ };
83
+ export declare function readSpriteVariable(variable: VariableValue): {
84
+ id: string;
85
+ name: string;
86
+ value: readonly SpriteListItem[];
87
+ isCloud?: undefined;
88
+ } | {
89
+ id: string;
90
+ name: string;
91
+ value: string | number;
92
+ isCloud: boolean | undefined;
93
+ } | undefined;
94
+ export declare function writeSpriteVariableValue(vm: {
95
+ runtime: {
96
+ getTargetById(id: string): {
97
+ variables: Record<string, VariableValue>;
98
+ } | undefined;
99
+ };
100
+ setVariableValue(targetId: string, variableId: string, value: SpriteVariableValue): boolean;
101
+ }, targetId: string, id: string, value: SpriteVariableValue): boolean;
64
102
  export declare const cloudVariablePrefix = "\u2601 ";
103
+ type VariableMonitorBlock = {
104
+ id: string;
105
+ opcode: string;
106
+ isMonitored?: boolean;
107
+ targetId: string | null;
108
+ };
109
+ type VariableMonitorRuntime = {
110
+ getTargetById(id: string): VariableTarget | undefined;
111
+ getMonitorState(): {
112
+ get(id: string): {
113
+ visible: boolean;
114
+ } | undefined;
115
+ };
116
+ monitorBlocks: {
117
+ getBlock(id: string): VariableMonitorBlock | undefined;
118
+ createBlock(block: VariableMonitorBlock & {
119
+ fields: Record<string, {
120
+ id: string;
121
+ name: string;
122
+ value: string;
123
+ variableType: '' | 'list';
124
+ }>;
125
+ inputs: Record<string, never>;
126
+ next: null;
127
+ parent: null;
128
+ topLevel: true;
129
+ shadow: false;
130
+ x: number;
131
+ y: number;
132
+ }): void;
133
+ changeBlock(change: {
134
+ id: string;
135
+ element: 'checkbox';
136
+ value: boolean;
137
+ }): void;
138
+ };
139
+ emitProjectChanged(): void;
140
+ };
141
+ export declare function showSpriteVariableOnStage(runtime: VariableMonitorRuntime, targetId: string, id: string, visible: boolean): void;
65
142
  export declare function editSpriteVariable(vm: VariableProject, targetId: string, edit: SpriteVariableEdit): SpriteVariableEditError | void;
66
143
  export {};
@@ -1,4 +1,46 @@
1
+ export function readSpriteVariable(variable) {
2
+ const { id, name, type, value, isCloud } = variable;
3
+ if (type === 'list' && Array.isArray(value))
4
+ return { id, name, value: value };
5
+ if (type === '' && (typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean')) {
6
+ return { id, name, value: typeof value === 'boolean' ? String(value) : value, isCloud };
7
+ }
8
+ }
9
+ export function writeSpriteVariableValue(vm, targetId, id, value) {
10
+ const variable = vm.runtime.getTargetById(targetId)?.variables[id];
11
+ const list = typeof value === 'object';
12
+ if (!variable || variable.type !== (list ? 'list' : '') || !vm.setVariableValue(targetId, id, value))
13
+ return false;
14
+ if (list)
15
+ variable._monitorUpToDate = false;
16
+ return true;
17
+ }
1
18
  export const cloudVariablePrefix = '☁ ';
19
+ export function showSpriteVariableOnStage(runtime, targetId, id, visible) {
20
+ const owner = runtime.getTargetById(targetId);
21
+ const variable = owner?.variables[id];
22
+ if (!owner?.isOriginal || !variable || !['', 'list'].includes(variable.type) ||
23
+ Boolean(runtime.getMonitorState().get(id)?.visible) === visible)
24
+ return;
25
+ const list = variable.type === 'list';
26
+ const opcode = list ? 'data_listcontents' : 'data_variable';
27
+ const field = list ? 'LIST' : 'VARIABLE';
28
+ const { monitorBlocks } = runtime;
29
+ let block = monitorBlocks.getBlock(id);
30
+ if (!block) {
31
+ monitorBlocks.createBlock({
32
+ id, opcode, targetId: owner.isStage ? null : owner.id, inputs: {},
33
+ fields: { [field]: { id, name: field, value: variable.name, variableType: list ? 'list' : '' } },
34
+ next: null, parent: null, topLevel: true, shadow: false, x: 0, y: 0
35
+ });
36
+ block = monitorBlocks.getBlock(id);
37
+ }
38
+ if (block?.opcode !== opcode)
39
+ return;
40
+ block.isMonitored = !visible;
41
+ monitorBlocks.changeBlock({ id, element: 'checkbox', value: visible });
42
+ runtime.emitProjectChanged();
43
+ }
2
44
  function isUsed(target, targets, id, name) {
3
45
  const targetName = target.isStage ? '_stage_' : target.getName();
4
46
  return targets.some(({ blocks }) => {
@@ -30,20 +72,29 @@ export function editSpriteVariable(vm, targetId, edit) {
30
72
  target.blocks.blocklyListen({ type: 'var_delete', varId: edit.id });
31
73
  }
32
74
  else {
33
- const name = edit.name.trim();
75
+ const variableType = edit.type === 'create' ? edit.variableType ?? '' : '';
76
+ const cloud = edit.type === 'create' ? variableType === '' && edit.isCloud === true : variable?.isCloud === true;
77
+ const name = (cloud && edit.name.startsWith(cloudVariablePrefix) ?
78
+ edit.name.slice(cloudVariablePrefix.length) : edit.name).trim();
34
79
  if (!name)
35
80
  return 'emptyName';
36
- const fullName = variable?.isCloud && !name.startsWith(cloudVariablePrefix) ? cloudVariablePrefix + name : name;
81
+ const fullName = cloud ? cloudVariablePrefix + name : name;
37
82
  if (fullName === variable?.name)
38
83
  return;
39
84
  const scope = target.isStage ? runtime.targets.filter(item => item.isStage || item.isOriginal) :
40
85
  [target, runtime.getTargetForStage()];
41
- if (scope.some(owner => owner && Object.values(owner.variables).some(item => item.type === '' && item.name === fullName))) {
86
+ if (scope.some(owner => owner && Object.values(owner.variables).some(item => item.type === variableType && item.name === fullName))) {
42
87
  return 'duplicateName';
43
88
  }
44
89
  if (edit.type === 'create') {
90
+ if (cloud && !target.isStage)
91
+ return 'unavailable';
92
+ if (cloud && !runtime.canAddCloudVariable?.())
93
+ return 'cloudLimit';
45
94
  const varId = Array.from(crypto.getRandomValues(new Uint32Array(4)), value => value.toString(16).padStart(8, '0')).join('');
46
- target.blocks.blocklyListen({ type: 'var_create', varId, varName: fullName, varType: '', isLocal: !target.isStage, isCloud: false });
95
+ target.blocks.blocklyListen({
96
+ type: 'var_create', varId, varName: fullName, varType: variableType, isLocal: !target.isStage, isCloud: cloud
97
+ });
47
98
  }
48
99
  else {
49
100
  target.blocks.blocklyListen({ type: 'var_rename', varId: edit.id, newName: fullName });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubohiroya/turbowarp-plus-editor-project-model",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "UI-free reads and edits of a running Scratch project for TurboWarp Plus Editor",
5
5
  "type": "module",
6
6
  "license": "MPL-2.0",
@@ -1,9 +1,12 @@
1
+ type VariableType = '' | 'list';
2
+
1
3
  export type SpriteVariableEdit =
2
- {type: 'create'; name: string} |
4
+ {type: 'create'; name: string; variableType?: ''; isCloud?: boolean} |
5
+ {type: 'create'; name: string; variableType: 'list'; isCloud?: false} |
3
6
  {type: 'rename'; id: string; name: string} |
4
7
  {type: 'delete'; id: string};
5
8
 
6
- export type SpriteVariableEditError = 'emptyName' | 'duplicateName' | 'inUse' | 'unavailable';
9
+ export type SpriteVariableEditError = 'emptyName' | 'duplicateName' | 'inUse' | 'unavailable' | 'cloudLimit';
7
10
 
8
11
  type Variable = {name: string; type: string; isCloud?: boolean};
9
12
  type Block = {
@@ -12,7 +15,7 @@ type Block = {
12
15
  inputs: Record<string, {block: string | null; shadow: string | null}>;
13
16
  };
14
17
  type VariableEvent =
15
- {type: 'var_create'; varId: string; varName: string; varType: ''; isLocal: boolean; isCloud: false} |
18
+ {type: 'var_create'; varId: string; varName: string; varType: VariableType; isLocal: boolean; isCloud: boolean} |
16
19
  {type: 'var_rename'; varId: string; newName: string} |
17
20
  {type: 'var_delete'; varId: string};
18
21
  type VariableTarget = {
@@ -33,12 +36,88 @@ type VariableProject = {
33
36
  targets: readonly VariableTarget[];
34
37
  getEditingTarget(): VariableTarget | null;
35
38
  getTargetForStage(): VariableTarget | null;
39
+ canAddCloudVariable?(): boolean;
36
40
  };
37
41
  refreshWorkspace(): void;
38
42
  };
39
43
 
44
+ export type SpriteListItem = string | number | boolean;
45
+ export type SpriteVariableValue = string | number | readonly SpriteListItem[];
46
+
47
+ type VariableValue = {id: string; name: string; type: string; value: unknown; isCloud?: boolean; _monitorUpToDate?: boolean};
48
+
49
+ export function readSpriteVariable(variable: VariableValue) {
50
+ const {id, name, type, value, isCloud} = variable;
51
+ if (type === 'list' && Array.isArray(value)) return {id, name, value: value as readonly SpriteListItem[]};
52
+ if (type === '' && (typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean')) {
53
+ return {id, name, value: typeof value === 'boolean' ? String(value) : value, isCloud};
54
+ }
55
+ }
56
+
57
+ export function writeSpriteVariableValue(vm: {
58
+ runtime: {getTargetById(id: string): {variables: Record<string, VariableValue>} | undefined};
59
+ setVariableValue(targetId: string, variableId: string, value: SpriteVariableValue): boolean;
60
+ }, targetId: string, id: string, value: SpriteVariableValue) {
61
+ const variable = vm.runtime.getTargetById(targetId)?.variables[id];
62
+ const list = typeof value === 'object';
63
+ if (!variable || variable.type !== (list ? 'list' : '') || !vm.setVariableValue(targetId, id, value)) return false;
64
+ if (list) variable._monitorUpToDate = false;
65
+ return true;
66
+ }
67
+
40
68
  export const cloudVariablePrefix = '☁ ';
41
69
 
70
+ type VariableMonitorBlock = {
71
+ id: string;
72
+ opcode: string;
73
+ isMonitored?: boolean;
74
+ targetId: string | null;
75
+ };
76
+
77
+ type VariableMonitorRuntime = {
78
+ getTargetById(id: string): VariableTarget | undefined;
79
+ getMonitorState(): {get(id: string): {visible: boolean} | undefined};
80
+ monitorBlocks: {
81
+ getBlock(id: string): VariableMonitorBlock | undefined;
82
+ createBlock(block: VariableMonitorBlock & {
83
+ fields: Record<string, {id: string; name: string; value: string; variableType: '' | 'list'}>;
84
+ inputs: Record<string, never>;
85
+ next: null;
86
+ parent: null;
87
+ topLevel: true;
88
+ shadow: false;
89
+ x: number;
90
+ y: number;
91
+ }): void;
92
+ changeBlock(change: {id: string; element: 'checkbox'; value: boolean}): void;
93
+ };
94
+ emitProjectChanged(): void;
95
+ };
96
+
97
+ export function showSpriteVariableOnStage(runtime: VariableMonitorRuntime, targetId: string, id: string, visible: boolean) {
98
+ const owner = runtime.getTargetById(targetId);
99
+ const variable = owner?.variables[id];
100
+ if (!owner?.isOriginal || !variable || !['', 'list'].includes(variable.type) ||
101
+ Boolean(runtime.getMonitorState().get(id)?.visible) === visible) return;
102
+ const list = variable.type === 'list';
103
+ const opcode = list ? 'data_listcontents' : 'data_variable';
104
+ const field = list ? 'LIST' : 'VARIABLE';
105
+ const {monitorBlocks} = runtime;
106
+ let block = monitorBlocks.getBlock(id);
107
+ if (!block) {
108
+ monitorBlocks.createBlock({
109
+ id, opcode, targetId: owner.isStage ? null : owner.id, inputs: {},
110
+ fields: {[field]: {id, name: field, value: variable.name, variableType: list ? 'list' : ''}},
111
+ next: null, parent: null, topLevel: true, shadow: false, x: 0, y: 0
112
+ });
113
+ block = monitorBlocks.getBlock(id);
114
+ }
115
+ if (block?.opcode !== opcode) return;
116
+ block.isMonitored = !visible;
117
+ monitorBlocks.changeBlock({id, element: 'checkbox', value: visible});
118
+ runtime.emitProjectChanged();
119
+ }
120
+
42
121
  function isUsed(target: VariableTarget, targets: readonly VariableTarget[], id: string, name: string) {
43
122
  const targetName = target.isStage ? '_stage_' : target.getName();
44
123
  return targets.some(({blocks}) => {
@@ -65,18 +144,26 @@ export function editSpriteVariable(vm: VariableProject, targetId: string, edit:
65
144
  if (isUsed(target, runtime.targets, edit.id, variable!.name)) return 'inUse';
66
145
  target.blocks.blocklyListen({type: 'var_delete', varId: edit.id});
67
146
  } else {
68
- const name = edit.name.trim();
147
+ const variableType = edit.type === 'create' ? edit.variableType ?? '' : '';
148
+ const cloud = edit.type === 'create' ? variableType === '' && edit.isCloud === true : variable?.isCloud === true;
149
+ const name = (cloud && edit.name.startsWith(cloudVariablePrefix) ?
150
+ edit.name.slice(cloudVariablePrefix.length) : edit.name).trim();
69
151
  if (!name) return 'emptyName';
70
- const fullName = variable?.isCloud && !name.startsWith(cloudVariablePrefix) ? cloudVariablePrefix + name : name;
152
+ const fullName = cloud ? cloudVariablePrefix + name : name;
71
153
  if (fullName === variable?.name) return;
72
154
  const scope = target.isStage ? runtime.targets.filter(item => item.isStage || item.isOriginal) :
73
155
  [target, runtime.getTargetForStage()];
74
- if (scope.some(owner => owner && Object.values(owner.variables).some(item => item.type === '' && item.name === fullName))) {
156
+ if (scope.some(owner => owner && Object.values(owner.variables).some(item =>
157
+ item.type === variableType && item.name === fullName))) {
75
158
  return 'duplicateName';
76
159
  }
77
160
  if (edit.type === 'create') {
161
+ if (cloud && !target.isStage) return 'unavailable';
162
+ if (cloud && !runtime.canAddCloudVariable?.()) return 'cloudLimit';
78
163
  const varId = Array.from(crypto.getRandomValues(new Uint32Array(4)), value => value.toString(16).padStart(8, '0')).join('');
79
- target.blocks.blocklyListen({type: 'var_create', varId, varName: fullName, varType: '', isLocal: !target.isStage, isCloud: false});
164
+ target.blocks.blocklyListen({
165
+ type: 'var_create', varId, varName: fullName, varType: variableType, isLocal: !target.isStage, isCloud: cloud
166
+ });
80
167
  } else {
81
168
  target.blocks.blocklyListen({type: 'var_rename', varId: edit.id, newName: fullName});
82
169
  }