+1
package.json
+1
package.json
+15
pnpm-lock.yaml
+15
pnpm-lock.yaml
···
11
'@tauri-apps/api':
12
specifier: ^2
13
version: 2.7.0
14
'@tauri-apps/plugin-opener':
15
specifier: ^2
16
version: 2.4.0
···
396
'@tauri-apps/api@2.7.0':
397
resolution: {integrity: sha512-v7fVE8jqBl8xJFOcBafDzXFc8FnicoH3j8o8DNNs0tHuEBmXUDqrCOAzMRX0UkfpwqZLqvrvK0GNQ45DfnoVDg==}
398
399
'@tauri-apps/cli-darwin-arm64@2.7.1':
400
resolution: {integrity: sha512-j2NXQN6+08G03xYiyKDKqbCV2Txt+hUKg0a8hYr92AmoCU8fgCjHyva/p16lGFGUG3P2Yu0xiNe1hXL9ZuRMzA==}
401
engines: {node: '>= 10'}
···
466
resolution: {integrity: sha512-RcGWR4jOUEl92w3uvI0h61Llkfj9lwGD1iwvDRD2isMrDhOzjeeeVn9aGzeW1jubQ/kAbMYfydcA4BA0Cy733Q==}
467
engines: {node: '>= 10'}
468
hasBin: true
469
470
'@tauri-apps/plugin-opener@2.4.0':
471
resolution: {integrity: sha512-43VyN8JJtvKWJY72WI/KNZszTpDpzHULFxQs0CJBIYUdCRowQ6Q1feWTDb979N7nldqSuDOaBupZ6wz2nvuWwQ==}
···
991
992
'@tauri-apps/api@2.7.0': {}
993
994
'@tauri-apps/cli-darwin-arm64@2.7.1':
995
optional: true
996
···
1037
'@tauri-apps/cli-win32-arm64-msvc': 2.7.1
1038
'@tauri-apps/cli-win32-ia32-msvc': 2.7.1
1039
'@tauri-apps/cli-win32-x64-msvc': 2.7.1
1040
1041
'@tauri-apps/plugin-opener@2.4.0':
1042
dependencies:
···
11
'@tauri-apps/api':
12
specifier: ^2
13
version: 2.7.0
14
+
'@tauri-apps/plugin-dialog':
15
+
specifier: ~2
16
+
version: 2.4.0
17
'@tauri-apps/plugin-opener':
18
specifier: ^2
19
version: 2.4.0
···
399
'@tauri-apps/api@2.7.0':
400
resolution: {integrity: sha512-v7fVE8jqBl8xJFOcBafDzXFc8FnicoH3j8o8DNNs0tHuEBmXUDqrCOAzMRX0UkfpwqZLqvrvK0GNQ45DfnoVDg==}
401
402
+
'@tauri-apps/api@2.8.0':
403
+
resolution: {integrity: sha512-ga7zdhbS2GXOMTIZRT0mYjKJtR9fivsXzsyq5U3vjDL0s6DTMwYRm0UHNjzTY5dh4+LSC68Sm/7WEiimbQNYlw==}
404
+
405
'@tauri-apps/cli-darwin-arm64@2.7.1':
406
resolution: {integrity: sha512-j2NXQN6+08G03xYiyKDKqbCV2Txt+hUKg0a8hYr92AmoCU8fgCjHyva/p16lGFGUG3P2Yu0xiNe1hXL9ZuRMzA==}
407
engines: {node: '>= 10'}
···
472
resolution: {integrity: sha512-RcGWR4jOUEl92w3uvI0h61Llkfj9lwGD1iwvDRD2isMrDhOzjeeeVn9aGzeW1jubQ/kAbMYfydcA4BA0Cy733Q==}
473
engines: {node: '>= 10'}
474
hasBin: true
475
+
476
+
'@tauri-apps/plugin-dialog@2.4.0':
477
+
resolution: {integrity: sha512-OvXkrEBfWwtd8tzVCEXIvRfNEX87qs2jv6SqmVPiHcJjBhSF/GUvjqUNIDmKByb5N8nvDqVUM7+g1sXwdC/S9w==}
478
479
'@tauri-apps/plugin-opener@2.4.0':
480
resolution: {integrity: sha512-43VyN8JJtvKWJY72WI/KNZszTpDpzHULFxQs0CJBIYUdCRowQ6Q1feWTDb979N7nldqSuDOaBupZ6wz2nvuWwQ==}
···
1000
1001
'@tauri-apps/api@2.7.0': {}
1002
1003
+
'@tauri-apps/api@2.8.0': {}
1004
+
1005
'@tauri-apps/cli-darwin-arm64@2.7.1':
1006
optional: true
1007
···
1048
'@tauri-apps/cli-win32-arm64-msvc': 2.7.1
1049
'@tauri-apps/cli-win32-ia32-msvc': 2.7.1
1050
'@tauri-apps/cli-win32-x64-msvc': 2.7.1
1051
+
1052
+
'@tauri-apps/plugin-dialog@2.4.0':
1053
+
dependencies:
1054
+
'@tauri-apps/api': 2.8.0
1055
1056
'@tauri-apps/plugin-opener@2.4.0':
1057
dependencies:
+1
public/assets/icons/floppy-disk-solid-full.svg
+1
public/assets/icons/floppy-disk-solid-full.svg
···
···
1
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path fill="#fff" d="M160 96C124.7 96 96 124.7 96 160L96 480C96 515.3 124.7 544 160 544L480 544C515.3 544 544 515.3 544 480L544 237.3C544 220.3 537.3 204 525.3 192L448 114.7C436 102.7 419.7 96 402.7 96L160 96zM192 192C192 174.3 206.3 160 224 160L384 160C401.7 160 416 174.3 416 192L416 256C416 273.7 401.7 288 384 288L224 288C206.3 288 192 273.7 192 256L192 192zM320 352C355.3 352 384 380.7 384 416C384 451.3 355.3 480 320 480C284.7 480 256 451.3 256 416C256 380.7 284.7 352 320 352z"/></svg>
+213
-1
src-tauri/Cargo.lock
+213
-1
src-tauri/Cargo.lock
···
13
"serde_json",
14
"tauri",
15
"tauri-build",
16
"tauri-plugin-opener",
17
"tokio",
18
]
···
76
version = "1.0.99"
77
source = "registry+https://github.com/rust-lang/crates.io-index"
78
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
79
80
[[package]]
81
name = "async-broadcast"
···
758
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
759
dependencies = [
760
"bitflags 2.9.1",
761
"objc2 0.6.1",
762
]
763
···
773
]
774
775
[[package]]
776
name = "dlopen2"
777
version = "0.7.0"
778
source = "registry+https://github.com/rust-lang/crates.io-index"
···
794
"quote",
795
"syn 2.0.104",
796
]
797
798
[[package]]
799
name = "dpi"
···
2634
dependencies = [
2635
"base64 0.22.1",
2636
"indexmap 2.10.0",
2637
-
"quick-xml",
2638
"serde",
2639
"time",
2640
]
···
2765
2766
[[package]]
2767
name = "quick-xml"
2768
version = "0.38.0"
2769
source = "registry+https://github.com/rust-lang/crates.io-index"
2770
checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b"
···
2813
]
2814
2815
[[package]]
2816
name = "rand_chacha"
2817
version = "0.2.2"
2818
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2833
]
2834
2835
[[package]]
2836
name = "rand_core"
2837
version = "0.5.1"
2838
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2851
]
2852
2853
[[package]]
2854
name = "rand_hc"
2855
version = "0.2.0"
2856
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2979
]
2980
2981
[[package]]
2982
name = "rustc-demangle"
2983
version = "0.1.26"
2984
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3079
]
3080
3081
[[package]]
3082
name = "scopeguard"
3083
version = "1.2.0"
3084
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3680
]
3681
3682
[[package]]
3683
name = "tauri-plugin-opener"
3684
version = "2.4.0"
3685
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3916
"libc",
3917
"mio",
3918
"pin-project-lite",
3919
"slab",
3920
"socket2",
3921
"tokio-macros",
3922
"windows-sys 0.59.0",
3923
]
3924
···
4433
]
4434
4435
[[package]]
4436
name = "web-sys"
4437
version = "0.3.77"
4438
source = "registry+https://github.com/rust-lang/crates.io-index"
···
5057
"ordered-stream",
5058
"serde",
5059
"serde_repr",
5060
"tracing",
5061
"uds_windows",
5062
"windows-sys 0.59.0",
···
5176
"endi",
5177
"enumflags2",
5178
"serde",
5179
"winnow 0.7.12",
5180
"zvariant_derive",
5181
"zvariant_utils",
···
13
"serde_json",
14
"tauri",
15
"tauri-build",
16
+
"tauri-plugin-dialog",
17
"tauri-plugin-opener",
18
"tokio",
19
]
···
77
version = "1.0.99"
78
source = "registry+https://github.com/rust-lang/crates.io-index"
79
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
80
+
81
+
[[package]]
82
+
name = "ashpd"
83
+
version = "0.11.0"
84
+
source = "registry+https://github.com/rust-lang/crates.io-index"
85
+
checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df"
86
+
dependencies = [
87
+
"enumflags2",
88
+
"futures-channel",
89
+
"futures-util",
90
+
"rand 0.9.2",
91
+
"raw-window-handle",
92
+
"serde",
93
+
"serde_repr",
94
+
"tokio",
95
+
"url",
96
+
"wayland-backend",
97
+
"wayland-client",
98
+
"wayland-protocols",
99
+
"zbus",
100
+
]
101
102
[[package]]
103
name = "async-broadcast"
···
780
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
781
dependencies = [
782
"bitflags 2.9.1",
783
+
"block2 0.6.1",
784
+
"libc",
785
"objc2 0.6.1",
786
]
787
···
797
]
798
799
[[package]]
800
+
name = "dlib"
801
+
version = "0.5.2"
802
+
source = "registry+https://github.com/rust-lang/crates.io-index"
803
+
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
804
+
dependencies = [
805
+
"libloading",
806
+
]
807
+
808
+
[[package]]
809
name = "dlopen2"
810
version = "0.7.0"
811
source = "registry+https://github.com/rust-lang/crates.io-index"
···
827
"quote",
828
"syn 2.0.104",
829
]
830
+
831
+
[[package]]
832
+
name = "downcast-rs"
833
+
version = "1.2.1"
834
+
source = "registry+https://github.com/rust-lang/crates.io-index"
835
+
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
836
837
[[package]]
838
name = "dpi"
···
2673
dependencies = [
2674
"base64 0.22.1",
2675
"indexmap 2.10.0",
2676
+
"quick-xml 0.38.0",
2677
"serde",
2678
"time",
2679
]
···
2804
2805
[[package]]
2806
name = "quick-xml"
2807
+
version = "0.37.5"
2808
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2809
+
checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb"
2810
+
dependencies = [
2811
+
"memchr",
2812
+
]
2813
+
2814
+
[[package]]
2815
+
name = "quick-xml"
2816
version = "0.38.0"
2817
source = "registry+https://github.com/rust-lang/crates.io-index"
2818
checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b"
···
2861
]
2862
2863
[[package]]
2864
+
name = "rand"
2865
+
version = "0.9.2"
2866
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2867
+
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
2868
+
dependencies = [
2869
+
"rand_chacha 0.9.0",
2870
+
"rand_core 0.9.3",
2871
+
]
2872
+
2873
+
[[package]]
2874
name = "rand_chacha"
2875
version = "0.2.2"
2876
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2891
]
2892
2893
[[package]]
2894
+
name = "rand_chacha"
2895
+
version = "0.9.0"
2896
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2897
+
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
2898
+
dependencies = [
2899
+
"ppv-lite86",
2900
+
"rand_core 0.9.3",
2901
+
]
2902
+
2903
+
[[package]]
2904
name = "rand_core"
2905
version = "0.5.1"
2906
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2919
]
2920
2921
[[package]]
2922
+
name = "rand_core"
2923
+
version = "0.9.3"
2924
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2925
+
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
2926
+
dependencies = [
2927
+
"getrandom 0.3.3",
2928
+
]
2929
+
2930
+
[[package]]
2931
name = "rand_hc"
2932
version = "0.2.0"
2933
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3056
]
3057
3058
[[package]]
3059
+
name = "rfd"
3060
+
version = "0.15.4"
3061
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3062
+
checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed"
3063
+
dependencies = [
3064
+
"ashpd",
3065
+
"block2 0.6.1",
3066
+
"dispatch2",
3067
+
"glib-sys",
3068
+
"gobject-sys",
3069
+
"gtk-sys",
3070
+
"js-sys",
3071
+
"log",
3072
+
"objc2 0.6.1",
3073
+
"objc2-app-kit",
3074
+
"objc2-core-foundation",
3075
+
"objc2-foundation 0.3.1",
3076
+
"raw-window-handle",
3077
+
"wasm-bindgen",
3078
+
"wasm-bindgen-futures",
3079
+
"web-sys",
3080
+
"windows-sys 0.59.0",
3081
+
]
3082
+
3083
+
[[package]]
3084
name = "rustc-demangle"
3085
version = "0.1.26"
3086
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3181
]
3182
3183
[[package]]
3184
+
name = "scoped-tls"
3185
+
version = "1.0.1"
3186
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3187
+
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
3188
+
3189
+
[[package]]
3190
name = "scopeguard"
3191
version = "1.2.0"
3192
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3788
]
3789
3790
[[package]]
3791
+
name = "tauri-plugin-dialog"
3792
+
version = "2.3.2"
3793
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3794
+
checksum = "37e5858cc7b455a73ab4ea2ebc08b5be33682c00ff1bf4cad5537d4fb62499d9"
3795
+
dependencies = [
3796
+
"log",
3797
+
"raw-window-handle",
3798
+
"rfd",
3799
+
"serde",
3800
+
"serde_json",
3801
+
"tauri",
3802
+
"tauri-plugin",
3803
+
"tauri-plugin-fs",
3804
+
"thiserror 2.0.12",
3805
+
"url",
3806
+
]
3807
+
3808
+
[[package]]
3809
+
name = "tauri-plugin-fs"
3810
+
version = "2.4.1"
3811
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3812
+
checksum = "8c6ef84ee2f2094ce093e55106d90d763ba343fad57566992962e8f76d113f99"
3813
+
dependencies = [
3814
+
"anyhow",
3815
+
"dunce",
3816
+
"glob",
3817
+
"percent-encoding",
3818
+
"schemars 0.8.22",
3819
+
"serde",
3820
+
"serde_json",
3821
+
"serde_repr",
3822
+
"tauri",
3823
+
"tauri-plugin",
3824
+
"tauri-utils",
3825
+
"thiserror 2.0.12",
3826
+
"toml 0.8.23",
3827
+
"url",
3828
+
]
3829
+
3830
+
[[package]]
3831
name = "tauri-plugin-opener"
3832
version = "2.4.0"
3833
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4064
"libc",
4065
"mio",
4066
"pin-project-lite",
4067
+
"signal-hook-registry",
4068
"slab",
4069
"socket2",
4070
"tokio-macros",
4071
+
"tracing",
4072
"windows-sys 0.59.0",
4073
]
4074
···
4583
]
4584
4585
[[package]]
4586
+
name = "wayland-backend"
4587
+
version = "0.3.11"
4588
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4589
+
checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35"
4590
+
dependencies = [
4591
+
"cc",
4592
+
"downcast-rs",
4593
+
"rustix",
4594
+
"scoped-tls",
4595
+
"smallvec",
4596
+
"wayland-sys",
4597
+
]
4598
+
4599
+
[[package]]
4600
+
name = "wayland-client"
4601
+
version = "0.31.11"
4602
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4603
+
checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d"
4604
+
dependencies = [
4605
+
"bitflags 2.9.1",
4606
+
"rustix",
4607
+
"wayland-backend",
4608
+
"wayland-scanner",
4609
+
]
4610
+
4611
+
[[package]]
4612
+
name = "wayland-protocols"
4613
+
version = "0.32.9"
4614
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4615
+
checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901"
4616
+
dependencies = [
4617
+
"bitflags 2.9.1",
4618
+
"wayland-backend",
4619
+
"wayland-client",
4620
+
"wayland-scanner",
4621
+
]
4622
+
4623
+
[[package]]
4624
+
name = "wayland-scanner"
4625
+
version = "0.31.7"
4626
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4627
+
checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3"
4628
+
dependencies = [
4629
+
"proc-macro2",
4630
+
"quick-xml 0.37.5",
4631
+
"quote",
4632
+
]
4633
+
4634
+
[[package]]
4635
+
name = "wayland-sys"
4636
+
version = "0.31.7"
4637
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4638
+
checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142"
4639
+
dependencies = [
4640
+
"dlib",
4641
+
"log",
4642
+
"pkg-config",
4643
+
]
4644
+
4645
+
[[package]]
4646
name = "web-sys"
4647
version = "0.3.77"
4648
source = "registry+https://github.com/rust-lang/crates.io-index"
···
5267
"ordered-stream",
5268
"serde",
5269
"serde_repr",
5270
+
"tokio",
5271
"tracing",
5272
"uds_windows",
5273
"windows-sys 0.59.0",
···
5387
"endi",
5388
"enumflags2",
5389
"serde",
5390
+
"url",
5391
"winnow 0.7.12",
5392
"zvariant_derive",
5393
"zvariant_utils",
+1
src-tauri/Cargo.toml
+1
src-tauri/Cargo.toml
+6
-3
src-tauri/capabilities/default.json
+6
-3
src-tauri/capabilities/default.json
+4
-4
src-tauri/src/frontend_calls/get_addresses.rs
+4
-4
src-tauri/src/frontend_calls/get_addresses.rs
+1
-1
src-tauri/src/frontend_calls/mod.rs
+1
-1
src-tauri/src/frontend_calls/mod.rs
+13
-10
src-tauri/src/frontend_calls/save_graph.rs
+13
-10
src-tauri/src/frontend_calls/save_graph.rs
···
1
-
use std::{ fs::File, io::Write };
2
3
-
use flate2::{ write::GzEncoder, Compression };
4
5
#[tauri::command]
6
-
pub fn save_graph( tab_name: String, graph: String ) {
7
-
dbg!(&graph);
8
9
-
let path = dirs::config_dir().unwrap().join("VRCMacros").join(format!("{}.macro", tab_name));
10
11
-
let file = File::create(&path).unwrap();
12
-
let mut encoder = GzEncoder::new(file, Compression::default());
13
14
-
encoder.write_all(graph.as_bytes()).unwrap();
15
-
encoder.finish().unwrap();
16
-
}
···
1
+
use std::{fs::File, io::Write};
2
3
+
use flate2::{write::GzEncoder, Compression};
4
5
#[tauri::command]
6
+
pub fn save_graph(tab_name: String, graph: String) {
7
+
dbg!(&graph);
8
9
+
let path = dirs::config_dir()
10
+
.unwrap()
11
+
.join("VRCMacros")
12
+
.join(format!("{}.macro", tab_name));
13
14
+
let file = File::create(&path).unwrap();
15
+
let mut encoder = GzEncoder::new(file, Compression::default());
16
17
+
encoder.write_all(graph.as_bytes()).unwrap();
18
+
encoder.finish().unwrap();
19
+
}
+33
-33
src-tauri/src/lib.rs
+33
-33
src-tauri/src/lib.rs
···
1
-
use std::{ fs, sync::Mutex };
2
3
use frontend_calls::*;
4
5
-
use crate::{ osc::OSCMessage, setup::setup, utils::config::Config };
6
7
mod frontend_calls;
8
-
mod structs;
9
mod setup;
10
mod utils;
11
-
mod osc;
12
13
#[cfg_attr(mobile, tauri::mobile_entry_point)]
14
#[tokio::main]
15
pub async fn run() {
16
-
let container_folder = dirs::config_dir().unwrap().join("VRCMacros");
17
18
-
match fs ::metadata(&container_folder){
19
-
Ok(meta) => {
20
-
if meta.is_file(){
21
-
panic!("document.write('Cannot launch app as the container path is a file not a directory')");
22
-
}
23
-
}
24
-
Err(_) => {
25
-
fs::create_dir(&container_folder).unwrap();
26
}
27
-
}
28
29
-
let conf_file = container_folder.join("conf");
30
-
let conf = Config::new(conf_file);
31
-
32
-
static ADDRESSES: Mutex<Vec<OSCMessage>> = Mutex::new(Vec::new());
33
34
-
tauri::Builder::default()
35
-
.plugin(tauri_plugin_opener::init())
36
-
.invoke_handler(tauri::generate_handler![
37
-
get_addresses::get_addresses,
38
39
-
save_graph::save_graph
40
-
])
41
-
.manage(conf)
42
-
.manage(&ADDRESSES)
43
-
.setup(| app | {
44
-
setup(app, &ADDRESSES);
45
-
Ok(())
46
-
})
47
-
.run(tauri::generate_context!())
48
-
.expect("error while running tauri application");
49
-
}
···
1
+
use std::{fs, sync::Mutex};
2
3
use frontend_calls::*;
4
5
+
use crate::{osc::OSCMessage, setup::setup, utils::config::Config};
6
7
mod frontend_calls;
8
+
mod osc;
9
mod setup;
10
+
mod structs;
11
mod utils;
12
13
#[cfg_attr(mobile, tauri::mobile_entry_point)]
14
#[tokio::main]
15
pub async fn run() {
16
+
let container_folder = dirs::config_dir().unwrap().join("VRCMacros");
17
18
+
match fs::metadata(&container_folder) {
19
+
Ok(meta) => {
20
+
if meta.is_file() {
21
+
panic!("document.write('Cannot launch app as the container path is a file not a directory')");
22
+
}
23
+
}
24
+
Err(_) => {
25
+
fs::create_dir(&container_folder).unwrap();
26
+
}
27
}
28
29
+
let conf_file = container_folder.join("conf");
30
+
let conf = Config::new(conf_file);
31
32
+
static ADDRESSES: Mutex<Vec<OSCMessage>> = Mutex::new(Vec::new());
33
34
+
tauri::Builder::default()
35
+
.plugin(tauri_plugin_dialog::init())
36
+
.plugin(tauri_plugin_opener::init())
37
+
.invoke_handler(tauri::generate_handler![
38
+
get_addresses::get_addresses,
39
+
save_graph::save_graph
40
+
])
41
+
.manage(conf)
42
+
.manage(&ADDRESSES)
43
+
.setup(|app| {
44
+
setup(app, &ADDRESSES);
45
+
Ok(())
46
+
})
47
+
.run(tauri::generate_context!())
48
+
.expect("error while running tauri application");
49
+
}
+3
-3
src-tauri/src/main.rs
+3
-3
src-tauri/src/main.rs
+117
-109
src-tauri/src/osc.rs
+117
-109
src-tauri/src/osc.rs
···
1
// https://gist.github.com/phaze-the-dumb/634daacb5141eae2f846e20987dba7a8
2
3
-
use std::{ net::UdpSocket, sync::mpsc::Sender };
4
5
use serde::Serialize;
6
7
use crate::structs::parameter_types::ParameterType;
8
9
#[derive(Debug, Clone, Serialize)]
10
-
pub struct OSCMessage{
11
-
pub address: String,
12
-
pub values: Vec<ParameterType>
13
}
14
15
-
impl PartialEq for OSCMessage{
16
-
// Technically this isn't exactly equal, but the only time i'm checking if OSCMessage's are equal
17
-
// Is when i'm checking for the "address" value
18
19
-
fn eq(&self, other: &Self) -> bool {
20
-
self.address == other.address
21
-
}
22
23
-
fn ne(&self, other: &Self) -> bool {
24
-
self.address != other.address
25
-
}
26
}
27
28
// TODO: Implement osc bundles
29
-
pub fn start_server( sender: Sender<OSCMessage>, addr: &str ) {
30
-
let socket = UdpSocket::bind(addr).unwrap();
31
32
-
loop {
33
-
let mut buf = [0; 1024];
34
-
let (amt, _src) = socket.recv_from(&mut buf).unwrap();
35
36
-
let buf = &mut buf[..amt];
37
-
if buf[0] != 0x2F { panic!("Packet is not an OSC Message"); }
38
39
-
let mut addr: Vec<u8> = Vec::new();
40
-
let mut value_start = 0;
41
42
-
loop{
43
-
let byte = buf[value_start];
44
-
if byte == 0x00{ break; }
45
46
-
value_start += 1;
47
-
addr.push(byte);
48
-
}
49
50
-
loop{
51
-
let byte = buf[value_start];
52
-
value_start += 1;
53
54
-
if byte == 0x2C{ break; }
55
-
}
56
57
-
let mut types: Vec<u8> = Vec::new();
58
59
-
loop{
60
-
let byte = buf[value_start];
61
-
if byte == 0x00{ break; }
62
63
-
types.push(byte);
64
-
value_start += 1;
65
-
}
66
67
-
value_start = ((value_start as f32 / 4.0).ceil() * 4.0) as usize;
68
-
let mut values = Vec::new();
69
70
-
for tp in types{
71
-
match tp{
72
-
0x69 => {
73
-
let val_buf = &buf[value_start..value_start + 4];
74
75
-
let bytes = <&[u8; 4]>::try_from(val_buf).unwrap().clone();
76
-
let int = i32::from_be_bytes(bytes);
77
78
-
values.push(ParameterType::Int(int));
79
-
value_start += 4;
80
-
},
81
-
0x66 => {
82
-
let val_buf = &buf[value_start..value_start + 4];
83
84
-
let bytes = <&[u8; 4]>::try_from(val_buf).unwrap().clone();
85
-
let float = f32::from_be_bytes(bytes);
86
87
-
values.push(ParameterType::Float(float));
88
-
value_start += 4;
89
-
},
90
-
0x54 => values.push(ParameterType::Boolean(true)),
91
-
0x46 => values.push(ParameterType::Boolean(false)),
92
-
_ => {}
93
-
}
94
-
}
95
96
-
let message = OSCMessage {
97
-
address: String::from_utf8(addr.clone()).unwrap(),
98
-
values: values
99
-
};
100
101
-
sender.send(message).unwrap();
102
-
}
103
}
104
105
-
pub fn send_message( address: &str, values: Vec<ParameterType>, ip_addr: &str ){
106
-
let socket = UdpSocket::bind("127.0.0.1:0").unwrap();
107
-
let mut buf: Vec<u8> = Vec::new();
108
109
-
buf.append(&mut address.as_bytes().to_vec());
110
111
-
let rounded_len = ((((buf.len() as f64) + 1.0) / 4.0).ceil() * 4.0) as usize;
112
-
let original_len = buf.len();
113
114
-
for _i in original_len..rounded_len {
115
-
buf.push(0);
116
-
}
117
118
-
buf.push(0x2C);
119
120
-
let mut value_count = 1;
121
-
for value in values.clone() {
122
-
match value {
123
-
ParameterType::Boolean( val ) => buf.push(if val { 0x54 } else { 0x46 }),
124
-
ParameterType::Float(_) => buf.push(0x66),
125
-
ParameterType::Int(_) => buf.push(0x69),
126
-
ParameterType::String(_) => buf.push(0x73),
127
-
_ => {}
128
-
};
129
130
-
value_count += 1;
131
-
}
132
133
-
for _i in 0..4 - (value_count % 4) {
134
-
buf.push(0);
135
-
}
136
137
-
for value in values{
138
-
match value{
139
-
ParameterType::Float( val ) => buf.append(&mut val.to_be_bytes().to_vec()),
140
-
ParameterType::Int( val ) => buf.append(&mut val.to_be_bytes().to_vec()),
141
-
ParameterType::String( val ) => {
142
-
let mut str_buf = val.as_bytes().to_vec();
143
-
let buf_len = str_buf.len().clone();
144
145
-
buf.append(&mut str_buf);
146
147
-
for _i in 0..4 - (buf_len % 4) {
148
-
buf.push(0);
149
}
150
-
}
151
-
_ => {}
152
}
153
-
}
154
155
-
println!("{:X?}", &buf);
156
-
socket.send_to(&buf, ip_addr).unwrap();
157
-
}
···
1
// https://gist.github.com/phaze-the-dumb/634daacb5141eae2f846e20987dba7a8
2
3
+
use std::{net::UdpSocket, sync::mpsc::Sender};
4
5
use serde::Serialize;
6
7
use crate::structs::parameter_types::ParameterType;
8
9
#[derive(Debug, Clone, Serialize)]
10
+
pub struct OSCMessage {
11
+
pub address: String,
12
+
pub values: Vec<ParameterType>,
13
}
14
15
+
impl PartialEq for OSCMessage {
16
+
// Technically this isn't exactly equal, but the only time i'm checking if OSCMessage's are equal
17
+
// Is when i'm checking for the "address" value
18
19
+
fn eq(&self, other: &Self) -> bool {
20
+
self.address == other.address
21
+
}
22
23
+
fn ne(&self, other: &Self) -> bool {
24
+
self.address != other.address
25
+
}
26
}
27
28
// TODO: Implement osc bundles
29
+
pub fn start_server(sender: Sender<OSCMessage>, addr: &str) {
30
+
let socket = UdpSocket::bind(addr).unwrap();
31
32
+
loop {
33
+
let mut buf = [0; 1024];
34
+
let (amt, _src) = socket.recv_from(&mut buf).unwrap();
35
36
+
let buf = &mut buf[..amt];
37
+
if buf[0] != 0x2F {
38
+
panic!("Packet is not an OSC Message");
39
+
}
40
41
+
let mut addr: Vec<u8> = Vec::new();
42
+
let mut value_start = 0;
43
44
+
loop {
45
+
let byte = buf[value_start];
46
+
if byte == 0x00 {
47
+
break;
48
+
}
49
50
+
value_start += 1;
51
+
addr.push(byte);
52
+
}
53
54
+
loop {
55
+
let byte = buf[value_start];
56
+
value_start += 1;
57
58
+
if byte == 0x2C {
59
+
break;
60
+
}
61
+
}
62
63
+
let mut types: Vec<u8> = Vec::new();
64
65
+
loop {
66
+
let byte = buf[value_start];
67
+
if byte == 0x00 {
68
+
break;
69
+
}
70
71
+
types.push(byte);
72
+
value_start += 1;
73
+
}
74
75
+
value_start = ((value_start as f32 / 4.0).ceil() * 4.0) as usize;
76
+
let mut values = Vec::new();
77
78
+
for tp in types {
79
+
match tp {
80
+
0x69 => {
81
+
let val_buf = &buf[value_start..value_start + 4];
82
83
+
let bytes = <&[u8; 4]>::try_from(val_buf).unwrap().clone();
84
+
let int = i32::from_be_bytes(bytes);
85
86
+
values.push(ParameterType::Int(int));
87
+
value_start += 4;
88
+
}
89
+
0x66 => {
90
+
let val_buf = &buf[value_start..value_start + 4];
91
92
+
let bytes = <&[u8; 4]>::try_from(val_buf).unwrap().clone();
93
+
let float = f32::from_be_bytes(bytes);
94
95
+
values.push(ParameterType::Float(float));
96
+
value_start += 4;
97
+
}
98
+
0x54 => values.push(ParameterType::Boolean(true)),
99
+
0x46 => values.push(ParameterType::Boolean(false)),
100
+
_ => {}
101
+
}
102
+
}
103
104
+
let message = OSCMessage {
105
+
address: String::from_utf8(addr.clone()).unwrap(),
106
+
values: values,
107
+
};
108
109
+
sender.send(message).unwrap();
110
+
}
111
}
112
113
+
pub fn send_message(address: &str, values: Vec<ParameterType>, ip_addr: &str) {
114
+
let socket = UdpSocket::bind("127.0.0.1:0").unwrap();
115
+
let mut buf: Vec<u8> = Vec::new();
116
117
+
buf.append(&mut address.as_bytes().to_vec());
118
119
+
let rounded_len = ((((buf.len() as f64) + 1.0) / 4.0).ceil() * 4.0) as usize;
120
+
let original_len = buf.len();
121
122
+
for _i in original_len..rounded_len {
123
+
buf.push(0);
124
+
}
125
126
+
buf.push(0x2C);
127
128
+
let mut value_count = 1;
129
+
for value in values.clone() {
130
+
match value {
131
+
ParameterType::Boolean(val) => buf.push(if val { 0x54 } else { 0x46 }),
132
+
ParameterType::Float(_) => buf.push(0x66),
133
+
ParameterType::Int(_) => buf.push(0x69),
134
+
ParameterType::String(_) => buf.push(0x73),
135
+
_ => {}
136
+
};
137
138
+
value_count += 1;
139
+
}
140
141
+
for _i in 0..4 - (value_count % 4) {
142
+
buf.push(0);
143
+
}
144
145
+
for value in values {
146
+
match value {
147
+
ParameterType::Float(val) => buf.append(&mut val.to_be_bytes().to_vec()),
148
+
ParameterType::Int(val) => buf.append(&mut val.to_be_bytes().to_vec()),
149
+
ParameterType::String(val) => {
150
+
let mut str_buf = val.as_bytes().to_vec();
151
+
let buf_len = str_buf.len().clone();
152
153
+
buf.append(&mut str_buf);
154
155
+
for _i in 0..4 - (buf_len % 4) {
156
+
buf.push(0);
157
+
}
158
+
}
159
+
_ => {}
160
}
161
}
162
163
+
println!("{:X?}", &buf);
164
+
socket.send_to(&buf, ip_addr).unwrap();
165
+
}
+35
-29
src-tauri/src/setup.rs
+35
-29
src-tauri/src/setup.rs
···
1
-
use std::{ fs::File, io::Read, sync::{ self, Mutex } };
2
3
use flate2::read::GzDecoder;
4
use serde_json::Value;
5
-
use tauri::{ App, Emitter, Listener, Manager };
6
7
-
use crate::osc::{ self, OSCMessage };
8
9
-
pub fn setup( app: &mut App, addresses: &'static Mutex<Vec<OSCMessage>> ){
10
-
let window = app.get_webview_window("main").unwrap();
11
12
-
let handle = window.clone();
13
-
window.listen("tauri://drag-drop", move | ev | {
14
-
let path: Value = serde_json::from_str(ev.payload()).unwrap();
15
-
let path = path["paths"][0].as_str().unwrap();
16
17
-
let file = File::open(path).unwrap();
18
-
let mut decoder = GzDecoder::new(file);
19
-
let mut string = String::new();
20
21
-
decoder.read_to_string(&mut string).unwrap();
22
23
-
handle.emit("load_new_tab", Value::String(string)).unwrap();
24
-
});
25
26
-
let ( sender, receiver ) = sync::mpsc::channel();
27
28
-
tokio::spawn(async move {
29
-
osc::start_server(sender, "127.0.0.1:9001");
30
-
});
31
32
-
tokio::spawn(async move {
33
-
loop {
34
-
let message = receiver.recv().unwrap();
35
36
-
window.emit("osc-message", &message).unwrap();
37
38
-
let msg = message.clone();
39
-
let mut addresses = addresses.lock().unwrap();
40
-
if !addresses.contains(&msg){ addresses.push(msg); }
41
-
}
42
-
});
43
-
}
···
1
+
use std::{
2
+
fs::File,
3
+
io::Read,
4
+
sync::{self, Mutex},
5
+
};
6
7
use flate2::read::GzDecoder;
8
use serde_json::Value;
9
+
use tauri::{App, Emitter, Listener, Manager};
10
11
+
use crate::osc::{self, OSCMessage};
12
13
+
pub fn setup(app: &mut App, addresses: &'static Mutex<Vec<OSCMessage>>) {
14
+
let window = app.get_webview_window("main").unwrap();
15
16
+
let handle = window.clone();
17
+
window.listen("tauri://drag-drop", move |ev| {
18
+
let path: Value = serde_json::from_str(ev.payload()).unwrap();
19
+
let path = path["paths"][0].as_str().unwrap();
20
21
+
let file = File::open(path).unwrap();
22
+
let mut decoder = GzDecoder::new(file);
23
+
let mut string = String::new();
24
25
+
decoder.read_to_string(&mut string).unwrap();
26
27
+
handle.emit("load_new_tab", Value::String(string)).unwrap();
28
+
});
29
30
+
let (sender, receiver) = sync::mpsc::channel();
31
32
+
tokio::spawn(async move {
33
+
osc::start_server(sender, "127.0.0.1:9001");
34
+
});
35
36
+
tokio::spawn(async move {
37
+
loop {
38
+
let message = receiver.recv().unwrap();
39
40
+
window.emit("osc-message", &message).unwrap();
41
42
+
let msg = message.clone();
43
+
let mut addresses = addresses.lock().unwrap();
44
+
if !addresses.contains(&msg) {
45
+
addresses.push(msg);
46
+
}
47
+
}
48
+
});
49
+
}
+1
-1
src-tauri/src/structs/mod.rs
+1
-1
src-tauri/src/structs/mod.rs
+8
-8
src-tauri/src/structs/parameter_types.rs
+8
-8
src-tauri/src/structs/parameter_types.rs
+69
-63
src-tauri/src/utils/config.rs
+69
-63
src-tauri/src/utils/config.rs
···
1
-
use std::{ collections::HashMap, fs::File, io::{ Read, Write }, path::PathBuf, sync::Mutex };
2
-
3
-
use flate2::{ read::GzDecoder, write::GzEncoder, Compression };
4
-
use serde_json::Value;
5
-
6
-
pub struct Config{
7
-
store: Mutex<HashMap<String, Value>>,
8
-
path: PathBuf
9
-
}
10
-
11
-
impl Config{
12
-
pub fn new( path: PathBuf ) -> Self{
13
-
let json_string = if path.exists(){
14
-
let mut decoder = GzDecoder::new(File::open(&path).unwrap());
15
-
let mut string = String::new();
16
-
17
-
decoder.read_to_string(&mut string).unwrap();
18
-
string
19
-
} else{
20
-
"{}".into()
21
-
};
22
-
23
-
let json: Value = serde_json::from_str(&json_string).unwrap();
24
-
25
-
let mut hashmap = HashMap::new();
26
-
27
-
let obj = json.as_object().unwrap();
28
-
for ( key, val ) in obj{
29
-
hashmap.insert(key.clone(), val.clone());
30
-
}
31
-
32
-
Config {
33
-
store: Mutex::new(hashmap),
34
-
path: path
35
-
}
36
-
}
37
-
38
-
pub fn set( &self, key: &str, value: Value ){
39
-
self.store.lock().unwrap().insert(key.to_owned(), value);
40
-
}
41
-
42
-
pub fn get( &self, key: &str ) -> Option<Value>{
43
-
let store = self.store.lock().unwrap();
44
-
let val = store.get(&key.to_owned());
45
-
46
-
if val.is_none(){
47
-
None
48
-
} else{
49
-
Some(val.unwrap().clone())
50
-
}
51
-
}
52
-
53
-
pub fn save( &self ){
54
-
let dat = serde_json::to_string(&self.store.lock().unwrap().clone()).unwrap();
55
-
dbg!(&dat);
56
-
57
-
let file = File::create(&self.path).unwrap();
58
-
let mut encoder = GzEncoder::new(file, Compression::default());
59
-
60
-
encoder.write_all(dat.as_bytes()).unwrap();
61
-
encoder.finish().unwrap();
62
-
}
63
-
}
···
1
+
use std::{
2
+
collections::HashMap,
3
+
fs::File,
4
+
io::{Read, Write},
5
+
path::PathBuf,
6
+
sync::Mutex,
7
+
};
8
+
9
+
use flate2::{read::GzDecoder, write::GzEncoder, Compression};
10
+
use serde_json::Value;
11
+
12
+
pub struct Config {
13
+
store: Mutex<HashMap<String, Value>>,
14
+
path: PathBuf,
15
+
}
16
+
17
+
impl Config {
18
+
pub fn new(path: PathBuf) -> Self {
19
+
let json_string = if path.exists() {
20
+
let mut decoder = GzDecoder::new(File::open(&path).unwrap());
21
+
let mut string = String::new();
22
+
23
+
decoder.read_to_string(&mut string).unwrap();
24
+
string
25
+
} else {
26
+
"{}".into()
27
+
};
28
+
29
+
let json: Value = serde_json::from_str(&json_string).unwrap();
30
+
31
+
let mut hashmap = HashMap::new();
32
+
33
+
let obj = json.as_object().unwrap();
34
+
for (key, val) in obj {
35
+
hashmap.insert(key.clone(), val.clone());
36
+
}
37
+
38
+
Config {
39
+
store: Mutex::new(hashmap),
40
+
path: path,
41
+
}
42
+
}
43
+
44
+
pub fn set(&self, key: &str, value: Value) {
45
+
self.store.lock().unwrap().insert(key.to_owned(), value);
46
+
}
47
+
48
+
pub fn get(&self, key: &str) -> Option<Value> {
49
+
let store = self.store.lock().unwrap();
50
+
let val = store.get(&key.to_owned());
51
+
52
+
if val.is_none() {
53
+
None
54
+
} else {
55
+
Some(val.unwrap().clone())
56
+
}
57
+
}
58
+
59
+
pub fn save(&self) {
60
+
let dat = serde_json::to_string(&self.store.lock().unwrap().clone()).unwrap();
61
+
dbg!(&dat);
62
+
63
+
let file = File::create(&self.path).unwrap();
64
+
let mut encoder = GzEncoder::new(file, Compression::default());
65
+
66
+
encoder.write_all(dat.as_bytes()).unwrap();
67
+
encoder.finish().unwrap();
68
+
}
69
+
}
+22
-10
src/Mangers/NodeManager.tsx
+22
-10
src/Mangers/NodeManager.tsx
···
5
import { listen } from "@tauri-apps/api/event";
6
import { getVersion } from "@tauri-apps/api/app";
7
import { NodesByID } from "../Nodes/Nodes";
8
9
export interface TabHashMap {
10
[details: string] : Tab;
···
17
private _tabs: TabHashMap = {};
18
19
private _nodes: Node[] = [];
20
-
private _needsSave = false;
21
22
constructor(){
23
NodeManager.Instance = this;
24
25
-
setInterval(() => {
26
-
// Save config every 1 second
27
-
if(this._needsSave)this._saveConfigToDisk();
28
-
}, 1_000);
29
-
30
listen('load_new_tab', ( ev: any ) => {
31
this._loadFromConfig(ev.payload);
32
})
···
38
39
public async AddTab( name: string ){
40
let [ selected, setSelected ] = createSignal(false);
41
42
let tab: Tab = {
43
name: name,
44
id: await NodeManager.Instance.GetNewNodeId(),
45
nodes: [],
46
-
selected: selected,
47
-
setSelected: setSelected
48
};
49
50
this._tabs[tab.id] = tab;
···
110
}
111
}
112
113
public HookTabUpdate( cb: ( tabs: TabHashMap ) => void ){
114
this._tabUpdateHook = cb;
115
}
···
150
151
152
public UpdateConfig(){
153
-
this._needsSave = true;
154
}
155
156
private async _loadFromConfig( config: string ){
···
215
}
216
217
private async _saveConfigToDisk(){
218
-
this._needsSave = false;
219
// Convert it into a structure we can actually save...
220
221
if(!this._selectedTab)return;
···
5
import { listen } from "@tauri-apps/api/event";
6
import { getVersion } from "@tauri-apps/api/app";
7
import { NodesByID } from "../Nodes/Nodes";
8
+
import { save } from "@tauri-apps/plugin-dialog";
9
10
export interface TabHashMap {
11
[details: string] : Tab;
···
18
private _tabs: TabHashMap = {};
19
20
private _nodes: Node[] = [];
21
22
constructor(){
23
NodeManager.Instance = this;
24
25
listen('load_new_tab', ( ev: any ) => {
26
this._loadFromConfig(ev.payload);
27
})
···
33
34
public async AddTab( name: string ){
35
let [ selected, setSelected ] = createSignal(false);
36
+
let [ needsSave, setNeedsSave ] = createSignal(false);
37
38
let tab: Tab = {
39
name: name,
40
id: await NodeManager.Instance.GetNewNodeId(),
41
nodes: [],
42
+
43
+
selected,
44
+
setSelected,
45
+
46
+
needsSave,
47
+
setNeedsSave
48
};
49
50
this._tabs[tab.id] = tab;
···
110
}
111
}
112
113
+
public async SaveTab( tab: Tab ){
114
+
let path = await save({ defaultPath: tab.name + '.macro', filters: [ { name: 'Macro Files', extensions: [ 'macro' ] } ] });
115
+
116
+
console.log(path);
117
+
118
+
// TODO: Add location metadata to tab interface so it knows where to save
119
+
// TODO: store file
120
+
}
121
+
122
public HookTabUpdate( cb: ( tabs: TabHashMap ) => void ){
123
this._tabUpdateHook = cb;
124
}
···
159
160
161
public UpdateConfig(){
162
+
if(!this._selectedTab)return;
163
+
let tab = this._tabs[this._selectedTab];
164
+
if(!tab)return;
165
+
166
+
tab.setNeedsSave(true);
167
}
168
169
private async _loadFromConfig( config: string ){
···
228
}
229
230
private async _saveConfigToDisk(){
231
// Convert it into a structure we can actually save...
232
233
if(!this._selectedTab)return;
-2
src/components/TabMenu.css
-2
src/components/TabMenu.css
+21
-10
src/components/TabMenu.tsx
+21
-10
src/components/TabMenu.tsx
···
1
-
import { createSignal, For, onMount } from 'solid-js';
2
import './TabMenu.css';
3
import { NodeManager } from '../Mangers/NodeManager';
4
import { Tab } from '../structs/Tab';
···
21
<For each={Object.values(tabs())}>
22
{
23
tab =>
24
-
<div class={ tab.selected() ? 'tab-selected ' : 'tab' }>
25
-
<div class="tab-icon" onClick={() => {
26
-
NodeManager.Instance.SelectTab(tab.id);
27
-
}}><img src="/assets/icons/pen-to-square-regular-full.svg" width="15" /></div>
28
-
<div class="tab-meta" onClick={() => {
29
-
NodeManager.Instance.SelectTab(tab.id);
30
-
}} onDblClick={( e ) => {
31
let input = <input class="tab-meta-input" value={ e.target.innerHTML } /> as HTMLInputElement;
32
33
e.target.innerHTML = '';
···
40
}
41
}}>{ tab.name }</div>
42
<div class="tab-close" onClick={() => {
43
-
NodeManager.Instance.CloseTab(tab.id);
44
}}><img src="/assets/icons/xmark-solid-full.svg" width="12" /></div>
45
</div>
46
}
···
54
55
window.addEventListener('click', closeTabImportMenu);
56
}}>
57
-
<div class="tab-new-dropdown" style={{ opacity: tabImportOpen() ? 1 : 0 }}>
58
<div class="tab">Import from file</div>
59
<div class="tab">Import from URL</div>
60
</div>
···
1
+
import { createSignal, For, onMount, Show } from 'solid-js';
2
import './TabMenu.css';
3
import { NodeManager } from '../Mangers/NodeManager';
4
import { Tab } from '../structs/Tab';
···
21
<For each={Object.values(tabs())}>
22
{
23
tab =>
24
+
<div class={ tab.selected() ? 'tab-selected ' : 'tab' } onClick={() => {
25
+
NodeManager.Instance.SelectTab(tab.id);
26
+
}}>
27
+
<div class="tab-icon" onClick={async () => {
28
+
if(tab.selected()){
29
+
NodeManager.Instance.SaveTab(tab);
30
+
}
31
+
}}>
32
+
<Show when={tab.selected() && tab.needsSave()} fallback={
33
+
<img src="/assets/icons/pen-to-square-regular-full.svg" width="15" />
34
+
}>
35
+
<img src="/assets/icons/floppy-disk-solid-full.svg" width="15" />
36
+
</Show>
37
+
38
+
</div>
39
+
<div class="tab-meta" onDblClick={( e ) => {
40
let input = <input class="tab-meta-input" value={ e.target.innerHTML } /> as HTMLInputElement;
41
42
e.target.innerHTML = '';
···
49
}
50
}}>{ tab.name }</div>
51
<div class="tab-close" onClick={() => {
52
+
setTimeout(() => {
53
+
NodeManager.Instance.CloseTab(tab.id);
54
+
}, 50)
55
}}><img src="/assets/icons/xmark-solid-full.svg" width="12" /></div>
56
</div>
57
}
···
65
66
window.addEventListener('click', closeTabImportMenu);
67
}}>
68
+
<div class="tab-new-dropdown" style={{ display: tabImportOpen() ? 'block' : 'none' }}>
69
<div class="tab">Import from file</div>
70
<div class="tab">Import from URL</div>
71
</div>
+4
src/structs/Tab.ts
+4
src/structs/Tab.ts