this repo has no description

buh

phaz.uk b7deb18e 8303945b

verified
+1
package.json
··· 13 "license": "MIT", 14 "dependencies": { 15 "@tauri-apps/api": "^2", 16 "@tauri-apps/plugin-opener": "^2", 17 "animejs": "^4.1.2", 18 "solid-js": "^1.9.3"
··· 13 "license": "MIT", 14 "dependencies": { 15 "@tauri-apps/api": "^2", 16 + "@tauri-apps/plugin-dialog": "~2", 17 "@tauri-apps/plugin-opener": "^2", 18 "animejs": "^4.1.2", 19 "solid-js": "^1.9.3"
+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 + <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
··· 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
··· 26 dirs = "6.0.0" 27 anyhow = "1.0.99" 28 flate2 = "1.1.2" 29
··· 26 dirs = "6.0.0" 27 anyhow = "1.0.99" 28 flate2 = "1.1.2" 29 + tauri-plugin-dialog = "2" 30
+6 -3
src-tauri/capabilities/default.json
··· 2 "$schema": "../gen/schemas/desktop-schema.json", 3 "identifier": "default", 4 "description": "Capability for the main window", 5 - "windows": ["main"], 6 "permissions": [ 7 "core:default", 8 - "opener:default" 9 ] 10 - }
··· 2 "$schema": "../gen/schemas/desktop-schema.json", 3 "identifier": "default", 4 "description": "Capability for the main window", 5 + "windows": [ 6 + "main" 7 + ], 8 "permissions": [ 9 "core:default", 10 + "opener:default", 11 + "dialog:default" 12 ] 13 + }
+4 -4
src-tauri/src/frontend_calls/get_addresses.rs
··· 5 use crate::osc::OSCMessage; 6 7 #[tauri::command] 8 - pub fn get_addresses( addresses: State<&Mutex<Vec<OSCMessage>>> ) -> Vec<OSCMessage> { 9 - let addresses = addresses.lock().unwrap(); 10 - addresses.clone() 11 - }
··· 5 use crate::osc::OSCMessage; 6 7 #[tauri::command] 8 + pub fn get_addresses(addresses: State<&Mutex<Vec<OSCMessage>>>) -> Vec<OSCMessage> { 9 + let addresses = addresses.lock().unwrap(); 10 + addresses.clone() 11 + }
+1 -1
src-tauri/src/frontend_calls/mod.rs
··· 1 pub mod get_addresses; 2 - pub mod save_graph;
··· 1 pub mod get_addresses; 2 + pub mod save_graph;
+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
··· 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
··· 2 #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 3 4 fn main() { 5 - #[cfg(target_os = "linux")] 6 - std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1"); // Fix webkit being shit 7 8 - vrcmacros_lib::run() 9 }
··· 2 #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 3 4 fn main() { 5 + #[cfg(target_os = "linux")] 6 + std::env::set_var("WEBKIT_DISABLE_DMABUF_RENDERER", "1"); // Fix webkit being shit 7 8 + vrcmacros_lib::run() 9 }
+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
··· 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 - pub mod parameter_types;
··· 1 + pub mod parameter_types;
+8 -8
src-tauri/src/structs/parameter_types.rs
··· 2 3 #[derive(Serialize, Clone, Debug)] 4 #[serde(tag = "key", content = "value")] 5 - pub enum ParameterType{ 6 - AnyType(String), 7 - Label(&'static str), 8 9 - Int(i32), 10 - Float(f32), 11 - Boolean(bool), 12 - String(String), 13 - }
··· 2 3 #[derive(Serialize, Clone, Debug)] 4 #[serde(tag = "key", content = "value")] 5 + pub enum ParameterType { 6 + AnyType(String), 7 + Label(&'static str), 8 9 + Int(i32), 10 + Float(f32), 11 + Boolean(bool), 12 + String(String), 13 + }
+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 + }
+1 -1
src-tauri/src/utils/mod.rs
··· 1 - pub mod config;
··· 1 + pub mod config;
+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
··· 88 } 89 90 .tab-new-dropdown{ 91 - opacity: 0; 92 position: absolute; 93 width: 180px; 94 transform: translate(-10px, 35px); 95 border-radius: 10px; 96 background: #fff1; 97 - transition: 0.1s; 98 }
··· 88 } 89 90 .tab-new-dropdown{ 91 position: absolute; 92 width: 180px; 93 transform: translate(-10px, 35px); 94 border-radius: 10px; 95 background: #fff1; 96 }
+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
··· 5 name: string, 6 id: string, 7 nodes: Node[], 8 selected: Accessor<boolean>, 9 setSelected: Setter<boolean> 10 }
··· 5 name: string, 6 id: string, 7 nodes: Node[], 8 + 9 selected: Accessor<boolean>, 10 setSelected: Setter<boolean> 11 + 12 + needsSave: Accessor<boolean>, 13 + setNeedsSave: Setter<boolean> 14 }