@virid/amber 0.1.0 → 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.
- package/README.md +96 -1
- package/README.zh.md +95 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1 +1,96 @@
|
|
|
1
|
-
|
|
1
|
+
### **@virid/amber**
|
|
2
|
+
|
|
3
|
+
`@virid/amber` acts as the "Time Machine" for `@virid/core`. It is responsible for the automated persistence and recording of tagged Components, providing both global and Component-level **Undo/Redo** functionality. It supports Component-level customization, side-effect triggers, and a wide array of hooks.
|
|
4
|
+
|
|
5
|
+
### 🌟 **Core Design Philosophy**
|
|
6
|
+
|
|
7
|
+
In `@virid/amber`, Undo and Redo operations are fully automated and **zero-intrusive**. There is no need for manual backup logic within your business code.
|
|
8
|
+
|
|
9
|
+
- **Component-Level & Global Tick Rollback:** By using `@virid/amber`, you can choose to roll back a specific local state without affecting the whole system, or revert all changes within an entire Tick at once. This offers flexible control over both local and global state transitions.
|
|
10
|
+
- **Zero Business Intrusion:** The automatic backup functionality of `@virid/amber` executes automatically during every Tick. Developers only need to define how to compare, back up, and restore the data; `@virid/amber` handles the data management, persistence, and recovery seamlessly.
|
|
11
|
+
- **Side-Effect Hooks:** `@virid/amber` provides an extensive set of side-effect hooks, allowing developers to decide exactly how to handle side effects generated during the Undo/Redo process.
|
|
12
|
+
|
|
13
|
+
## 🔌Enable plugins
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createVirid } from '@virid/core'
|
|
17
|
+
import { AmberPlugin, Backup, Amber } from "@virid/amber";
|
|
18
|
+
const app = createVirid()
|
|
19
|
+
app.use(AmberPlugin, {});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 🛠️ @virid/amber Core API Overview
|
|
23
|
+
|
|
24
|
+
### **1. @Backup(options?: BackupOptions)**
|
|
25
|
+
|
|
26
|
+
- **Function:** Applied to a `Component` to notify Amber that this component requires state persistence and version tracking.
|
|
27
|
+
- **Logic:** At the end of every Tick, Amber automatically identifies modified components and creates a snapshot based on the defined configuration.
|
|
28
|
+
- **Example:**
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
@Component()
|
|
32
|
+
@Backup({
|
|
33
|
+
// The onRestore hook handles side effects during undo/redo
|
|
34
|
+
onRestore: (oldData, newData, direction) => {
|
|
35
|
+
console.log(
|
|
36
|
+
`[Hook] Restore direction: ${direction}. Data changed from ${oldData.count} -> ${newData.count}`
|
|
37
|
+
);
|
|
38
|
+
},
|
|
39
|
+
// Execution logic before/after the backup process
|
|
40
|
+
onBeforeBackup(oldData) {
|
|
41
|
+
console.log(`[Hook] Preparing backup: current count is ${oldData.count}`);
|
|
42
|
+
},
|
|
43
|
+
onAfterBackup(newData) {
|
|
44
|
+
console.log(`[Hook] Backup complete: new count is ${newData.count}`);
|
|
45
|
+
},
|
|
46
|
+
// Custom logic for comparison, storage, and restoration
|
|
47
|
+
// Useful for complex data types or optimizing performance by avoiding full deep copies
|
|
48
|
+
diff(oldData: any, component: PlayerComponent) {
|
|
49
|
+
return oldData.count !== component.count;
|
|
50
|
+
},
|
|
51
|
+
serialize(player: PlayerComponent) {
|
|
52
|
+
return { count: player.count };
|
|
53
|
+
},
|
|
54
|
+
deserialize(component: PlayerComponent, newData: { count: number }) {
|
|
55
|
+
component.count = newData.count;
|
|
56
|
+
},
|
|
57
|
+
})
|
|
58
|
+
class PlayerComponent {
|
|
59
|
+
public count = 0;
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
------
|
|
64
|
+
|
|
65
|
+
### **2. Amber (Global Manager)**
|
|
66
|
+
|
|
67
|
+
- **Function:** The global `Amber` class provides centralized control over component versions, enabling both component-specific and global Tick-based Undo/Redo.
|
|
68
|
+
- **Example:**
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
// Undo/Redo for a specific component type
|
|
72
|
+
Amber.undo(PlayerComponent);
|
|
73
|
+
Amber.redo(PlayerComponent);
|
|
74
|
+
|
|
75
|
+
// Undo/Redo for the entire global Tick
|
|
76
|
+
Amber.undoTick();
|
|
77
|
+
Amber.redoTick();
|
|
78
|
+
|
|
79
|
+
// Clear all version history and record logs
|
|
80
|
+
Amber.resetAll();
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
------
|
|
84
|
+
|
|
85
|
+
### ⚠️ **Important Considerations**
|
|
86
|
+
|
|
87
|
+
#### **State Synchronization Philosophy**
|
|
88
|
+
|
|
89
|
+
To resolve inconsistencies between global and local state versions, Amber adopts a specific philosophy: **"A global Undo/Redo is a local update; a local Undo/Redo is a global update."** * When you perform an Undo/Redo on a specific **Component**, the global **Tick** count increments.
|
|
90
|
+
|
|
91
|
+
- Conversely, when you perform an Undo/Redo on the current **Tick**, all affected components generate new versions.
|
|
92
|
+
- Amber can implement "tick redo can redo component redo", but this often leads to confusion, so in most cases, only one function is recommended.
|
|
93
|
+
|
|
94
|
+
#### **Linear Timeline Model**
|
|
95
|
+
|
|
96
|
+
Amber utilizes a standard **Linear Timeline** model. If a new history (state change) is generated while the system is in a "past" state (after an undo), any "already occurred futures" between that past point and the new history will be truncated (discarded) to maintain a single cohesive timeline.
|
package/README.zh.md
CHANGED
|
@@ -1 +1,95 @@
|
|
|
1
|
-
|
|
1
|
+
# @virid/amber
|
|
2
|
+
|
|
3
|
+
`@virid/amber` 是 ` @virid/core` 的时光机器,负责自动化保存和记录被标记的`Component`,并提供全局与`Component`级别的undo/redo功能,支持`Component`级别的自定义与副作用触发以及多种钩子。
|
|
4
|
+
|
|
5
|
+
## 🌟 核心设计理念
|
|
6
|
+
|
|
7
|
+
在 `@virid/amber` 中,`Component`的undo与redo是**全自动、0入侵**的,业务逻辑代码中不会存在任何手动备份的逻辑。
|
|
8
|
+
|
|
9
|
+
- **Component级与全局Tick回退**:通过使用`@virid/amber`,不仅可以只回退某个局部状态而不回退整体,还能一次性回退整个Tick内的所有变化,灵活的控制局部与整体的变化。
|
|
10
|
+
- **0业务入侵**:`@virid/amber`的自动备份功能将会在每个tick自动执行,开发者只须告诉`@virid/amber`如何对比、备份、还原此数据,`@virid/amber`即可自动进行数据管理并备份和恢复数据。
|
|
11
|
+
- **副作用钩子**:`@virid/amber`提供了大量的副作用钩子来让开发者自己决定如何处理 undo/redo过程中产生的副作用。
|
|
12
|
+
|
|
13
|
+
## 🔌启用插件
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createVirid } from '@virid/core'
|
|
17
|
+
import { AmberPlugin, Backup, Amber } from "@virid/amber";
|
|
18
|
+
const app = createVirid()
|
|
19
|
+
app.use(AmberPlugin, {});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 🛠️ @virid/amber 核心 API 概览
|
|
23
|
+
|
|
24
|
+
### 1. Vue适配装饰器
|
|
25
|
+
|
|
26
|
+
#### `@Backup(shallow?: boolean)`
|
|
27
|
+
|
|
28
|
+
- **功能**:标记在一个Component上,告诉amber,此Component是需要被备份的。
|
|
29
|
+
- **逻辑**:在每个Tick结束后,amber将会自动找出那些Component发生了变化,并保存一份备份
|
|
30
|
+
- **示例:**
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
|
|
34
|
+
@Component()
|
|
35
|
+
@Backup({
|
|
36
|
+
// onRestore钩子可以用于处理副作用
|
|
37
|
+
onRestore: (old, now, dir) => {
|
|
38
|
+
console.log(
|
|
39
|
+
`[Hook] 组件恢复方向: ${dir}, 数据从 ${old.count} -> ${now.count}`,
|
|
40
|
+
);
|
|
41
|
+
},
|
|
42
|
+
// 这两个钩子可以在备份前后执行一些逻辑
|
|
43
|
+
onAfterBackup(newData: { count: number }) {
|
|
44
|
+
console.log(`[Hook] 组件已备份: count=${newData.count}`);
|
|
45
|
+
},
|
|
46
|
+
onBeforeBackup(oldData: { count: number }) {
|
|
47
|
+
console.log(`[Hook] 组件已备份: count=${oldData.count}`);
|
|
48
|
+
},
|
|
49
|
+
// 这三个方法可以让amber使用开发者自定义的对比、储存、恢复方法
|
|
50
|
+
// 用于支持各种类型的自定义数据与避免不必要的全量备份
|
|
51
|
+
diff(oldData: number, component: PlayerComponent) {
|
|
52
|
+
return oldData !== component.count;
|
|
53
|
+
},
|
|
54
|
+
serialize(player: PlayerComponent) {
|
|
55
|
+
return {
|
|
56
|
+
count: player.count,
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
deserialize(
|
|
60
|
+
component: PlayerComponent,
|
|
61
|
+
newData: {
|
|
62
|
+
count: number;
|
|
63
|
+
},
|
|
64
|
+
) {
|
|
65
|
+
component.count = newData.count;
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
class PlayerComponent {
|
|
69
|
+
public count = 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### `Amber`
|
|
75
|
+
|
|
76
|
+
- **功能**:通过一个全局的Amber类,即可控制每个组件的版本,实现特定组件、全局的Tick的 undo/redo功能
|
|
77
|
+
- **示例:**
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
// 组件撤销重做
|
|
81
|
+
Amber.undo(PlayerComponent);
|
|
82
|
+
Amber.redo(PlayerComponent);
|
|
83
|
+
// Tick级撤销重做
|
|
84
|
+
Amber.undoTick();
|
|
85
|
+
Amber.reodoTick();
|
|
86
|
+
// 清空所有版本记录
|
|
87
|
+
Amber.resetAll();
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## ⚠️一些重要注意事项
|
|
92
|
+
|
|
93
|
+
为了处理全局和局部的状态版本不一致问题,Amber采取了一种“全局的undo/redo是局部的更新,局部的undo/redo是全局的更新”的哲学。这意味着当你在undo/redo某个Component时,全局的Tick将会增加。而当你在undo/redo当前的Tick时,所有的组件都将产生新的版本。因此,Amber可以实现“全局的回退可以回退局部的回退”,但是这往往容易产生混乱,因此大多数情况下只建议使用一种功能。
|
|
94
|
+
|
|
95
|
+
Amber采取了常见的“线性时间”模型,当在过去时如果产生了新的历史,那么从过去到新的历史之间的“已经发生的未来”将会被裁剪。
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @virid/amber v0.
|
|
2
|
+
* @virid/amber v0.1.1
|
|
3
3
|
* Add replay, redo, undo and other functions to virid
|
|
4
4
|
*/
|
|
5
5
|
var y=Object.defineProperty;var $=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var N=Object.prototype.hasOwnProperty;var j=(r,t,e)=>t in r?y(r,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):r[t]=e;var a=(r,t)=>y(r,"name",{value:t,configurable:!0});var L=(r,t)=>{for(var e in t)y(r,e,{get:t[e],enumerable:!0})},P=(r,t,e,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of C(t))!N.call(r,o)&&o!==e&&y(r,o,{get:()=>t[o],enumerable:!(i=$(t,o))||i.enumerable});return r};var F=r=>P(y({},"__esModule",{value:!0}),r);var d=(r,t,e)=>j(r,typeof t!="symbol"?t+"":t,e);var G={};L(G,{Amber:()=>z,AmberPlugin:()=>q,Backup:()=>Q,RestoreDirection:()=>T,activateConfig:()=>O,afterExecuteHooks:()=>w,afterTickHooks:()=>_,amberComponentStore:()=>f,amberTickStore:()=>u});module.exports=F(G);var S=require("@virid/core");var h=require("@virid/core");var U=require("@virid/core"),s={...U.VIRID_METADATA,BACKUP:"virid:amber:backup",VERSION:"virid:amber:version",CUSTOM_METHOD:"virid:amber:custom-method"};var T=(function(r){return r.UNDO="UNDO",r.REDO="REDO",r})({});function V(r,t=5){if(r===null||typeof r!="object")return r;if(t<=0)return Array.isArray(r)?[]:{};if(Array.isArray(r))return r.map(o=>V(o,t-1));let e={},i=Object.keys(r);for(let o of i){if(o.startsWith("_"))continue;let n=r[o];typeof n!="function"&&(e[o]=typeof n=="object"&&n!==null?V(n,t-1):n)}return e}a(V,"_serialization");function D(r,t){!r||!t||Object.keys(t).forEach(e=>{let i=t[e],o=r[e];o&&typeof o=="object"&&i&&typeof i=="object"&&!Array.isArray(i)?D(o,i):r[e]=i})}a(D,"_deserialization");function R(r,t,e=3){if(r===t)return!1;if(typeof r!=typeof t||r===null||t===null)return!0;if(Array.isArray(r)){if(!Array.isArray(t)||r.length!==t.length)return!0;for(let o=0;o<r.length;o++)if(R(r[o],t[o],e-1))return!0;return!1}let i=Object.keys(r);for(let o of i){let n=r[o],c=t[o];if(typeof n=="object"&&n!==null){if(e<=0)continue;if(R(n,c,e-1))return!0}else if(n!==c)return!0}return!1}a(R,"_diff");var M={serialization:V,deserialization:D,diff:R};function O(r){M=Object.assign(M,r)}a(O,"activateConfig");var p,W=(p=class{constructor(t=20){d(this,"tickHistory",[]);d(this,"baseTick",0);d(this,"maxTickLength",20);d(this,"currentTick",0);this.maxTickLength=t}getMinTick(){return this.baseTick}getMaxTick(){return this.tickHistory.length===0?0:this.baseTick+this.tickHistory.length-1}updateTickHistory(){this.currentTick<this.baseTick+this.tickHistory.length&&this.tickHistory.splice(this.currentTick-this.baseTick+1);let t=new Map(f.currentHistory);this.tickHistory.push(t),this.tickHistory.length>this.maxTickLength&&(this.tickHistory.shift(),this.baseTick++);let e=this.baseTick+this.tickHistory.length-1;this.currentTick=e}travel(t){let e=t-this.baseTick,i=this.tickHistory[e];if(!i){h.MessageWriter.error(new Error(`[Virid Amber] Travel Error: Tick ${t} is out of history range.`));return}let o=t<this.currentTick?T.UNDO:T.REDO,n=[];for(let[c,m]of i){let l=f.getCurrentData(c);if(m!==l){let b=l;f.deserializeComponent(c,m),f.seal(c);let v=Reflect.getMetadata(s.CUSTOM_METHOD,c);v?.onRestore&&n.push({strategy:v,oldData:b,newData:m})}let E=A.get(c)}this.currentTick=t,n.forEach(({strategy:c,oldData:m,newData:l})=>{c.onRestore(m,l,o)})}resetTickStore(){this.tickHistory=[],this.baseTick=0,this.currentTick=0,this.updateTickHistory()}},a(p,"AmberTickStore"),p),g,K=(g=class{constructor(t=20){d(this,"componentHistory",new Map);d(this,"biasVersions",new Map);d(this,"maxComponentHistorySize",20);d(this,"currentHistory",new Map);this.maxComponentHistorySize=t}serializeComponent(t){let e=A.get(t);if(!e)return h.MessageWriter.error(new Error(`[Virid Amber] Serialize Error: Component ${t.name} is not registered.`)),null;let o=Reflect.getMetadata(s.CUSTOM_METHOD,t)?.serialize||M.serialization;try{return o(e)}catch(n){h.MessageWriter.error(n,`[Virid Amber] Serialize Error: Component ${t.name} serialization error`)}return null}deserializeComponent(t,e){let i=A.get(t);if(!i){h.MessageWriter.error(new Error(`[Virid Amber] Deserialize Error: Component ${t.name} is not registered.`));return}if(!e){h.MessageWriter.error(new Error(`[Virid Amber] Deserialize Error: Component ${t.name} data is ${e}!.`));return}let n=Reflect.getMetadata(s.CUSTOM_METHOD,t)?.deserialize||M.deserialization;try{n(i,e)}catch(c){h.MessageWriter.error(c,`[Virid Amber] Deserialize Error: Component ${t.name} deserialize error`)}}diffComponent(t,e){let i=A.get(t);if(!i)return h.MessageWriter.error(new Error(`[Virid Amber] Diff Error: Component ${t.name} is not registered.`)),!1;if(!e)return h.MessageWriter.error(new Error(`[Virid Amber] Diff Error: Component ${t.name} data is ${e}!.`)),!1;let n=Reflect.getMetadata(s.CUSTOM_METHOD,t)?.diff||M.diff;try{return n(i,e)}catch(c){h.MessageWriter.error(c,`[Virid Amber] Diff Error: Component ${t.name} diff error`)}return!1}initComponent(t){let e=t.constructor,o=Reflect.getMetadata(s.CUSTOM_METHOD,e)?.serialize||M.serialization,n;try{n=o(t)}catch(c){h.MessageWriter.error(c,`[Virid Amber] Serialize Error: Component ${e.name} serialization error`);return}this.componentHistory.set(e,[]),this.biasVersions.set(e,0),this.componentHistory.get(e).push(n),this.currentHistory.set(e,n),Reflect.defineMetadata(s.VERSION,0,e)}getCurrentData(t){return this.currentHistory.get(t)}getMinVersion(t){return this.biasVersions.get(t)??0}getMaxVersion(t){let e=this.componentHistory.get(t),i=this.biasVersions.get(t)??0;return!e||e.length===0?i:i+e.length-1}seal(t){let e=this.componentHistory.get(t);e||h.MessageWriter.error(new Error(`[Virid Amber] Seal Error: Component ${t.name} is not initialized.`));let i=this.biasVersions.get(t),o=this.getCurrentData(t);if(o&&!this.diffComponent(t,o))return;let n=Reflect.getMetadata(s.CUSTOM_METHOD,t);n?.onBeforeBackup&&n.onBeforeBackup(o);let c=Reflect.getMetadata(s.VERSION,t)||0,m=i+e.length-1;c<m&&e.splice(c-i+1);let l=this.serializeComponent(t);if(!l){h.MessageWriter.error(new Error(`[Virid Amber] Seal Error: Component ${t.name} is not serializable.`));return}e.push(l),this.currentHistory.set(t,l),e.length>this.maxComponentHistorySize&&(e.shift(),this.biasVersions.set(t,i+1));let E=this.biasVersions.get(t)+e.length-1;Reflect.defineMetadata(s.VERSION,E,t),n?.onAfterBackup&&n.onAfterBackup(l)}seek(t,e){let i=this.componentHistory.get(t);if(!i)return h.MessageWriter.error(new Error(`[Virid Amber] Seal Error: Component ${t.name} is not initialized.`)),!1;let o=this.biasVersions.get(t)||0,n=e-o;if(n<0||n>=i.length)return!1;let c=this.getCurrentData(t),m=i[n],l=Reflect.getMetadata(s.VERSION,t)||0,E=e<l?T.UNDO:T.REDO;this.deserializeComponent(t,m),this.currentHistory.set(t,m),Reflect.defineMetadata(s.VERSION,e,t);let b=Reflect.getMetadata(s.CUSTOM_METHOD,t);return b?.onRestore&&b.onRestore(c,m,E),u.updateTickHistory(),!0}resetComponentInternal(t){let e=this.serializeComponent(t);e&&(this.componentHistory.set(t,[e]),this.biasVersions.set(t,0),this.currentHistory.set(t,e),Reflect.defineMetadata(s.VERSION,0,t))}resetComponent(t){this.resetComponentInternal(t),u.updateTickHistory()}},a(g,"AmberComponentStore"),g),u=new W,f=new K;var I=class I{static getVersion(t){return Reflect.getMetadata(s.VERSION,t)||0}static canUndo(t){let e=this.getVersion(t),i=f.getMinVersion(t);return e>i}static canRedo(t){let e=this.getVersion(t),i=f.getMaxVersion(t);return e<i}static undo(t){if(!this.canUndo(t))return!1;let e=this.getVersion(t);return f.seek(t,e-1)}static redo(t){if(!this.canRedo(t))return!1;let e=this.getVersion(t);return f.seek(t,e+1)}static undoTick(){if(!this.canUndoTick())return!1;let t=u.currentTick-1;return u.travel(t),!0}static redoTick(){if(!this.canRedoTick())return!1;let t=u.currentTick+1;return u.travel(t),!0}static canUndoTick(){let t=u.currentTick,e=u.getMinTick();return t>e}static canRedoTick(){let t=u.currentTick,e=u.getMaxTick();return t<e}static resetTick(){u.resetTickStore()}static resetComponent(t){f.resetComponent(t)}static resetAll(){Array.from(f.componentHistory.keys()).forEach(e=>{f.resetComponentInternal(e)}),u.resetTickStore()}};a(I,"Amber");var z=I;var k=require("@virid/core");var H=new Set,w=a((r,t)=>{if(r instanceof k.ErrorMessage||r instanceof k.WarnMessage||r instanceof k.InfoMessage)return;(r instanceof k.AtomicModifyMessage?[...t.context.params,r.ComponentClass]:t.context.params).forEach(i=>{Reflect.hasMetadata(s.BACKUP,i)&&H.add(i)})},"afterExecuteHooks"),_=a(r=>{H.size!==0&&(H.forEach(t=>{f.seal(t)}),u.updateTickHistory(),H.clear())},"afterTickHooks");var x=null;function B(r,t){r.onAfterTick(_,!0),r.onAfterExecute(S.BaseMessage,w,!0),t&&O(t);let e=a(i=>(i&&Reflect.hasMetadata(s.VERSION,i.constructor)&&f.initComponent(i),i),"amberInitHook");r.addActivationHook(e),x=r}a(B,"activateApp");var A=new Proxy({},{get(r,t){return(...e)=>{if(!x)return S.MessageWriter.warn(`[Virid Vue] App method "${String(t)}" called before initialization.`),null;let i=x[t];if(typeof i=="function")return i.apply(x,e)}}});function Q(r){return t=>{Reflect.defineMetadata(s.BACKUP,!0,t),Reflect.defineMetadata(s.VERSION,0,t),r&&Reflect.defineMetadata(s.CUSTOM_METHOD,r,t)}}a(Q,"Backup");var q={name:"@virid/amber",install(r,t){B(r,t)}};0&&(module.exports={Amber,AmberPlugin,Backup,RestoreDirection,activateConfig,afterExecuteHooks,afterTickHooks,amberComponentStore,amberTickStore});
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @virid/amber v0.
|
|
2
|
+
* @virid/amber v0.1.1
|
|
3
3
|
* Add replay, redo, undo and other functions to virid
|
|
4
4
|
*/
|
|
5
5
|
var D=Object.defineProperty;var v=(r,t,e)=>t in r?D(r,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):r[t]=e;var a=(r,t)=>D(r,"name",{value:t,configurable:!0});var d=(r,t,e)=>v(r,typeof t!="symbol"?t+"":t,e);import{MessageWriter as P,BaseMessage as F}from"@virid/core";import{MessageWriter as h}from"@virid/core";import{VIRID_METADATA as U}from"@virid/core";var s={...U,BACKUP:"virid:amber:backup",VERSION:"virid:amber:version",CUSTOM_METHOD:"virid:amber:custom-method"};var p=(function(r){return r.UNDO="UNDO",r.REDO="REDO",r})({});function E(r,t=5){if(r===null||typeof r!="object")return r;if(t<=0)return Array.isArray(r)?[]:{};if(Array.isArray(r))return r.map(n=>E(n,t-1));let e={},i=Object.keys(r);for(let n of i){if(n.startsWith("_"))continue;let o=r[n];typeof o!="function"&&(e[n]=typeof o=="object"&&o!==null?E(o,t-1):o)}return e}a(E,"_serialization");function H(r,t){!r||!t||Object.keys(t).forEach(e=>{let i=t[e],n=r[e];n&&typeof n=="object"&&i&&typeof i=="object"&&!Array.isArray(i)?H(n,i):r[e]=i})}a(H,"_deserialization");function b(r,t,e=3){if(r===t)return!1;if(typeof r!=typeof t||r===null||t===null)return!0;if(Array.isArray(r)){if(!Array.isArray(t)||r.length!==t.length)return!0;for(let n=0;n<r.length;n++)if(b(r[n],t[n],e-1))return!0;return!1}let i=Object.keys(r);for(let n of i){let o=r[n],c=t[n];if(typeof o=="object"&&o!==null){if(e<=0)continue;if(b(o,c,e-1))return!0}else if(o!==c)return!0}return!1}a(b,"_diff");var k={serialization:E,deserialization:H,diff:b};function O(r){k=Object.assign(k,r)}a(O,"activateConfig");var T,B=(T=class{constructor(t=20){d(this,"tickHistory",[]);d(this,"baseTick",0);d(this,"maxTickLength",20);d(this,"currentTick",0);this.maxTickLength=t}getMinTick(){return this.baseTick}getMaxTick(){return this.tickHistory.length===0?0:this.baseTick+this.tickHistory.length-1}updateTickHistory(){this.currentTick<this.baseTick+this.tickHistory.length&&this.tickHistory.splice(this.currentTick-this.baseTick+1);let t=new Map(f.currentHistory);this.tickHistory.push(t),this.tickHistory.length>this.maxTickLength&&(this.tickHistory.shift(),this.baseTick++);let e=this.baseTick+this.tickHistory.length-1;this.currentTick=e}travel(t){let e=t-this.baseTick,i=this.tickHistory[e];if(!i){h.error(new Error(`[Virid Amber] Travel Error: Tick ${t} is out of history range.`));return}let n=t<this.currentTick?p.UNDO:p.REDO,o=[];for(let[c,m]of i){let l=f.getCurrentData(c);if(m!==l){let A=l;f.deserializeComponent(c,m),f.seal(c);let S=Reflect.getMetadata(s.CUSTOM_METHOD,c);S?.onRestore&&o.push({strategy:S,oldData:A,newData:m})}let y=g.get(c)}this.currentTick=t,o.forEach(({strategy:c,oldData:m,newData:l})=>{c.onRestore(m,l,n)})}resetTickStore(){this.tickHistory=[],this.baseTick=0,this.currentTick=0,this.updateTickHistory()}},a(T,"AmberTickStore"),T),M,$=(M=class{constructor(t=20){d(this,"componentHistory",new Map);d(this,"biasVersions",new Map);d(this,"maxComponentHistorySize",20);d(this,"currentHistory",new Map);this.maxComponentHistorySize=t}serializeComponent(t){let e=g.get(t);if(!e)return h.error(new Error(`[Virid Amber] Serialize Error: Component ${t.name} is not registered.`)),null;let n=Reflect.getMetadata(s.CUSTOM_METHOD,t)?.serialize||k.serialization;try{return n(e)}catch(o){h.error(o,`[Virid Amber] Serialize Error: Component ${t.name} serialization error`)}return null}deserializeComponent(t,e){let i=g.get(t);if(!i){h.error(new Error(`[Virid Amber] Deserialize Error: Component ${t.name} is not registered.`));return}if(!e){h.error(new Error(`[Virid Amber] Deserialize Error: Component ${t.name} data is ${e}!.`));return}let o=Reflect.getMetadata(s.CUSTOM_METHOD,t)?.deserialize||k.deserialization;try{o(i,e)}catch(c){h.error(c,`[Virid Amber] Deserialize Error: Component ${t.name} deserialize error`)}}diffComponent(t,e){let i=g.get(t);if(!i)return h.error(new Error(`[Virid Amber] Diff Error: Component ${t.name} is not registered.`)),!1;if(!e)return h.error(new Error(`[Virid Amber] Diff Error: Component ${t.name} data is ${e}!.`)),!1;let o=Reflect.getMetadata(s.CUSTOM_METHOD,t)?.diff||k.diff;try{return o(i,e)}catch(c){h.error(c,`[Virid Amber] Diff Error: Component ${t.name} diff error`)}return!1}initComponent(t){let e=t.constructor,n=Reflect.getMetadata(s.CUSTOM_METHOD,e)?.serialize||k.serialization,o;try{o=n(t)}catch(c){h.error(c,`[Virid Amber] Serialize Error: Component ${e.name} serialization error`);return}this.componentHistory.set(e,[]),this.biasVersions.set(e,0),this.componentHistory.get(e).push(o),this.currentHistory.set(e,o),Reflect.defineMetadata(s.VERSION,0,e)}getCurrentData(t){return this.currentHistory.get(t)}getMinVersion(t){return this.biasVersions.get(t)??0}getMaxVersion(t){let e=this.componentHistory.get(t),i=this.biasVersions.get(t)??0;return!e||e.length===0?i:i+e.length-1}seal(t){let e=this.componentHistory.get(t);e||h.error(new Error(`[Virid Amber] Seal Error: Component ${t.name} is not initialized.`));let i=this.biasVersions.get(t),n=this.getCurrentData(t);if(n&&!this.diffComponent(t,n))return;let o=Reflect.getMetadata(s.CUSTOM_METHOD,t);o?.onBeforeBackup&&o.onBeforeBackup(n);let c=Reflect.getMetadata(s.VERSION,t)||0,m=i+e.length-1;c<m&&e.splice(c-i+1);let l=this.serializeComponent(t);if(!l){h.error(new Error(`[Virid Amber] Seal Error: Component ${t.name} is not serializable.`));return}e.push(l),this.currentHistory.set(t,l),e.length>this.maxComponentHistorySize&&(e.shift(),this.biasVersions.set(t,i+1));let y=this.biasVersions.get(t)+e.length-1;Reflect.defineMetadata(s.VERSION,y,t),o?.onAfterBackup&&o.onAfterBackup(l)}seek(t,e){let i=this.componentHistory.get(t);if(!i)return h.error(new Error(`[Virid Amber] Seal Error: Component ${t.name} is not initialized.`)),!1;let n=this.biasVersions.get(t)||0,o=e-n;if(o<0||o>=i.length)return!1;let c=this.getCurrentData(t),m=i[o],l=Reflect.getMetadata(s.VERSION,t)||0,y=e<l?p.UNDO:p.REDO;this.deserializeComponent(t,m),this.currentHistory.set(t,m),Reflect.defineMetadata(s.VERSION,e,t);let A=Reflect.getMetadata(s.CUSTOM_METHOD,t);return A?.onRestore&&A.onRestore(c,m,y),u.updateTickHistory(),!0}resetComponentInternal(t){let e=this.serializeComponent(t);e&&(this.componentHistory.set(t,[e]),this.biasVersions.set(t,0),this.currentHistory.set(t,e),Reflect.defineMetadata(s.VERSION,0,t))}resetComponent(t){this.resetComponentInternal(t),u.updateTickHistory()}},a(M,"AmberComponentStore"),M),u=new B,f=new $;var x=class x{static getVersion(t){return Reflect.getMetadata(s.VERSION,t)||0}static canUndo(t){let e=this.getVersion(t),i=f.getMinVersion(t);return e>i}static canRedo(t){let e=this.getVersion(t),i=f.getMaxVersion(t);return e<i}static undo(t){if(!this.canUndo(t))return!1;let e=this.getVersion(t);return f.seek(t,e-1)}static redo(t){if(!this.canRedo(t))return!1;let e=this.getVersion(t);return f.seek(t,e+1)}static undoTick(){if(!this.canUndoTick())return!1;let t=u.currentTick-1;return u.travel(t),!0}static redoTick(){if(!this.canRedoTick())return!1;let t=u.currentTick+1;return u.travel(t),!0}static canUndoTick(){let t=u.currentTick,e=u.getMinTick();return t>e}static canRedoTick(){let t=u.currentTick,e=u.getMaxTick();return t<e}static resetTick(){u.resetTickStore()}static resetComponent(t){f.resetComponent(t)}static resetAll(){Array.from(f.componentHistory.keys()).forEach(e=>{f.resetComponentInternal(e)}),u.resetTickStore()}};a(x,"Amber");var z=x;import{AtomicModifyMessage as C,ErrorMessage as N,WarnMessage as j,InfoMessage as L}from"@virid/core";var V=new Set,I=a((r,t)=>{if(r instanceof N||r instanceof j||r instanceof L)return;(r instanceof C?[...t.context.params,r.ComponentClass]:t.context.params).forEach(i=>{Reflect.hasMetadata(s.BACKUP,i)&&V.add(i)})},"afterExecuteHooks"),w=a(r=>{V.size!==0&&(V.forEach(t=>{f.seal(t)}),u.updateTickHistory(),V.clear())},"afterTickHooks");var R=null;function _(r,t){r.onAfterTick(w,!0),r.onAfterExecute(F,I,!0),t&&O(t);let e=a(i=>(i&&Reflect.hasMetadata(s.VERSION,i.constructor)&&f.initComponent(i),i),"amberInitHook");r.addActivationHook(e),R=r}a(_,"activateApp");var g=new Proxy({},{get(r,t){return(...e)=>{if(!R)return P.warn(`[Virid Vue] App method "${String(t)}" called before initialization.`),null;let i=R[t];if(typeof i=="function")return i.apply(R,e)}}});function Ht(r){return t=>{Reflect.defineMetadata(s.BACKUP,!0,t),Reflect.defineMetadata(s.VERSION,0,t),r&&Reflect.defineMetadata(s.CUSTOM_METHOD,r,t)}}a(Ht,"Backup");var It={name:"@virid/amber",install(r,t){_(r,t)}};export{z as Amber,It as AmberPlugin,Ht as Backup,p as RestoreDirection,O as activateConfig,I as afterExecuteHooks,w as afterTickHooks,f as amberComponentStore,u as amberTickStore};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@virid/amber",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.2",
|
|
5
5
|
"description": "Add replay, redo, undo and other functions to virid",
|
|
6
6
|
"author": "Ailrid",
|
|
7
7
|
"license": "Apache 2.0",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
],
|
|
14
14
|
"repository": {
|
|
15
15
|
"type": "git",
|
|
16
|
-
"url": "git+https://github.com/
|
|
16
|
+
"url": "git+https://github.com/Ailrid/virid.git"
|
|
17
17
|
},
|
|
18
18
|
"main": "./dist/index.cjs",
|
|
19
19
|
"module": "./dist/index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@virid/core": "0.1.
|
|
37
|
+
"@virid/core": "0.1.2"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsup --config tsup.config.ts",
|