-7
README.md
-7
README.md
···
1
-
# Tauri + Solid + Typescript
2
-
3
-
This template should help get you started developing with Tauri, Solid and Typescript in Vite.
4
-
5
-
## Recommended IDE Setup
6
-
7
-
- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)
+3
-2
src-tauri/Cargo.lock
+3
-2
src-tauri/Cargo.lock
···
6
6
name = "VRCMacros"
7
7
version = "0.1.0"
8
8
dependencies = [
9
+
"anyhow",
9
10
"dirs",
10
11
"serde",
11
12
"serde_json",
···
78
79
79
80
[[package]]
80
81
name = "anyhow"
81
-
version = "1.0.98"
82
+
version = "1.0.99"
82
83
source = "registry+https://github.com/rust-lang/crates.io-index"
83
-
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
84
+
checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
84
85
85
86
[[package]]
86
87
name = "async-broadcast"
+1
src-tauri/Cargo.toml
+1
src-tauri/Cargo.toml
+10
-3
src-tauri/src/lib.rs
+10
-3
src-tauri/src/lib.rs
···
1
1
use std::fs;
2
2
3
-
use sqlx::{migrate::MigrateDatabase, Sqlite, SqlitePool};
3
+
use sqlx::{ migrate::MigrateDatabase, Sqlite, SqlitePool };
4
4
5
-
use crate::utils::config::Config;
5
+
use crate::{ setup::setup, utils::config::Config };
6
6
7
+
mod setup;
7
8
mod utils;
9
+
mod osc;
8
10
9
11
#[cfg_attr(mobile, tauri::mobile_entry_point)]
10
12
#[tokio::main]
···
26
28
if !db_file.exists(){ Sqlite::create_database(db_file.to_str().unwrap()).await.unwrap(); }
27
29
28
30
let conf_file = container_folder.join("VRCMacros.json");
29
-
if !conf_file.exists(){ fs::write(&conf_file, "{ \"setup_complete\": false }").unwrap() }
31
+
if !conf_file.exists(){ fs::write(&conf_file, "{}").unwrap() }
30
32
31
33
let pool = SqlitePool::connect(db_file.to_str().unwrap()).await.unwrap();
32
34
let conf = Config::new(conf_file);
···
36
38
.invoke_handler(tauri::generate_handler![])
37
39
.manage(pool)
38
40
.manage(conf)
41
+
.setup(| app | {
42
+
setup(app);
43
+
44
+
Ok(())
45
+
})
39
46
.run(tauri::generate_context!())
40
47
.expect("error while running tauri application");
41
48
}
+5
-2
src-tauri/src/osc.rs
+5
-2
src-tauri/src/osc.rs
···
2
2
3
3
use std::{ net::UdpSocket, sync::mpsc::Sender };
4
4
5
-
#[derive(Debug, Clone)]
5
+
use serde::Serialize;
6
+
7
+
#[derive(Debug, Clone, Serialize)]
6
8
pub enum OSCValue{
7
9
Int(i32),
8
10
Float(f32),
···
10
12
String(String),
11
13
}
12
14
13
-
#[derive(Debug)]
15
+
#[derive(Debug, Clone, Serialize)]
14
16
pub struct OSCMessage{
15
17
pub address: String,
16
18
pub values: Vec<OSCValue>
17
19
}
18
20
21
+
// TODO: implement osc bundles
19
22
pub fn start_server( sender: Sender<OSCMessage>, addr: &str ) {
20
23
let socket = UdpSocket::bind(addr).unwrap();
21
24
+25
src-tauri/src/setup.rs
+25
src-tauri/src/setup.rs
···
1
+
use std::sync;
2
+
3
+
use tauri::{ App, Emitter, Manager };
4
+
5
+
use crate::osc;
6
+
7
+
pub fn setup( app: &mut App ){
8
+
let window = app.get_webview_window("main").unwrap();
9
+
10
+
let ( sender, receiver ) = sync::mpsc::channel();
11
+
12
+
tokio::spawn(async move {
13
+
osc::start_server(sender, "127.0.0.1:9001");
14
+
});
15
+
16
+
tokio::spawn(async move {
17
+
loop {
18
+
let message = receiver.recv().unwrap();
19
+
20
+
21
+
22
+
window.emit("osc-message", message).unwrap();
23
+
}
24
+
});
25
+
}
+1
-1
src-tauri/src/utils/config.rs
+1
-1
src-tauri/src/utils/config.rs
+18
-7
src/App.css
+18
-7
src/App.css
···
22
22
23
23
h1, h2, h3, h4, h5, h6, p{
24
24
margin: 0;
25
+
font-weight: 500;
25
26
}
26
27
27
-
div[app-centre]{
28
+
div[app-carousel]{
28
29
position: fixed;
29
-
top: 50%;
30
-
left: 50%;
31
-
transform: translate(-50%, -50%);
32
-
text-align: center;
30
+
top: 10px;
31
+
left: 220px;
32
+
width: calc(100vw - 230px);
33
+
height: calc(100vh - 20px);
34
+
overflow: hidden;
35
+
}
36
+
37
+
div[app-page]{
38
+
width: calc(100vw - 230px);
39
+
height: calc(100vh - 20px);
40
+
overflow-y: auto;
41
+
overflow-x: hidden;
33
42
}
34
43
35
44
div[app-button]{
···
67
76
68
77
div[app-col]{
69
78
display: flex;
70
-
justify-content: center;
71
-
align-items: center;
79
+
}
80
+
81
+
div[app-col] > div{
82
+
width: 50%;
72
83
}
+26
-2
src/App.tsx
+26
-2
src/App.tsx
···
1
-
import { onMount } from "solid-js";
2
1
import "./App.css";
3
2
3
+
import { createEffect, createSignal, onCleanup, onMount } from "solid-js";
4
+
import { listen } from "@tauri-apps/api/event";
5
+
6
+
import { Sidebar } from "./Components/Sidebar";
7
+
import { Actions } from "./Components/Actions";
8
+
import { Relays } from "./Components/Relays";
9
+
import { animate } from "animejs";
10
+
import { Settings } from "./Components/Settings";
11
+
import { Debug } from "./Components/Debug";
12
+
4
13
let App = () => {
5
-
onMount(() => {
14
+
let [ page, setPage ] = createSignal(0);
15
+
let carousel!: HTMLDivElement;
16
+
17
+
onMount(async () => {
6
18
7
19
});
20
+
21
+
createEffect(() => {
22
+
let pagenum = page();
23
+
animate(carousel.children, { translateY: '-' + ( 100 * pagenum ) + '%', ease: 'outElastic(.1, .7)', duration: 500 });
24
+
})
8
25
9
26
return (
10
27
<>
28
+
<Sidebar setPage={setPage} />
11
29
30
+
<div app-carousel ref={carousel}>
31
+
<Actions />
32
+
<Relays />
33
+
<Debug page={page} />
34
+
<Settings />
35
+
</div>
12
36
</>
13
37
);
14
38
}
src/Components/Actions.css
src/Components/Actions.css
This is a binary file and will not be displayed.
+16
src/Components/Actions.tsx
+16
src/Components/Actions.tsx
···
1
+
import './Actions.css';
2
+
3
+
export let Actions = () => {
4
+
return (
5
+
<div app-page>
6
+
<div app-col>
7
+
<div><h1>Actions</h1></div>
8
+
<div app-button style={{ width: 'fit-content', "margin-left": '50%' }}>+</div>
9
+
</div>
10
+
11
+
<div>
12
+
13
+
</div>
14
+
</div>
15
+
)
16
+
}
+8
src/Components/Debug.css
+8
src/Components/Debug.css
+89
src/Components/Debug.tsx
+89
src/Components/Debug.tsx
···
1
+
import './Debug.css';
2
+
3
+
import { createEffect, onCleanup, onMount } from 'solid-js';
4
+
import { listen, UnlistenFn } from '@tauri-apps/api/event';
5
+
import { OSCMessage, OSCValue } from '../Structs/OSCMessage';
6
+
7
+
let formatValuesForDebug = ( values: OSCValue[] ): string => {
8
+
let text = '';
9
+
10
+
for(let value of values){
11
+
if(value.Boolean !== undefined)
12
+
text += ' Boolean: ' + value.Boolean;
13
+
else if(value.Float !== undefined)
14
+
text += ' Float: ' + value.Float.toFixed(6);
15
+
else if(value.Int !== undefined)
16
+
text += ' Int: ' + value.Int;
17
+
else if(value.String !== undefined)
18
+
text += ' String: ' + value.String;
19
+
}
20
+
21
+
return text.trimStart();
22
+
}
23
+
24
+
export interface DebugProps{
25
+
page: () => number
26
+
}
27
+
28
+
export let Debug = ( props: DebugProps ) => {
29
+
let debugContainer!: HTMLDivElement;
30
+
31
+
let debugEls: any = {};
32
+
33
+
let isListening = false;
34
+
let unlisten: UnlistenFn;
35
+
36
+
let stopListening = () => {
37
+
if(!isListening)return;
38
+
isListening = false;
39
+
40
+
unlisten();
41
+
}
42
+
43
+
let startListening = async () => {
44
+
if(isListening)return;
45
+
isListening = true;
46
+
47
+
unlisten = await listen<OSCMessage>('osc-message', ( ev ) => {
48
+
let el = debugEls[ev.payload.address];
49
+
if(el){
50
+
el.style.boxShadow = '#00ccff 0 0 10px';
51
+
debugContainer.insertBefore(el, debugContainer.firstChild);
52
+
53
+
el.innerHTML = `<div>${ ev.payload.address }</div><div>${ formatValuesForDebug(ev.payload.values) }</div>`;
54
+
setTimeout(() => { el.style.boxShadow = '#00ccff 0 0 0px'; })
55
+
} else{
56
+
el = <div app-debug-el app-col><div>{ ev.payload.address }</div><div>{ formatValuesForDebug(ev.payload.values) }</div></div> as Node;
57
+
58
+
el.style.boxShadow = '#00ccff 0 0 10px';
59
+
debugContainer.insertBefore(el, debugContainer.firstChild);
60
+
61
+
setTimeout(() => { el.style.boxShadow = '#00ccff 0 0 0px'; })
62
+
debugEls[ev.payload.address] = el;
63
+
}
64
+
})
65
+
}
66
+
67
+
onMount(() => {
68
+
createEffect(() => {
69
+
if(props.page() === 2)
70
+
startListening();
71
+
else
72
+
stopListening();
73
+
});
74
+
});
75
+
76
+
onCleanup(() => {
77
+
stopListening();
78
+
});
79
+
80
+
return (
81
+
<div app-page>
82
+
<h1>Debug</h1>
83
+
84
+
<div ref={debugContainer}>
85
+
86
+
</div>
87
+
</div>
88
+
)
89
+
}
src/Components/Relays.css
src/Components/Relays.css
This is a binary file and will not be displayed.
+9
src/Components/Relays.tsx
+9
src/Components/Relays.tsx
src/Components/Settings.css
src/Components/Settings.css
This is a binary file and will not be displayed.
+9
src/Components/Settings.tsx
+9
src/Components/Settings.tsx
+32
src/Components/Sidebar.css
+32
src/Components/Sidebar.css
···
1
+
div[app-sidebar]{
2
+
position: fixed;
3
+
top: 10px;
4
+
left: 10px;
5
+
6
+
height: calc(100vh - 20px);
7
+
width: 200px;
8
+
padding: 0px;
9
+
10
+
background: #272e44;
11
+
border-radius: 5px;
12
+
}
13
+
14
+
div[app-sidebar-tab]{
15
+
padding: 10px;
16
+
cursor: pointer;
17
+
user-select: none;
18
+
-webkit-user-select: none;
19
+
transition: 0.1s;
20
+
margin: 10px;
21
+
border-radius: 5px;
22
+
}
23
+
24
+
div[app-sidebar-tab]:hover{
25
+
background: #5b6ca5;
26
+
}
27
+
28
+
div[app-sidebar-tab-dropped]{
29
+
position: absolute;
30
+
width: calc(100% - 20px);
31
+
bottom: 0px;
32
+
}
+19
src/Components/Sidebar.tsx
+19
src/Components/Sidebar.tsx
···
1
+
import './Sidebar.css'
2
+
3
+
export interface SidebarProps{
4
+
setPage: ( page: number ) => number
5
+
}
6
+
7
+
export let Sidebar = ( props: SidebarProps ) => {
8
+
return (
9
+
<>
10
+
<div app-sidebar>
11
+
<div app-sidebar-tab onClick={() => props.setPage(0)}>Actions</div>
12
+
<div app-sidebar-tab onClick={() => props.setPage(1)}>Relays</div>
13
+
<div app-sidebar-tab onClick={() => props.setPage(2)}>Debug</div>
14
+
15
+
<div app-sidebar-tab app-sidebar-tab-dropped onClick={() => props.setPage(3)}>Settings</div>
16
+
</div>
17
+
</>
18
+
)
19
+
}