├── LICENSE
├── README.md
├── examples
├── 2fa.json
└── revault.json
├── index.html
├── rust
├── .gitignore
├── Cargo.toml
├── README.md
└── src
│ └── lib.rs
├── screenshot.png
└── www
├── app.js
├── js
├── area-plugin.min.js
├── auto-arrange-plugin.min.js
├── comment-plugin.min.js
├── connection-mastery-plugin.min.js
├── connection-plugin.min.js
├── context-menu-plugin.min.js
├── history-plugin.min.js
├── lodash.min.js
├── rete.min.js
├── vue-render-plugin.min.js
└── vue.min.js
└── style.css
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Stepan Snigirev
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Miniscript playground
2 |
3 | WORK IN PROGRESS
4 |
5 | WASM + [rust-miniscript](https://github.com/rust-bitcoin/rust-miniscript) + [rete.js](https://github.com/retejs/rete) = miniscript node editor
6 |
7 | Try online: [miniscript.fun](https://miniscript.fun)
8 |
9 | 
10 |
11 | ## How to build and run
12 |
13 | You will need [rust](https://www.rust-lang.org/tools/install) and [wasm-pack](https://rustwasm.github.io/wasm-pack/installer/), then:
14 | ```
15 | cd rust
16 | cargo build
17 | wasm-pack build --target web
18 | cd ..
19 | python3 -m http.server
20 | ```
21 |
22 | You'll see the website on http://localhost:8000
23 |
24 | ## Roadmap
25 |
26 | Planned:
27 |
28 | - [x] all policy op-codes
29 | - [x] address derivation
30 | - [x] automatic url encoding of full editor state (`#/full/b64-encoded-json`)
31 | - [x] node inspector with node output
32 | - [x] bip39 and DescriptorKey nodes (mnemonic to root key, then derive with path and select allowed derivation)
33 | - [x] network selector (bitcoin / testnet / regtest / signet)
34 | - [x] export/import file and loading nodes state from external json (`#/url/path/to/config.json`)
35 | - [x] add labels for inputs like threshold, mnemonic, password, derivation path etc.
36 | - [ ] allow aliases for keys (any string instead of a valid (x)pub)
37 | - [ ] add control panel with all nodes
38 | - [ ] add info-description for nodes
39 | - [ ] use sortedmulti instead of multi
40 | - [ ] build nodes from url with policy (`#/policy/andor(blah(blah))`)
41 | - [ ] build nodes from url with descriptor (`#/descriptor/wsh(andor(blah(blah)))`) - uplift?
42 | - [ ] simple miniscript wallet using block explorer
43 | - [ ] fetch balances
44 | - [ ] create psbt
45 | - [ ] finalize psbt
46 | - [ ] Specter-DIY QR support
47 | - [ ] allow xprv and WIF keys?
48 | - [ ] taproot and tapscript
49 | - [ ] wpkh, sh(wsh), sh(wpkh)
50 |
--------------------------------------------------------------------------------
/examples/2fa.json:
--------------------------------------------------------------------------------
1 | {
2 | "network": "bitcoin",
3 | "id": "demo@0.1.0",
4 | "nodes": {
5 | "2": {
6 | "id": 2,
7 | "data": {
8 | "mnemonic": "crash fatal hollow thank swallow submit tattoo portion code foam math force",
9 | "password": "qwe",
10 | "derivation": "m/48h/0h/0h/2h"
11 | },
12 | "inputs": {},
13 | "outputs": {
14 | "key": {
15 | "connections": [
16 | {
17 | "node": 3,
18 | "input": "key",
19 | "data": {}
20 | }
21 | ]
22 | }
23 | },
24 | "position": [
25 | -152.8076377700496,
26 | -17.469585946171378
27 | ],
28 | "name": "BIP39"
29 | },
30 | "3": {
31 | "id": 3,
32 | "data": {},
33 | "inputs": {
34 | "key": {
35 | "connections": [
36 | {
37 | "node": 2,
38 | "output": "key",
39 | "data": {}
40 | }
41 | ]
42 | }
43 | },
44 | "outputs": {
45 | "key": {
46 | "connections": [
47 | {
48 | "node": 11,
49 | "input": "pol1",
50 | "data": {}
51 | }
52 | ]
53 | }
54 | },
55 | "position": [
56 | 122.914765186986,
57 | 5.616892819342844
58 | ],
59 | "name": "Key"
60 | },
61 | "6": {
62 | "id": 6,
63 | "data": {},
64 | "inputs": {
65 | "pol": {
66 | "connections": [
67 | {
68 | "node": 11,
69 | "output": "pol",
70 | "data": {}
71 | }
72 | ]
73 | }
74 | },
75 | "outputs": {
76 | "desc": {
77 | "connections": [
78 | {
79 | "node": 7,
80 | "input": "desc",
81 | "data": {}
82 | }
83 | ]
84 | }
85 | },
86 | "position": [
87 | 693.6821541648973,
88 | 53.389483440603115
89 | ],
90 | "name": "Descriptor"
91 | },
92 | "7": {
93 | "id": 7,
94 | "data": {
95 | "idx": 0
96 | },
97 | "inputs": {
98 | "desc": {
99 | "connections": [
100 | {
101 | "node": 6,
102 | "output": "desc",
103 | "data": {}
104 | }
105 | ]
106 | }
107 | },
108 | "outputs": {
109 | "addr": {
110 | "connections": []
111 | }
112 | },
113 | "position": [
114 | 988.2460797658175,
115 | 20.323670883283746
116 | ],
117 | "name": "Address"
118 | },
119 | "9": {
120 | "id": 9,
121 | "data": {
122 | "key": "xpub6DywUwVSjaQuHySfPtkSVc8XyjxyFzCJ9e2e3KQMf5QBycShzmB47q9Jtbf1uPQtFVLgpQ1dLwrR3UCBoEyNxEX2c2WzPEss1LZRp6o9DEV/0/*"
123 | },
124 | "inputs": {
125 | "key": {
126 | "connections": []
127 | }
128 | },
129 | "outputs": {
130 | "key": {
131 | "connections": [
132 | {
133 | "node": 10,
134 | "input": "pol1",
135 | "data": {}
136 | }
137 | ]
138 | }
139 | },
140 | "position": [
141 | -136.09896079872425,
142 | 334.11622465974835
143 | ],
144 | "name": "Key"
145 | },
146 | "10": {
147 | "id": 10,
148 | "data": {
149 | "ratio": 99
150 | },
151 | "inputs": {
152 | "pol1": {
153 | "connections": [
154 | {
155 | "node": 9,
156 | "output": "key",
157 | "data": {}
158 | }
159 | ]
160 | },
161 | "pol2": {
162 | "connections": [
163 | {
164 | "node": 13,
165 | "output": "pol",
166 | "data": {}
167 | }
168 | ]
169 | }
170 | },
171 | "outputs": {
172 | "pol": {
173 | "connections": [
174 | {
175 | "node": 11,
176 | "input": "pol2",
177 | "data": {}
178 | }
179 | ]
180 | }
181 | },
182 | "position": [
183 | 140.62768701089715,
184 | 227.67396146156142
185 | ],
186 | "name": "Or"
187 | },
188 | "11": {
189 | "id": 11,
190 | "data": {},
191 | "inputs": {
192 | "pol1": {
193 | "connections": [
194 | {
195 | "node": 3,
196 | "output": "key",
197 | "data": {}
198 | }
199 | ]
200 | },
201 | "pol2": {
202 | "connections": [
203 | {
204 | "node": 10,
205 | "output": "pol",
206 | "data": {}
207 | }
208 | ]
209 | }
210 | },
211 | "outputs": {
212 | "pol": {
213 | "connections": [
214 | {
215 | "node": 6,
216 | "input": "pol",
217 | "data": {}
218 | }
219 | ]
220 | }
221 | },
222 | "position": [
223 | 418.6244747244316,
224 | 63.23954774359686
225 | ],
226 | "name": "And"
227 | },
228 | "13": {
229 | "id": 13,
230 | "data": {
231 | "num": 12960
232 | },
233 | "inputs": {},
234 | "outputs": {
235 | "pol": {
236 | "connections": [
237 | {
238 | "node": 10,
239 | "input": "pol2",
240 | "data": {}
241 | }
242 | ]
243 | }
244 | },
245 | "position": [
246 | -139.81149767680319,
247 | 521.6599845978153
248 | ],
249 | "name": "Older"
250 | }
251 | }
252 | }
--------------------------------------------------------------------------------
/examples/revault.json:
--------------------------------------------------------------------------------
1 | {"id":"demo@0.1.0","nodes":{"6":{"id":6,"data":{},"inputs":{"pol":{"connections":[{"node":8,"output":"pol","data":{}}]}},"outputs":{"desc":{"connections":[{"node":7,"input":"desc","data":{}}]}},"position":[827.2563990815245,279.64567139130827],"name":"Descriptor"},"7":{"id":7,"data":{"idx":0},"inputs":{"desc":{"connections":[{"node":6,"output":"desc","data":{}}]}},"outputs":{"addr":{"connections":[]}},"position":[1122.3006668714986,252.3892593730548],"name":"Address"},"8":{"id":8,"data":{"ratio":9},"inputs":{"pol1":{"connections":[{"node":10,"output":"pol","data":{}}]},"pol2":{"connections":[{"node":9,"output":"pol","data":{}}]}},"outputs":{"pol":{"connections":[{"node":6,"input":"pol","data":{}}]}},"position":[539.7785187461096,261.73422249533155],"name":"Or"},"9":{"id":9,"data":{"thresh":3},"inputs":{"policies":{"connections":[{"node":13,"output":"key","data":{}},{"node":14,"output":"key","data":{}},{"node":22,"output":"key","data":{}}]}},"outputs":{"pol":{"connections":[{"node":8,"input":"pol2","data":{}}]}},"position":[50.97798766840302,929.8555875494229],"name":"Threshold"},"10":{"id":10,"data":{},"inputs":{"pol1":{"connections":[{"node":11,"output":"pol","data":{}}]},"pol2":{"connections":[{"node":15,"output":"pol","data":{}}]}},"outputs":{"pol":{"connections":[{"node":8,"input":"pol1","data":{}}]}},"position":[244.13287322825937,163.76229566846416],"name":"And"},"11":{"id":11,"data":{"thresh":2},"inputs":{"policies":{"connections":[{"node":19,"output":"key","data":{}},{"node":20,"output":"key","data":{}},{"node":21,"output":"key","data":{}}]}},"outputs":{"pol":{"connections":[{"node":10,"input":"pol1","data":{}}]}},"position":[-88.88949924349397,-183.66528682247528],"name":"Threshold"},"12":{"id":12,"data":{"num":1290},"inputs":{},"outputs":{"pol":{"connections":[{"node":15,"input":"pol2","data":{}}]}},"position":[-459.3368488511535,595.6358175836106],"name":"Older"},"13":{"id":13,"data":{"key":"xpub6EvTWdPaWk4SMyt51Zwq5yvNKMBZr24hPDLFcArvd6W3iRuTQ2eZWcrJR7N7ctnHAcGdtDdD9ATDTrEaaTPwbGhhaSWyU97B64JhmaM5uu3/0/*"},"inputs":{"key":{"connections":[]}},"outputs":{"key":{"connections":[{"node":9,"input":"policies","data":{}}]}},"position":[-271.82122028608967,958.290584727851],"name":"Key"},"14":{"id":14,"data":{"key":"xpub6CWGuhPzd1cMoPupGHKHyG2MCVTnVARRXVns8LFp1ibc19ZAnWCLFNf35Xc9bEyiG4wUN8pBra4Yh3gxGdtQjB1MK68TPpocSDLUYAf4fnd/0/*"},"inputs":{"key":{"connections":[]}},"outputs":{"key":{"connections":[{"node":9,"input":"policies","data":{}}]}},"position":[-501.59028731749675,1089.3701482726492],"name":"Key"},"15":{"id":15,"data":{},"inputs":{"pol1":{"connections":[{"node":16,"output":"pol","data":{}}]},"pol2":{"connections":[{"node":12,"output":"pol","data":{}}]}},"outputs":{"pol":{"connections":[{"node":10,"input":"pol2","data":{}}]}},"position":[-108.57625211982548,402.6066832486713],"name":"And"},"16":{"id":16,"data":{"thresh":3},"inputs":{"policies":{"connections":[{"node":17,"output":"key","data":{}},{"node":18,"output":"key","data":{}},{"node":23,"output":"key","data":{}}]}},"outputs":{"pol":{"connections":[{"node":15,"input":"pol1","data":{}}]}},"position":[-866.597747841136,395.0611831712697],"name":"Threshold"},"17":{"id":17,"data":{"key":"xpub6FHmQQjpxgAQZb88QhEAZE5gANtyBjJjHRT1bi5m4RAVdtB2zYgiX6E7cyxCK9ZBB3m5VWe6Lc2EWZztXe7b5ZA5UbZccDm9qidU1n55yiy"},"inputs":{"key":{"connections":[]}},"outputs":{"key":{"connections":[{"node":16,"input":"policies","data":{}}]}},"position":[-1182.1877215662512,303.964919063744],"name":"Key"},"18":{"id":18,"data":{"key":"xpub6EehKwrhACGqgkrPyPRZ3zxMoreVUkFaYckbCwP3s6gMgTBuDjQLQs1XarhTfV1hTjvJfdZXXCGX7qHUM7Rz6o36w12uEUUg1hSPgVRzTAr"},"inputs":{"key":{"connections":[]}},"outputs":{"key":{"connections":[{"node":16,"input":"policies","data":{}}]}},"position":[-1209.6072719759593,594.9103154902438],"name":"Key"},"19":{"id":19,"data":{"key":"xpub6EK3QXDHrj3HeXedaYxZnKHEdtmY8ccnYk6WHco7U9Ru9xBLP6Ug5FdtjwqigAxeYNVWe14LGmkGQ9nwCqiUgnZ4Rt8ato6mvQyNRMhv4Wi/0/*"},"inputs":{"key":{"connections":[]}},"outputs":{"key":{"connections":[{"node":11,"input":"policies","data":{}}]}},"position":[-438.19899670805273,-51.67724682662989],"name":"Key"},"20":{"id":20,"data":{"key":"xpub6CPE9XefeSBxNG2vmXXJ247sJWWixmBdz15E2PGweKnzuZaWfrGQsQLuM7t4wM9fLw871afg3iWNPBm4vQPC8efSMbqQFvT1KMrxeQ1kHAU/0/*"},"inputs":{"key":{"connections":[]}},"outputs":{"key":{"connections":[{"node":11,"input":"policies","data":{}}]}},"position":[-386.2967196437753,-293.0470047032918],"name":"Key"},"21":{"id":21,"data":{"key":"xpub6DVebFbt5rtyYb8Xj4twxjivGZpw5Z51pdyefftKGrGHqr6RfoZkLY9HJkKgx5cBToQAqY3V7vWatKhZ9rxRyhzx3Tj9866bsUoxCn59c7A"},"inputs":{"key":{"connections":[]}},"outputs":{"key":{"connections":[{"node":11,"input":"policies","data":{}}]}},"position":[-642.246150860148,-200.79213393240119],"name":"Key"},"22":{"id":22,"data":{"key":"xpub6DLb8bF7d1M1BZffkWjL5CTETTyrGRHvb8EVWxZmpx2T9T9KkufAiBpB8ZzfP4v59y7V6Ly72Ep8zh2k6jy73TJPcJWEqLHXr5AYhscwq72/0/*"},"inputs":{"key":{"connections":[]}},"outputs":{"key":{"connections":[{"node":9,"input":"policies","data":{}}]}},"position":[-291.09865062496556,1172.2041892146071],"name":"Key"},"23":{"id":23,"data":{"key":"xpub6Bo96rk3NyBGhUvNkbD173RZJx4hLKpRqfM8TpbdfyvPxQYEramZCPnvGwZ1jtqDBjwPnDbATxNNSy3N5tabSayTbrAZcE4mHNrqpZz179v"},"inputs":{"key":{"connections":[]}},"outputs":{"key":{"connections":[{"node":16,"input":"policies","data":{}}]}},"position":[-1423.943010648504,423.75142363071006],"name":"Key"}},"comments":[{"text":"All stakeholders must sign","position":[-531.5902873174967,895.0763256877427],"links":[22,14,9,13],"type":"frame","width":832.5682749858997,"height":522.1278635268644},{"text":"All cosigners must sign","position":[-1453.943010648504,273.964919063744],"links":[17,16,18,23],"type":"frame","width":837.3452628073679,"height":565.9453964264998},{"text":"At least 2 managers must sign","position":[-672.246150860148,-354.5131448465522],"links":[11,19,21,20],"type":"frame","width":833.3566516166541,"height":547.8358980199223}]}
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Miniscript builder
7 |
8 |
9 |
10 |
11 |
12 |
Right-click to add or delete nodes.
For frame comment select nodes and press Shifh+F
.
13 |
21 |
22 |
23 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/rust/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | Cargo.lock
3 | pkg
--------------------------------------------------------------------------------
/rust/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "compiler"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | crate-type = ["cdylib", "rlib"]
8 |
9 | [dependencies]
10 | wasm-bindgen = { version = "0.2.78" }
11 | # miniscript = {version = "9.0.1", features=["compiler"]}
12 | miniscript = {git = "https://github.com/rust-bitcoin/rust-miniscript.git", rev = "4a3ba11c2fd5063be960741d557f3f7a28041e1f", features=["compiler"]}
13 | bip39 = "1.0.1"
14 |
--------------------------------------------------------------------------------
/rust/README.md:
--------------------------------------------------------------------------------
1 | # Wasm Rust miniscript functions
2 |
3 | ## build
4 |
5 | ```sh
6 | cargo build
7 | wasm-pack build --target web
8 | ```
9 |
--------------------------------------------------------------------------------
/rust/src/lib.rs:
--------------------------------------------------------------------------------
1 | use wasm_bindgen::prelude::*;
2 | use std::str::FromStr;
3 |
4 | use miniscript::bitcoin::{self, secp256k1};
5 | use miniscript::bitcoin::util::bip32::{
6 | ExtendedPrivKey, ExtendedPubKey, DerivationPath
7 | };
8 | use miniscript::policy::Concrete;
9 | use miniscript::{Descriptor, DescriptorPublicKey};
10 |
11 | use bip39::Mnemonic;
12 |
13 | // https://github.com/rustwasm/wasm-bindgen/issues/1742#issuecomment-643793491
14 | macro_rules! jserr {
15 | ($expression:expr) => {
16 | match $expression {
17 | Ok(a) => a,
18 | Err(e) => {
19 | return Err(JsValue::from(format!("{}", e)));
20 | }
21 | }
22 | };
23 | }
24 |
25 | #[wasm_bindgen]
26 | pub fn compile(policy: &str) -> Result{
27 | let policy = jserr!(Concrete::::from_str(policy));
28 | let minisc = jserr!(Descriptor::new_wsh(jserr!(policy.compile())));
29 | Ok(minisc.to_string().into())
30 | }
31 |
32 | #[wasm_bindgen]
33 | pub fn address(desc: &str, idx: u32, network: &str) -> Result{
34 | let network = jserr!(network.parse::());
35 | if idx >= 0x80000000 {
36 | return Err(JsValue::from("Invalid index"));
37 | }
38 | let secp_ctx = secp256k1::Secp256k1::verification_only();
39 | let desc = jserr!(Descriptor::::from_str(desc));
40 | let desc = jserr!(desc.at_derivation_index(idx));
41 | let desc = jserr!(desc.derived_descriptor(&secp_ctx));
42 | let addr = jserr!(desc.address(network));
43 | Ok(addr.to_string().into())
44 | }
45 |
46 | #[wasm_bindgen]
47 | pub fn bip39_root(mnemonic: &str, password: &str, network: &str) -> Result {
48 | let network = jserr!(network.parse::());
49 | let mnemonic = jserr!(Mnemonic::parse(mnemonic));
50 | let seed = mnemonic.to_seed(password);
51 |
52 | // generate root bip-32 key from seed
53 | let root = jserr!(ExtendedPrivKey::new_master(network, &seed));
54 |
55 | let secp_ctx = secp256k1::Secp256k1::new();
56 | let fingerprint = root.fingerprint(&secp_ctx);
57 | Ok(format!("[{}]{}", fingerprint, root).into())
58 | }
59 |
60 | #[wasm_bindgen]
61 | pub fn bip39_derive(mnemonic: &str, password: &str, path: &str, network: &str) -> Result {
62 | let network = jserr!(network.parse::());
63 | let mnemonic = jserr!(Mnemonic::parse(mnemonic));
64 | let seed = mnemonic.to_seed(password);
65 | let derivation = jserr!(DerivationPath::from_str(path));
66 |
67 | // generate root bip-32 key from seed
68 | let secp_ctx = secp256k1::Secp256k1::new();
69 | let root = jserr!(ExtendedPrivKey::new_master(network, &seed));
70 | let fingerprint = root.fingerprint(&secp_ctx);
71 |
72 | let child = jserr!(root.derive_priv(&secp_ctx, &derivation));
73 | let xpub = ExtendedPubKey::from_priv(&secp_ctx, &child);
74 | let key = format!("[{}{}]{}", fingerprint, &path[1..], xpub);
75 |
76 | Ok(key.into())
77 | }
78 |
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stepansnigirev/miniscript-builder/a13777cd4390f4d5d8d8738f41df81b00268308b/screenshot.png
--------------------------------------------------------------------------------
/www/app.js:
--------------------------------------------------------------------------------
1 | var numSocket = new Rete.Socket('Number');
2 | var policySocket = new Rete.Socket('Policy');
3 | var keySocket = new Rete.Socket('Key');
4 | var descriptorSocket = new Rete.Socket('Descriptor');
5 | var stringSocket = new Rete.Socket('String');
6 |
7 | // helper function
8 | function sleep(ms) {
9 | return new Promise(resolve => setTimeout(resolve, ms));
10 | }
11 |
12 | var network = "bitcoin";
13 |
14 | /*************************** TEMPLATES *************************/
15 |
16 | // TODO: lots of copy-paste, figure out how to simplify
17 | var VueNumControl = {
18 | props: ['readonly', 'emitter', 'ikey', 'getData', 'putData', 'label'],
19 | template: ``,
20 | data() {
21 | return {
22 | value: 1,
23 | label: "Number",
24 | }
25 | },
26 | methods: {
27 | change(e){
28 | this.value = +e.target.value;
29 | this.update();
30 | },
31 | update() {
32 | if (this.ikey)
33 | this.putData(this.ikey, this.value)
34 | this.emitter.trigger('process');
35 | }
36 | },
37 | mounted() {
38 | this.value = this.getData(this.ikey);
39 | }
40 | }
41 |
42 | // TODO: ugly, make label dynamic and styled in css
43 | var VueRatioControl = {
44 | props: ['readonly', 'emitter', 'ikey', 'getData', 'putData', 'label'],
45 | template: ``,
46 | data() {
47 | return {
48 | value: 1,
49 | label: "Ratio:",
50 | }
51 | },
52 | methods: {
53 | change(e){
54 | this.value = +e.target.value;
55 | if(this.value <= 0){
56 | this.value = 1;
57 | }
58 | this.update();
59 | },
60 | update() {
61 | if (this.ikey)
62 | this.putData(this.ikey, this.value)
63 | this.emitter.trigger('process');
64 | }
65 | },
66 | mounted() {
67 | this.value = this.getData(this.ikey);
68 | if(this.value <= 0){
69 | this.value = 1;
70 | }
71 | }
72 | }
73 |
74 | var VueStringControl = {
75 | props: ['readonly', 'emitter', 'ikey', 'getData', 'putData', 'label'],
76 | template: ``,
77 | data() {
78 | return {
79 | value: "",
80 | label: "",
81 | }
82 | },
83 | methods: {
84 | change(e){
85 | this.value = e.target.value;
86 | this.update();
87 | },
88 | update() {
89 | if (this.ikey)
90 | this.putData(this.ikey, this.value)
91 | this.emitter.trigger('process');
92 | }
93 | },
94 | mounted() {
95 | this.value = this.getData(this.ikey);
96 | }
97 | }
98 |
99 | var VuePreviewControl = {
100 | props: ['readonly', 'emitter', 'ikey', 'getData', 'putData'],
101 | template: `{{value}}
`,
102 | data() {
103 | return {
104 | value: "",
105 | }
106 | },
107 | methods: {
108 | change(e){
109 | this.value = e.target.value;
110 | this.update();
111 | },
112 | update() {
113 | if (this.ikey)
114 | this.putData(this.ikey, this.value)
115 | this.emitter.trigger('process');
116 | }
117 | },
118 | mounted() {
119 | this.value = this.getData(this.ikey);
120 | }
121 | }
122 |
123 | /*************************** CONTROLS *************************/
124 |
125 | // TODO: lots of copy-paste, figure out how to simplify
126 | class NumControl extends Rete.Control {
127 |
128 | constructor(emitter, key, readonly, label="Number") {
129 | super(key);
130 | this.component = VueNumControl;
131 | this.props = { emitter, ikey: key, readonly, label };
132 | }
133 |
134 | setValue(val) {
135 | this.vueContext.value = val;
136 | }
137 | }
138 |
139 | class RatioControl extends Rete.Control {
140 |
141 | constructor(emitter, key, readonly, label="Probability ratio:") {
142 | super(key);
143 | this.component = VueRatioControl;
144 | this.props = { emitter, ikey: key, readonly, label };
145 | }
146 |
147 | setValue(val) {
148 | this.vueContext.value = val;
149 | }
150 | }
151 |
152 | class StringControl extends Rete.Control {
153 |
154 | constructor(emitter, key, readonly, label="") {
155 | super(key);
156 | this.component = VueStringControl;
157 | this.props = { emitter, ikey: key, readonly, label };
158 | }
159 |
160 | setValue(val) {
161 | this.vueContext.value = val;
162 | }
163 | }
164 |
165 | class PreviewControl extends Rete.Control {
166 |
167 | constructor(emitter, key, readonly) {
168 | super(key);
169 | this.component = VuePreviewControl;
170 | this.props = { emitter, ikey: key, readonly };
171 | }
172 |
173 | setValue(val) {
174 | this.vueContext.value = val;
175 | }
176 | }
177 |
178 | /*************************** POLICY COMPONENTS *************************/
179 |
180 | class BIP39Component extends Rete.Component {
181 |
182 | constructor(){
183 | super("BIP39");
184 | }
185 |
186 | builder(node) {
187 | var out = new Rete.Output('key', "Key", keySocket);
188 | return node
189 | .addControl(new PreviewControl(this.editor, 'preview', true))
190 | .addControl(new StringControl(this.editor, 'mnemonic', false, "Recovery phrase:"))
191 | .addControl(new StringControl(this.editor, 'password', false, "Password:"))
192 | .addControl(new StringControl(this.editor, 'derivation', false, "Derivation path:"))
193 | .addOutput(out);
194 | }
195 |
196 | worker(node, inputs, outputs) {
197 | let out = '';
198 | try{
199 | let mnemonic = (node.data.mnemonic) ? node.data.mnemonic : '';
200 | let password = (node.data.password) ? node.data.password : '';
201 | let derivation = (node.data.derivation) ? node.data.derivation : 'm';
202 | out = miniscript.bip39_derive(mnemonic, password, derivation, network) + "/0/*";
203 | }catch (e){
204 | out = `${e}`;
205 | console.error(e);
206 | }
207 | this.editor.nodes.find(n => n.id == node.id).controls.get('preview').setValue(out);
208 | outputs['key'] = out;
209 | }
210 | }
211 |
212 | class KeyComponent extends Rete.Component {
213 |
214 | constructor(){
215 | super("Key");
216 | }
217 |
218 | builder(node) {
219 | var out = new Rete.Output('key', "Policy", policySocket);
220 | var inp = new Rete.Input("key", "Key", keySocket);
221 | inp.addControl(new StringControl(this.editor, 'key', false, "[Extended] Public Key:"));
222 | return node
223 | .addControl(new PreviewControl(this.editor, 'preview', true))
224 | .addInput(inp)
225 | .addOutput(out);
226 | }
227 |
228 | worker(node, inputs, outputs) {
229 | let k = inputs['key'].length ? inputs['key'][0] : node.data.key;
230 | k = k ? k : '';
231 | let val = `pk(${k})`;
232 | this.editor.nodes.find(n => n.id == node.id).controls.get('preview').setValue(val);
233 | outputs['key'] = val;
234 | }
235 | }
236 |
237 | class AndComponent extends Rete.Component {
238 | constructor(){
239 | super("And");
240 | }
241 |
242 | builder(node) {
243 | var inp1 = new Rete.Input('pol1',"Policy", policySocket);
244 | var inp2 = new Rete.Input('pol2', "Policy", policySocket);
245 | var out = new Rete.Output('pol', "Policy", policySocket);
246 |
247 | inp1.addControl(new StringControl(this.editor, 'pol1'))
248 | inp2.addControl(new StringControl(this.editor, 'pol2'))
249 |
250 | return node
251 | .addInput(inp1)
252 | .addInput(inp2)
253 | .addControl(new PreviewControl(this.editor, 'preview', true))
254 | .addOutput(out);
255 | }
256 |
257 | worker(node, inputs, outputs) {
258 | var p1 = inputs['pol1'].length?inputs['pol1'][0]:node.data.pol1;
259 | var p2 = inputs['pol2'].length?inputs['pol2'][0]:node.data.pol2;
260 |
261 | var pol = `and(${p1 ? p1 : ''},${p2 ? p2 : ''})`;
262 | this.editor.nodes.find(n => n.id == node.id).controls.get('preview').setValue(pol);
263 | outputs['pol'] = pol;
264 | }
265 | }
266 |
267 | // TODO: add probabilities
268 | class OrComponent extends Rete.Component {
269 | constructor(){
270 | super("Or");
271 | }
272 |
273 | builder(node) {
274 | var inp1 = new Rete.Input('pol1',"Policy", policySocket);
275 | var inp2 = new Rete.Input('pol2', "Policy", policySocket);
276 | var out = new Rete.Output('pol', "Policy", policySocket);
277 |
278 | inp1.addControl(new StringControl(this.editor, 'pol1'))
279 | inp2.addControl(new StringControl(this.editor, 'pol2'))
280 |
281 | return node
282 | .addInput(inp1)
283 | .addInput(inp2)
284 | .addControl(new PreviewControl(this.editor, 'preview', true))
285 | .addControl(new RatioControl(this.editor, "ratio"))
286 | .addOutput(out);
287 | }
288 |
289 | worker(node, inputs, outputs) {
290 | var ratio = (node.data.ratio) ? node.data.ratio : 1;
291 | let r1 = Math.round(ratio*100);
292 | let r2 = 100;
293 | // mutual reduce
294 | while(r1%10 == 0 && r2%10 == 0){
295 | r1 = Math.round(r1/10);
296 | r2 = Math.round(r2/10);
297 | }
298 | var p1 = inputs['pol1'].length?inputs['pol1'][0]:node.data.pol1;
299 | var p2 = inputs['pol2'].length?inputs['pol2'][0]:node.data.pol2;
300 | // handle undefined
301 | p1 = (p1) ? p1 : '';
302 | p2 = (p2) ? p2 : '';
303 | if(r1 != r2){
304 | if(r1 != 1){
305 | p1 = `${r1}@${p1}`;
306 | }
307 | if(r2 != 1){
308 | p2 = `${r2}@${p2}`;
309 | }
310 | }
311 | var pol = `or(${p1},${p2})`;
312 | this.editor.nodes.find(n => n.id == node.id).controls.get('preview').setValue(pol);
313 | outputs['pol'] = pol;
314 | }
315 | }
316 |
317 | class TimeComponent extends Rete.Component {
318 | constructor(name="After"){
319 | super(name);
320 | this._op = name.toLowerCase();
321 | this._label = (this._op == "after") ? "Blockheight:" : "Blocks from prev tx:";
322 | }
323 |
324 | builder(node) {
325 | // var inp = new Rete.Input('num',"Number", numSocket);
326 | var out = new Rete.Output('pol', "Policy", policySocket);
327 |
328 | return node
329 | .addControl(new PreviewControl(this.editor, 'preview', true))
330 | .addControl(new NumControl(this.editor, 'num', false, this._label))
331 | .addOutput(out);
332 | }
333 |
334 | worker(node, inputs, outputs) {
335 | var n = node.data.num;
336 | n = (n==undefined) ? 0 : n;
337 | var pol = `${this._op}(${n})`;
338 |
339 | this.editor.nodes.find(n => n.id == node.id).controls.get('preview').setValue(pol);
340 | outputs['pol'] = pol;
341 | }
342 | }
343 |
344 | class HashComponent extends Rete.Component {
345 | constructor(name="SHA256"){
346 | super(name);
347 | this._op = name.toLowerCase();
348 | this._len = this._op.search("256") >= 0 ? 32 : 20
349 | }
350 |
351 | builder(node) {
352 | // var inp = new Rete.Input('num',"Number", numSocket);
353 | var out = new Rete.Output('pol', "Policy", policySocket);
354 |
355 | return node
356 | .addControl(new PreviewControl(this.editor, 'preview', true))
357 | .addControl(new StringControl(this.editor, 'hash', false, `${this._len}-byte hash in hex`))
358 | .addOutput(out);
359 | }
360 |
361 | worker(node, inputs, outputs) {
362 | var n = node.data.hash;
363 | n = (n==undefined) ? '' : n;
364 | var pol = `${this._op}(${n})`;
365 |
366 | this.editor.nodes.find(n => n.id == node.id).controls.get('preview').setValue(pol);
367 | outputs['pol'] = pol;
368 | }
369 | }
370 |
371 | class ThreshComponent extends Rete.Component {
372 | constructor(){
373 | super("Threshold");
374 | }
375 |
376 | builder(node) {
377 | let pol = new Rete.Input(`policies`, "Policies", policySocket, true);
378 | node.addInput(pol);
379 |
380 | var out = new Rete.Output('pol', "Policy", policySocket);
381 |
382 | return node
383 | .addControl(new PreviewControl(this.editor, 'preview', true))
384 | .addControl(new NumControl(this.editor, 'thresh', false, "Threshold:"))
385 | .addOutput(out);
386 | }
387 |
388 | worker(node, inputs, outputs) {
389 | let n = node.data.thresh;
390 | n = (n==undefined) ? 1 : n;
391 | let nodeobj = this.editor.nodes.find(n => n.id == node.id);
392 | let args = "";
393 | // sort alphabetically, bad but at least deterministic
394 | inputs['policies'].sort().forEach((inpol)=>{
395 | args += `,${inpol}`
396 | })
397 | let pol = `thresh(${n}${args})`;
398 | nodeobj.controls.get('preview').setValue(pol);
399 | outputs['pol'] = pol;
400 | nodeobj.update();
401 | }
402 | }
403 |
404 | /*************************** DESCRIPTOR *************************/
405 |
406 | class DescriptorComponent extends Rete.Component {
407 | constructor(){
408 | super("Descriptor");
409 | }
410 |
411 | builder(node) {
412 | var inp = new Rete.Input('pol',"Policy", policySocket);
413 | var out = new Rete.Output('desc', "Descriptor", descriptorSocket);
414 |
415 | inp.addControl(new StringControl(this.editor, 'pol'))
416 |
417 | return node
418 | .addInput(inp)
419 | .addControl(new PreviewControl(this.editor, 'preview', true))
420 | .addOutput(out);
421 | }
422 |
423 | worker(node, inputs, outputs) {
424 | let pol = inputs['pol'].length ? inputs['pol'][0] : node.data.pol;
425 | let desc = '';
426 | try{
427 | desc = miniscript.compile(pol);
428 | }catch(e){
429 | desc = `Error: ${e}`;
430 | }
431 | this.editor.nodes.find(n => n.id == node.id).controls.get('preview').setValue(desc);
432 | outputs['desc'] = desc;
433 | }
434 | }
435 |
436 | class AddressComponent extends Rete.Component {
437 | constructor(){
438 | super("Address");
439 | }
440 |
441 | builder(node) {
442 | var inp = new Rete.Input('desc',"Descriptor", descriptorSocket);
443 | var out = new Rete.Output('addr', "Address", stringSocket);
444 |
445 | inp.addControl(new StringControl(this.editor, 'desc', false, "Descriptor:"))
446 |
447 | return node
448 | .addInput(inp)
449 | .addControl(new PreviewControl(this.editor, 'preview', true))
450 | .addControl(new NumControl(this.editor, 'idx', false, "Index:"))
451 | .addOutput(out);
452 | }
453 |
454 | worker(node, inputs, outputs) {
455 | let desc = inputs['desc'].length ? inputs['desc'][0] : node.data.desc;
456 | let idx = node.data.idx;
457 | let addr = '';
458 | try{
459 | addr = miniscript.address(desc, idx, network);
460 | }catch(e){
461 | addr = `Error: ${e}`;
462 | }
463 | this.editor.nodes.find(n => n.id == node.id).controls.get('preview').setValue(addr);
464 | outputs['addr'] = addr;
465 | }
466 | }
467 |
468 | function toBase64(obj){
469 | // ugly hack: inject network here
470 | obj.network = network;
471 | return btoa(JSON.stringify(obj)).replace(/\//g, '_').replace(/\+/g, '-');
472 | }
473 |
474 | function fromBase64(b64){
475 | let obj = JSON.parse(atob(b64.replace(/_/g, '/').replace(/-/g, '+')));
476 | return obj;
477 | }
478 |
479 | async function loadObj(obj){
480 | // get network from obj
481 | let net = obj.network;
482 | if(net != undefined){
483 | document.getElementById("network").value = net;
484 | network = net;
485 | }
486 | await editor.fromJSON(obj);
487 | editor.view.resize();
488 | AreaPlugin.zoomAt(editor);
489 | editor.trigger("process");
490 | await sleep(300);
491 | editor.trigger("process");
492 | // select descriptor by default
493 | let d = editor.nodes.find(n => n.name == "Descriptor");
494 | if(d){
495 | editor.selectNode(d);
496 | }
497 | }
498 |
499 | async function loadHash(){
500 | try{
501 | let obj = {};
502 | if(window.location.hash.startsWith("#/full/")){
503 | obj = fromBase64(window.location.hash.substr("#/full/".length))
504 | }else if(window.location.hash.startsWith("#/url/")){
505 | let url = window.location.hash.substr("#/url/".length);
506 | let res = await fetch(url);
507 | obj = await res.json();
508 | }else{
509 | throw "Can't parse hash";
510 | }
511 | await loadObj(obj);
512 | }catch(e){
513 | console.error(`Error: ${e}`)
514 | }
515 | }
516 |
517 | function exportJSON(el){
518 | let obj = editor.toJSON();
519 | let data = "text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(obj));
520 |
521 | el.setAttribute("href", "data:"+data);
522 | el.setAttribute("download", "data.json");
523 | }
524 | document.getElementById("jsonfile").addEventListener("change", async (e) => {
525 | files = e.currentTarget.files;
526 | for(let i=0; i<1; i++){
527 | let reader = new FileReader();
528 | reader.onload = async function(e) {
529 | let obj = JSON.parse(reader.result)
530 | await loadObj(obj);
531 | }
532 | reader.readAsText(files[i]);
533 | }
534 | });
535 |
536 | function arrangeNodes(){
537 | editor.arrange();
538 | editor.view.resize();
539 | AreaPlugin.zoomAt(editor);
540 | }
541 |
542 | /*************************** APP MAIN *************************/
543 |
544 | function displayOut(node){
545 | if(!node || !engine){
546 | return;
547 | }
548 | try{
549 | if(!engine.data){
550 | return;
551 | }
552 | let d = engine.data.nodes[node.id].outputData;
553 | let res = Object.values(d)[0];
554 | document.getElementById("node-name").innerText = node.name;
555 | document.getElementById("node-output").innerText = res;
556 | }catch(e){
557 | console.error(e);
558 | }
559 | }
560 |
561 | async function updateNetwork(){
562 | network = document.getElementById("network").value;
563 | editor.trigger('process');
564 | }
565 |
566 | window.app_init = async ()=>{
567 | var container = document.querySelector('#rete');
568 | let AndC = new AndComponent();
569 | let AfterC = new TimeComponent("After");
570 | let OlderC = new TimeComponent("Older");
571 | let ThreshC = new ThreshComponent();
572 | let KeyC = new KeyComponent();
573 | let OrC = new OrComponent();
574 | let DescC = new DescriptorComponent();
575 | let AddressC = new AddressComponent();
576 | let Sha256_C = new HashComponent("SHA256");
577 | let Ripemd160_C = new HashComponent("Ripemd160");
578 | let Hash256_C = new HashComponent("Hash256");
579 | let Hash160_C = new HashComponent("Hash160");
580 | let BIP39_C = new BIP39Component();
581 | var components = [
582 | BIP39_C,
583 | KeyC,
584 | AfterC,
585 | OlderC,
586 | ThreshC,
587 | AndC,
588 | OrC,
589 | Sha256_C,
590 | Ripemd160_C,
591 | Hash256_C,
592 | Hash160_C,
593 | DescC,
594 | AddressC,
595 | ];
596 |
597 | window.editor = new Rete.NodeEditor('demo@0.1.0', container);
598 | editor.use(ConnectionPlugin.default);
599 | editor.use(VueRenderPlugin.default);
600 | editor.use(ContextMenuPlugin.default, {
601 | searchBar: false,
602 | });
603 | editor.use(AreaPlugin);
604 | editor.use(CommentPlugin.default);
605 | editor.use(HistoryPlugin);
606 | // editor.use(DockPlugin.default,{
607 | // container: document.querySelector('#dock'),
608 | // // itemClass: 'item' // default: dock-item
609 | // plugins: [VueRenderPlugin.default], // render plugins
610 | // });
611 | editor.use(ConnectionMasteryPlugin.default);
612 | editor.use(AutoArrangePlugin.default); //, { margin: {x: 50, y: 50 }, depth: 0 }); // depth - max depth for arrange (0 - unlimited)
613 |
614 | window.engine = new Rete.Engine('demo@0.1.0');
615 |
616 | components.map(c => {
617 | editor.register(c);
618 | engine.register(c);
619 | });
620 |
621 | if(window.location.hash.length > 5){
622 | await loadHash();
623 | }else{
624 | let p1 = await OlderC.createNode({num: 12960});
625 | let mn = await BIP39_C.createNode({
626 | mnemonic: "crash fatal hollow thank swallow submit tattoo portion code foam math force",
627 | password:"",
628 | derivation:"m/48h/0h/0h/2h",
629 | });
630 | let p2 = await KeyC.createNode();
631 | let p3 = await KeyC.createNode({key: "038b4059419fe3b95acdee6aff2f9afdca87231d14bd2cbcd3367b11d9d819a71d"});
632 | let thresh = await ThreshC.createNode({thresh: 2});
633 | let desc = await DescC.createNode();
634 | let addr = await AddressC.createNode({idx: 0});
635 |
636 | p1.position = [80, 200];
637 | mn.position = [-200, 400];
638 | p2.position = [80, 400];
639 | p3.position = [80, 600];
640 | thresh.position = [500, 240];
641 | desc.position = [800, 240];
642 | addr.position = [1100, 240];
643 |
644 | editor.addNode(p1);
645 | editor.addNode(mn);
646 | editor.addNode(p2);
647 | editor.addNode(p3);
648 | editor.addNode(thresh);
649 | editor.addNode(desc);
650 | editor.addNode(addr);
651 |
652 | editor.connect(mn.outputs.get('key'), p2.inputs.get('key'));
653 | editor.connect(p1.outputs.get('pol'), thresh.inputs.get('policies'));
654 | editor.connect(thresh.outputs.get('pol'), desc.inputs.get('pol'));
655 | editor.connect(p2.outputs.get('key'), thresh.inputs.get('policies'));
656 | editor.connect(p3.outputs.get('key'), thresh.inputs.get('policies'));
657 | editor.connect(desc.outputs.get('desc'), addr.inputs.get('desc'));
658 |
659 | editor.view.resize();
660 | AreaPlugin.zoomAt(editor);
661 | }
662 |
663 | editor.on('process nodecreated noderemoved connectioncreated connectionremoved', async () => {
664 | let obj = editor.toJSON();
665 | let h = toBase64(obj);
666 | window.location.hash = "/full/"+h;
667 | await engine.abort();
668 | await engine.process(obj);
669 | if(editor.selected.list.length){
670 | displayOut(editor.selected.list[0]);
671 | }
672 | });
673 |
674 | editor.on('nodetranslated', async () => {
675 | let obj = editor.toJSON();
676 | let h = toBase64(obj);
677 | window.location.hash = "/full/"+h;
678 | });
679 |
680 | editor.on('nodeselected', async (node) => {
681 | displayOut(editor.selected.list[0]);
682 | });
683 |
684 | editor.trigger('process');
685 |
686 | // on hash change
687 | window.addEventListener("hashchange", async ()=>{
688 | let obj = editor.toJSON();
689 | let h = toBase64(obj);
690 | let prevhash = "#/full/"+h;
691 | // external hash change
692 | if(window.location.hash != prevhash){
693 | loadHash();
694 | }
695 | }, false);
696 |
697 | // select descriptor by default
698 | let d = editor.nodes.find(n => n.name == "Descriptor");
699 | if(d){
700 | editor.selectNode(d);
701 | }
702 | };
--------------------------------------------------------------------------------
/www/js/area-plugin.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * rete-area-plugin v0.2.1
3 | * (c) 2019
4 | * Released under the ISC license.
5 | */
6 | !function(t){"use strict";var u,e=Object.prototype,s=e.hasOwnProperty,n="function"==typeof Symbol?Symbol:{},o=n.iterator||"@@iterator",r=n.asyncIterator||"@@asyncIterator",i=n.toStringTag||"@@toStringTag",a="object"==typeof module,c=t.regeneratorRuntime;if(c)a&&(module.exports=c);else{(c=t.regeneratorRuntime=a?module.exports:{}).wrap=w;var l="suspendedStart",f="suspendedYield",p="executing",d="completed",y={},h={};h[o]=function(){return this};var v=Object.getPrototypeOf,g=v&&v(v(A([])));g&&g!==e&&s.call(g,o)&&(h=g);var m=L.prototype=b.prototype=Object.create(h);E.prototype=m.constructor=L,L.constructor=E,L[i]=E.displayName="GeneratorFunction",c.isGeneratorFunction=function(t){var e="function"==typeof t&&t.constructor;return!!e&&(e===E||"GeneratorFunction"===(e.displayName||e.name))},c.mark=function(t){return Object.setPrototypeOf?Object.setPrototypeOf(t,L):(t.__proto__=L,i in t||(t[i]="GeneratorFunction")),t.prototype=Object.create(m),t},c.awrap=function(t){return{__await:t}},k(_.prototype),_.prototype[r]=function(){return this},c.AsyncIterator=_,c.async=function(t,e,n,r){var o=new _(w(t,e,n,r));return c.isGeneratorFunction(e)?o:o.next().then(function(t){return t.done?t.value:o.next()})},k(m),m[i]="Generator",m[o]=function(){return this},m.toString=function(){return"[object Generator]"},c.keys=function(n){var r=[];for(var t in n)r.push(t);return r.reverse(),function t(){for(;r.length;){var e=r.pop();if(e in n)return t.value=e,t.done=!1,t}return t.done=!0,t}},c.values=A,z.prototype={constructor:z,reset:function(t){if(this.prev=0,this.next=0,this.sent=this._sent=u,this.done=!1,this.delegate=null,this.method="next",this.arg=u,this.tryEntries.forEach(T),!t)for(var e in this)"t"===e.charAt(0)&&s.call(this,e)&&!isNaN(+e.slice(1))&&(this[e]=u)},stop:function(){this.done=!0;var t=this.tryEntries[0].completion;if("throw"===t.type)throw t.arg;return this.rval},dispatchException:function(n){if(this.done)throw n;var r=this;function t(t,e){return i.type="throw",i.arg=n,r.next=t,e&&(r.method="next",r.arg=u),!!e}for(var e=this.tryEntries.length-1;0<=e;--e){var o=this.tryEntries[e],i=o.completion;if("root"===o.tryLoc)return t("end");if(o.tryLoc<=this.prev){var a=s.call(o,"catchLoc"),c=s.call(o,"finallyLoc");if(a&&c){if(this.preve.max&&(t.zoom=e.max)}},{key:"restrictTranslate",value:function(t){var e="boolean"==typeof this.translateExtent?{width:5e3,height:4e3}:this.translateExtent,n=this.editor.view.container,r=t.transform.k,o=e.width*r,i=e.height*r,a=n.clientWidth/2,c=n.clientHeight/2;t.x-=a,t.y-=c,t.x>o?t.x=o:t.x<-o&&(t.x=-o),t.y>i?t.y=i:t.y<-i&&(t.y=-i),t.x+=a,t.y+=c}}]),r}(),s=function(){function c(t,e){var r=this,n=e.size,o=void 0===n?16:n,i=e.dynamic,a=void 0===i||i;u(this,c),this.editor=t,this.size=o,a?this.editor.on("nodetranslate",this.onTranslate.bind(this)):this.editor.on("rendernode",function(t){var e=t.node,n=t.el;n.addEventListener("mouseup",r.onDrag.bind(r,e)),n.addEventListener("touchend",r.onDrag.bind(r,e)),n.addEventListener("touchcancel",r.onDrag.bind(r,e))})}return t(c,[{key:"onTranslate",value:function(t){var e=t.x,n=t.y;t.x=this.snap(e),t.y=this.snap(n)}},{key:"onDrag",value:function(t){var e=w(t.position,2),n=e[0],r=e[1];t.position[0]=this.snap(n),t.position[1]=this.snap(r),console.log(this,n,r,t.position),this.editor.view.nodes.get(t).update(),this.editor.view.updateConnections({node:t})}},{key:"snap",value:function(t){return Math.round(t/this.size)*this.size}}]),c}(),x=function(t){return 0===t.length?0:Math.min.apply(Math,e(t))},b=function(t){return 0===t.length?0:Math.max.apply(Math,e(t))};return{install:function(t,e){var n=e.background||!1,r=e.snap||!1,o=e.scaleExtent||!1,i=e.translateExtent||!1;n&&(this._background=new a(t,n)),(o||i)&&(this._restrictor=new c(t,o,i)),r&&(this._snap=new s(t,r))},zoomAt:function(t){var e,n,r,o,i,a,c=1this.depth||(e[n]||(e[n]=[]),e[n].includes(t))))return e[n].push(t),this.getNodes(t,"output").map(function(t){return r.getNodesTable(t,e,n+1)}),this.getNodes(t,"input").map(function(t){return r.getNodesTable(t,e,n-1)}),e}},{key:"nodeSize",value:function(t){var r=this.editor.view.nodes.get(t).el;return{width:r.clientWidth,height:r.clientHeight}}},{key:"arrange",value:function(){for(var e=this,t=0e.left&&n.righte.top&&n.bottome.right||n.righte.bottom||n.bottom * {\n pointer-events: all; }\n .connection .main-path {\n fill: none;\n stroke-width: 5px;\n stroke: steelblue; }\n");var n={name:"connection",install:function(r){r.bind("connectionpath"),r.bind("connectiondrop"),r.bind("connectionpick"),r.bind("resetconnection");var n=new e(r),i=new v(n),c=new WeakMap;function a(t){var e=c.get(this);if(e){var n=e.input,o=e.output;r.view.container.dispatchEvent(new PointerEvent("pointermove",t)),t.preventDefault(),t.stopPropagation(),i.start({input:n,output:o},n||o)}}function t(t){var e=document.elementFromPoint(t.clientX,t.clientY);n.io&&r.trigger("connectiondrop",n.io),e&&i.complete(function t(e,n){return e.get(n)||(n.parentElement?t(e,n.parentElement):null)}(c,e)||{})}r.on("resetconnection",function(){return i.complete()}),r.on("rendersocket",function(t){var e=t.el,n=t.input,o=t.output;c.set(e,{input:n,output:o}),e.removeEventListener("pointerdown",a),e.addEventListener("pointerdown",a)}),window.addEventListener("pointerup",t),r.on("renderconnection",function(t){var e=t.el,n=t.connection,o=t.points;p({el:e,d:l(r,o,n),connection:n})}),r.on("updateconnection",function(t){var e=t.el,n=t.connection,o=t.points;f({el:e,d:l(r,o,n)})}),r.on("destroy",function(){window.removeEventListener("pointerup",t)})}};t.default=n,t.defaultPath=h,Object.defineProperty(t,"__esModule",{value:!0})});
7 | //# sourceMappingURL=connection-plugin.min.js.map
8 |
--------------------------------------------------------------------------------
/www/js/context-menu-plugin.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * rete-context-menu-plugin v0.5.2
3 | * (c) 2019 Vitaliy Stoliarov
4 | * Released under the MIT license.
5 | */
6 | !function(e){"use strict";var u,t=Object.prototype,c=t.hasOwnProperty,n="function"==typeof Symbol?Symbol:{},i=n.iterator||"@@iterator",r=n.asyncIterator||"@@asyncIterator",o=n.toStringTag||"@@toStringTag",a="object"==typeof module,s=e.regeneratorRuntime;if(s)a&&(module.exports=s);else{(s=e.regeneratorRuntime=a?module.exports:{}).wrap=y;var d="suspendedStart",f="suspendedYield",m="executing",h="completed",p={},l={};l[i]=function(){return this};var v=Object.getPrototypeOf,A=v&&v(v(I([])));A&&A!==t&&c.call(A,i)&&(l=A);var b=w.prototype=x.prototype=Object.create(l);C.prototype=b.constructor=w,w.constructor=C,w[o]=C.displayName="GeneratorFunction",s.isGeneratorFunction=function(e){var t="function"==typeof e&&e.constructor;return!!t&&(t===C||"GeneratorFunction"===(t.displayName||t.name))},s.mark=function(e){return Object.setPrototypeOf?Object.setPrototypeOf(e,w):(e.__proto__=w,o in e||(e[o]="GeneratorFunction")),e.prototype=Object.create(b),e},s.awrap=function(e){return{__await:e}},E(S.prototype),S.prototype[r]=function(){return this},s.AsyncIterator=S,s.async=function(e,t,n,r){var i=new S(y(e,t,n,r));return s.isGeneratorFunction(t)?i:i.next().then(function(e){return e.done?e.value:i.next()})},E(b),b[o]="Generator",b[i]=function(){return this},b.toString=function(){return"[object Generator]"},s.keys=function(n){var r=[];for(var e in n)r.push(e);return r.reverse(),function e(){for(;r.length;){var t=r.pop();if(t in n)return e.value=t,e.done=!1,e}return e.done=!0,e}},s.values=I,O.prototype={constructor:O,reset:function(e){if(this.prev=0,this.next=0,this.sent=this._sent=u,this.done=!1,this.delegate=null,this.method="next",this.arg=u,this.tryEntries.forEach(j),!e)for(var t in this)"t"===t.charAt(0)&&c.call(this,t)&&!isNaN(+t.slice(1))&&(this[t]=u)},stop:function(){this.done=!0;var e=this.tryEntries[0].completion;if("throw"===e.type)throw e.arg;return this.rval},dispatchException:function(n){if(this.done)throw n;var r=this;function e(e,t){return o.type="throw",o.arg=n,r.next=e,t&&(r.method="next",r.arg=u),!!t}for(var t=this.tryEntries.length-1;0<=t;--t){var i=this.tryEntries[t],o=i.completion;if("root"===i.tryLoc)return e("end");if(i.tryLoc<=this.prev){var a=c.call(i,"catchLoc"),s=c.call(i,"finallyLoc");if(a&&s){if(this.prev\n.socket(\n :class="[type, socket.name] | kebab",\n :title="socket.name"\n)\n\n\n