kipphi 2.0.1 → 2.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.
@@ -0,0 +1,285 @@
1
+ import { Chart } from "../chart";
2
+
3
+
4
+
5
+
6
+ export type OpEventType = "do" | "undo" | "redo" | "error" | "needsupdate" | "maxcombochanged" | "noundo" | "noredo" | "firstmodified" | "needsreflow";
7
+
8
+ // 最讲类型安全的一集(
9
+ // 当然要有,不然的话编辑器那边检测的时候逆变会出问题
10
+
11
+ interface DirectlyInstaciableEventMap {
12
+ "noundo": OpEvent;
13
+ "noredo": OpEvent;
14
+ "firstmodified": OpEvent;
15
+ }
16
+
17
+ // 创建一个类型来检测意外的 override
18
+ // AI太好用了你知道吗
19
+ type CheckFinalOverrides<T> = {
20
+ [K in keyof T]: K extends keyof OpEventMap ?
21
+ T[K] extends OpEventMap[K] ? T[K] : never :
22
+ T[K]
23
+ };
24
+
25
+ interface OpEventMap extends CheckFinalOverrides<DirectlyInstaciableEventMap> {
26
+ "error": OperationErrorEvent;
27
+ "maxcombochanged": MaxComboChangeEvent;
28
+ "undo": OperationEvent;
29
+ "redo": OperationEvent;
30
+ "do": OperationEvent;
31
+ "needsupdate": OperationEvent;
32
+ "needsreflow": NeedsReflowEvent;
33
+ }
34
+
35
+
36
+ class OpEvent extends Event {
37
+ protected constructor(type: OpEventType) {
38
+ super(type);
39
+ }
40
+ /**
41
+ * 如果这个类型没有对应子类应该用这个
42
+ */
43
+ static create(type: keyof DirectlyInstaciableEventMap) {
44
+ return new OpEvent(type);
45
+ }
46
+ }
47
+
48
+ export class NeedsReflowEvent extends OpEvent {
49
+ constructor(public condition: number) {
50
+ super("needsreflow");
51
+ }
52
+ }
53
+
54
+ export class OperationEvent extends OpEvent {
55
+ constructor(t: "do" | "undo" | "redo" | "error" | "needsupdate", public operation: Operation) {
56
+ super(t);
57
+ }
58
+ }
59
+
60
+ export class OperationErrorEvent extends OperationEvent {
61
+ constructor(operation: Operation, public error: Error) {
62
+ super("error", operation);
63
+ }
64
+ }
65
+
66
+ export class MaxComboChangeEvent extends OpEvent {
67
+ constructor(public comboDelta: number) {
68
+ super("maxcombochanged");
69
+ }
70
+ }
71
+
72
+
73
+
74
+ export class OperationList extends EventTarget {
75
+ operations: Operation[];
76
+ undoneOperations: Operation[];
77
+ constructor(public readonly chart: Chart) {
78
+ super()
79
+ this.operations = [];
80
+ this.undoneOperations = [];
81
+ }
82
+ undo() {
83
+ const op = this.operations.pop()
84
+ if (op) {
85
+ if (!this.chart.modified){
86
+ this.chart.modified = true;
87
+ this.dispatchEvent(OpEvent.create("firstmodified"))
88
+ }
89
+
90
+ try {
91
+ op.undo(this.chart);
92
+ } catch (e) {
93
+ this.dispatchEvent(new OperationErrorEvent(op, e as Error))
94
+ return
95
+ }
96
+ this.undoneOperations.push(op)
97
+ this.dispatchEvent(new OperationEvent("undo", op))
98
+ this.processFlags(op);
99
+ } else {
100
+ this.dispatchEvent(OpEvent.create("noundo"))
101
+ }
102
+ }
103
+ redo() {
104
+ const op = this.undoneOperations.pop()
105
+ if (op) {
106
+ if (!this.chart.modified){
107
+ this.chart.modified = true;
108
+ this.dispatchEvent(OpEvent.create("firstmodified"))
109
+ }
110
+
111
+ try {
112
+ op.do(this.chart);
113
+ } catch (e) {
114
+ this.dispatchEvent(new OperationErrorEvent(op, e as Error))
115
+ return
116
+ }
117
+ this.operations.push(op)
118
+ this.dispatchEvent(new OperationEvent("redo", op))
119
+ this.processFlags(op);
120
+ } else {
121
+ this.dispatchEvent(OpEvent.create("noredo"))
122
+ }
123
+ }
124
+ do(operation: Operation) {
125
+ if (operation.ineffective) {
126
+ return
127
+ }
128
+ if (!this.chart.modified){
129
+ this.chart.modified = true;
130
+ this.dispatchEvent(OpEvent.create("firstmodified"))
131
+ }
132
+ // 如果上一个操作是同一个构造器的,那么试图修改上一个操作而不是立即推入新的操作
133
+ if (this.operations.length !== 0) {
134
+
135
+ const lastOp = this.operations[this.operations.length - 1]
136
+ if (operation.constructor === lastOp.constructor) {
137
+ // 返回值指示是否重写成功
138
+ if (lastOp.rewrite(operation, this.chart)) {
139
+ this.processFlags(operation)
140
+ return;
141
+ }
142
+ }
143
+ }
144
+ try {
145
+ operation.do(this.chart);
146
+ } catch (e) {
147
+ this.dispatchEvent(new OperationErrorEvent(operation, e as Error))
148
+ return
149
+ }
150
+ this.dispatchEvent(new OperationEvent("do", operation));
151
+ this.processFlags(operation);
152
+ this.operations.push(operation);
153
+ }
154
+ processFlags(operation: Operation) {
155
+
156
+ if (operation.updatesEditor) {
157
+ this.dispatchEvent(new OperationEvent("needsupdate", operation));
158
+ }
159
+ if (operation.comboDelta) {
160
+ this.dispatchEvent(new MaxComboChangeEvent(operation.comboDelta));
161
+ }
162
+ if (operation.reflows) {
163
+ this.dispatchEvent(new NeedsReflowEvent(operation.reflows))
164
+ }
165
+ }
166
+ clear() {
167
+ this.operations = [];
168
+ }
169
+ addEventListener<T extends OpEventType>(type: T, listener: (event: OpEventMap[T]) => void, options?: boolean | AddEventListenerOptions): void {
170
+ super.addEventListener(type, listener, options);
171
+ }
172
+ }
173
+
174
+
175
+ export abstract class Operation {
176
+ ineffective: boolean;
177
+ updatesEditor: boolean;
178
+ // 用于判定线编辑区的重排,若操作完成时的布局为这个值就会重排
179
+ reflows: number;
180
+ /**
181
+ * 此操作对谱面总物量产生了多少影响,正增负减。
182
+ *
183
+ * 如果操作自身无法评估,应返回NaN,导致全谱重新数清物量
184
+ */
185
+ comboDelta: number;
186
+ constructor() {
187
+
188
+ }
189
+ abstract do(chart: Chart): void
190
+ abstract undo(chart: Chart): void
191
+ rewrite(op: this, chart: Chart): boolean {return false;}
192
+ toString(): string {
193
+ return this.constructor.name;
194
+ }
195
+ static lazy<C extends new (...args: any[]) => any = typeof this>(this: C, ...args: ConstructorParameters<C>) {
196
+ return new LazyOperation<C>(this, ...args)
197
+ }
198
+ }
199
+
200
+
201
+
202
+
203
+ /**
204
+ * 懒操作,实例化的时候不记录任何数据,do的时候才执行真正实例化
205
+ * 防止连续的操作中状态改变导致的错误
206
+ */
207
+ export class LazyOperation<C extends new (...args: any[]) => any> extends Operation {
208
+ public operationClass: C;
209
+ public args: ConstructorParameters<C>;
210
+ public operation: InstanceType<C> | null = null;
211
+ constructor(
212
+ operationClass: C,
213
+ ...args: ConstructorParameters<C>
214
+ ) {
215
+ super();
216
+ this.operationClass = operationClass;
217
+ this.args = args;
218
+ }
219
+ do(chart: Chart) {
220
+ this.operation = new this.operationClass(...this.args);
221
+ this.operation.do(chart);
222
+ }
223
+ undo(chart: Chart) {
224
+ this.operation.undo(chart);
225
+ }
226
+ }
227
+
228
+
229
+ /**
230
+ * C语言借来的概念
231
+ *
232
+ * 一个不确定类型的子操作
233
+ *
234
+ * 注意这个操作不懒,会在构造时就实例化子操作
235
+ */
236
+ export class UnionOperation<T extends Operation> extends Operation {
237
+ operation: T;
238
+ constructor(matcher: () => T) {
239
+ super();
240
+ this.operation = matcher();
241
+ if (!this.operation) {
242
+ this.ineffective = true;
243
+ }
244
+ }
245
+ // 这样子写不够严密,如果要继承这个类,并且子操作需要谱面,就要重写这个方法的签名
246
+ do(chart?: Chart) {
247
+ this.operation?.do(chart);
248
+ }
249
+ undo(chart?: Chart) {
250
+ this.operation?.undo(chart);
251
+ }
252
+ }
253
+
254
+
255
+ export class ComplexOperation<T extends Operation[]> extends Operation {
256
+ subOperations: T;
257
+ length: number;
258
+ constructor(...sub: T) {
259
+ super()
260
+ this.subOperations = sub
261
+ this.length = sub.length
262
+ this.reflows = sub.reduce((prev, op) => prev | op.reflows, 0);
263
+ this.updatesEditor = sub.some((op) => op.updatesEditor);
264
+ this.comboDelta = sub.reduce((prev, op) => prev + op.comboDelta, 0);
265
+ }
266
+ // 这样子写不够严密,如果要继承这个类,并且子操作需要谱面,就要重写这个方法的签名
267
+ do(chart?: Chart) {
268
+ const length = this.length
269
+ for (let i = 0; i < length; i++) {
270
+ const op = this.subOperations[i]
271
+ if (op.ineffective) {
272
+ continue;
273
+ }
274
+ op.do(chart)
275
+ }
276
+ }
277
+ undo(chart?: Chart) {
278
+ const length = this.length
279
+ for (let i = length - 1; i >= 0; i--) {
280
+ const op = this.subOperations[i]
281
+ if (op.ineffective) { continue; }
282
+ op.undo(chart)
283
+ }
284
+ }
285
+ }
@@ -0,0 +1,21 @@
1
+ import { Chart } from "../chart";
2
+ import { Operation } from "./basic";
3
+
4
+ export type ChartPropName = "name" | "level" | "composer" | "illustrator" | "charter" | "offset"
5
+
6
+ export class ChartPropChangeOperation<T extends ChartPropName> extends Operation {
7
+ originalValue: Chart[T];
8
+ constructor(public chart: Chart, public field: T, public value: Chart[T]) {
9
+ super();
10
+ this.originalValue = chart[field];
11
+ if (field === "level" || field === "name") {
12
+ this.updatesEditor = true;
13
+ }
14
+ }
15
+ do() {
16
+ this.chart[this.field] = this.value;
17
+ }
18
+ undo() {
19
+ this.chart[this.field] = this.originalValue;
20
+ }
21
+ }