[ENG-1690] Upgrade to Tauri 2.0 (#2433)

* Most of it

* Cleanup menu

* fixes

* Fixed linux compilation, still not working though, as the app panic on start

* Add dialog permissions

* fix search keybind

* Fix linux
∙  - Disable linux-ipc-protocol feature, as that is causing panics due to trying to access some WebKit internal structures outside the main thread

* Update to Ubuntu 22.04, new tauri doesn't support ubuntu 20.04
>  - Add note to download page about deb's distro support

* Remove gen/schemas

* Update linux dependencies in setup.sh

* Fix linux deps again
 - Enable rt-tokio-crypto-rust feature in secret-service so clippy stops complaining

* Clippy + auto fmt

* Fix cache-factory
 - Create a devtools feature for desktop app, which is enabled by default on dev builds

* Fix minor error in patchTauri.mjs

* Fix some envvars that have been renamed on tauri v2

* Dont change the secrets

* Add missing linux dependency

---------

Co-authored-by: Vítor Vasconcellos <vasconcellos.dev@gmail.com>
This commit is contained in:
Oscar Beaumont 2024-05-07 16:36:50 +08:00 committed by GitHub
parent 408499229b
commit a238760c88
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 2407 additions and 1375 deletions

View file

@ -40,15 +40,15 @@ jobs:
target: x86_64-pc-windows-msvc target: x86_64-pc-windows-msvc
# - host: windows-latest # - host: windows-latest
# target: aarch64-pc-windows-msvc # target: aarch64-pc-windows-msvc
- host: ubuntu-20.04 - host: ubuntu-22.04
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
# - host: ubuntu-20.04 # - host: ubuntu-22.04
# target: x86_64-unknown-linux-musl # target: x86_64-unknown-linux-musl
# - host: ubuntu-20.04 # - host: uubuntu-22.04
# target: aarch64-unknown-linux-gnu # target: aarch64-unknown-linux-gnu
# - host: ubuntu-20.04 # - host: ubuntu-22.04
# target: aarch64-unknown-linux-musl # target: aarch64-unknown-linux-musl
# - host: ubuntu-20.04 # - host: ubuntu-22.04
# target: armv7-unknown-linux-gnueabihf # target: armv7-unknown-linux-gnueabihf
name: 'Make Cache' name: 'Make Cache'
runs-on: ${{ matrix.settings.host }} runs-on: ${{ matrix.settings.host }}

View file

@ -178,7 +178,7 @@ jobs:
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
strategy: strategy:
matrix: matrix:
platform: [ubuntu-20.04, macos-14, windows-latest] platform: [ubuntu-22.04, macos-14, windows-latest]
permissions: permissions:
contents: read contents: read
timeout-minutes: 45 timeout-minutes: 45
@ -241,7 +241,7 @@ jobs:
# runs-on: ${{ matrix.platform }} # runs-on: ${{ matrix.platform }}
# strategy: # strategy:
# matrix: # matrix:
# platform: [ubuntu-20.04, macos-latest, windows-latest] # platform: [ubuntu-22.04, macos-latest, windows-latest]
# steps: # steps:
# - name: Checkout repository # - name: Checkout repository
# uses: actions/checkout@v4 # uses: actions/checkout@v4

View file

@ -32,17 +32,17 @@ jobs:
arch: x86_64 arch: x86_64
# - host: windows-latest # - host: windows-latest
# target: aarch64-pc-windows-msvc # target: aarch64-pc-windows-msvc
- host: ubuntu-20.04 - host: ubuntu-22.04
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
bundles: deb bundles: deb
os: linux os: linux
arch: x86_64 arch: x86_64
# - host: ubuntu-20.04 # - host: ubuntu-22.04
# target: x86_64-unknown-linux-musl # target: x86_64-unknown-linux-musl
# - host: ubuntu-20.04 # - host: ubuntu-22.04
# target: aarch64-unknown-linux-gnu # target: aarch64-unknown-linux-gnu
# bundles: deb # bundles: deb
# - host: ubuntu-20.04 # - host: ubuntu-22.04
# target: aarch64-unknown-linux-musl # target: aarch64-unknown-linux-musl
name: Desktop - Main ${{ matrix.settings.target }} name: Desktop - Main ${{ matrix.settings.target }}
runs-on: ${{ matrix.settings.host }} runs-on: ${{ matrix.settings.host }}
@ -101,8 +101,8 @@ jobs:
run: | run: |
pnpm tauri build --ci -v --target ${{ matrix.settings.target }} --bundles ${{ matrix.settings.bundles }},updater pnpm tauri build --ci -v --target ${{ matrix.settings.target }} --bundles ${{ matrix.settings.bundles }},updater
env: env:
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }} ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}

2138
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -21,19 +21,19 @@ repository = "https://github.com/spacedriveapp/spacedrive"
[workspace.dependencies] [workspace.dependencies]
# First party dependencies # First party dependencies
prisma-client-rust = { git = "https://github.com/spacedriveapp/prisma-client-rust", rev = "f99d6f5566570f3ab1edecb7a172ad25b03d95af", features = [ prisma-client-rust = { git = "https://github.com/spacedriveapp/prisma-client-rust", rev = "d6f6b224b874fad904bb17b81cf2e570c6003ac9", features = [
"specta", "specta",
"sqlite-create-many", "sqlite-create-many",
"migrations", "migrations",
"sqlite", "sqlite",
], default-features = false } ], default-features = false }
prisma-client-rust-cli = { git = "https://github.com/spacedriveapp/prisma-client-rust", rev = "f99d6f5566570f3ab1edecb7a172ad25b03d95af", features = [ prisma-client-rust-cli = { git = "https://github.com/spacedriveapp/prisma-client-rust", rev = "d6f6b224b874fad904bb17b81cf2e570c6003ac9", features = [
"specta", "specta",
"sqlite-create-many", "sqlite-create-many",
"migrations", "migrations",
"sqlite", "sqlite",
], default-features = false } ], default-features = false }
prisma-client-rust-sdk = { git = "https://github.com/spacedriveapp/prisma-client-rust", rev = "f99d6f5566570f3ab1edecb7a172ad25b03d95af", features = [ prisma-client-rust-sdk = { git = "https://github.com/spacedriveapp/prisma-client-rust", rev = "d6f6b224b874fad904bb17b81cf2e570c6003ac9", features = [
"sqlite", "sqlite",
], default-features = false } ], default-features = false }
@ -42,12 +42,11 @@ tracing-subscriber = "0.3.18"
tracing-appender = "0.2.3" tracing-appender = "0.2.3"
rspc = { version = "0.1.4" } rspc = { version = "0.1.4" }
specta = { version = "=2.0.0-rc.7" } specta = { version = "=2.0.0-rc.11" }
tauri-specta = { version = "=2.0.0-rc.4" } tauri-specta = { version = "=2.0.0-rc.8" }
swift-rs = { version = "1.0.6" } swift-rs = { version = "1.0.6" }
# Third party dependencies used by one or more of our crates # Third party dependencies used by one or more of our crates
anyhow = "1.0.75" anyhow = "1.0.75"
async-channel = "2.0.0" async-channel = "2.0.0"
@ -96,11 +95,7 @@ tracing-test = { version = "^0.2.4" }
if-watch = { git = "https://github.com/oscartbeaumont/if-watch.git", rev = "a92c17d3f85c1c6fb0afeeaf6c2b24d0b147e8c3" } if-watch = { git = "https://github.com/oscartbeaumont/if-watch.git", rev = "a92c17d3f85c1c6fb0afeeaf6c2b24d0b147e8c3" }
# We hack it to the high heavens # We hack it to the high heavens
rspc = { git = "https://github.com/spacedriveapp/rspc.git", rev = "f3347e2e8bfe3f37bfacc437ca329fe71cdcb048" } rspc = { git = "https://github.com/spacedriveapp/rspc.git", rev = "ab12964b140991e0730c3423693533fba71efb03" }
# `cursor_position` method
tauri = { git = "https://github.com/spacedriveapp/tauri.git", rev = "8409af71a83d631ff9d1cd876c441a57511a1cbd" }
tao = { git = "https://github.com/spacedriveapp/tao", rev = "7880adbc090402c44fbcf006669458fa82623403" }
# Add `Control::open_stream_with_addrs` # Add `Control::open_stream_with_addrs`
libp2p = { git = "https://github.com/spacedriveapp/rust-libp2p.git", rev = "a005656df7e82059a0eb2e333ebada4731d23f8c" } libp2p = { git = "https://github.com/spacedriveapp/rust-libp2p.git", rev = "a005656df7e82059a0eb2e333ebada4731d23f8c" }

View file

@ -11,5 +11,5 @@ libc = "0.2"
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
# WARNING: gtk should follow the same version used by tauri # WARNING: gtk should follow the same version used by tauri
# https://github.com/tauri-apps/tauri/blob/441eb4f4a5f9af206752c2e287975eb8d5ccfd01/core/tauri/Cargo.toml#L95 # https://github.com/tauri-apps/tauri/blob/tauri-v2.0.0-beta.17/core/tauri/Cargo.toml#L85C1-L85C51
gtk = { version = "0.15", features = [ "v3_20" ] } gtk = { version = "0.18", features = [ "v3_24" ] }

View file

@ -20,7 +20,10 @@
"@sd/ui": "workspace:*", "@sd/ui": "workspace:*",
"@t3-oss/env-core": "^0.7.1", "@t3-oss/env-core": "^0.7.1",
"@tanstack/react-query": "^4.36.1", "@tanstack/react-query": "^4.36.1",
"@tauri-apps/api": "1.5.1", "@tauri-apps/api": "next",
"@tauri-apps/plugin-dialog": "2.0.0-beta.2",
"@tauri-apps/plugin-os": "2.0.0-beta.2",
"@tauri-apps/plugin-shell": "2.0.0-beta.2",
"consistent-hash": "^1.2.2", "consistent-hash": "^1.2.2",
"immer": "^10.0.3", "immer": "^10.0.3",
"react": "^18.2.0", "react": "^18.2.0",
@ -31,7 +34,7 @@
"devDependencies": { "devDependencies": {
"@sd/config": "workspace:*", "@sd/config": "workspace:*",
"@sentry/vite-plugin": "^2.16.0", "@sentry/vite-plugin": "^2.16.0",
"@tauri-apps/cli": "^1.5.11", "@tauri-apps/cli": "next",
"@types/react": "^18.2.67", "@types/react": "^18.2.67",
"@types/react-dom": "^18.2.22", "@types/react-dom": "^18.2.22",
"sass": "^1.72.0", "sass": "^1.72.0",

View file

@ -1,6 +1,7 @@
# Generated by Cargo # Generated by Cargo
# will have compiled files and executables # will have compiled files and executables
/target/ /target/
gen/
WixTools WixTools
*.dll *.dll
*.dll.* *.dll.*

View file

@ -28,26 +28,25 @@ tracing = { workspace = true }
tauri-specta = { workspace = true, features = ["typescript"] } tauri-specta = { workspace = true, features = ["typescript"] }
uuid = { workspace = true, features = ["serde"] } uuid = { workspace = true, features = ["serde"] }
thiserror.workspace = true thiserror.workspace = true
directories = "5.0.1"
opener = { version = "0.6.1", features = ["reveal"] } opener = { version = "0.6.1", features = ["reveal"] }
tauri = { version = "=1.5.3", features = [ tauri = { version = "=2.0.0-beta.17", features = [
"macos-private-api", "macos-private-api",
"path-all", "unstable",
"protocol-all", "linux-libxdo",
"os-all",
"shell-all",
"dialog-all",
"linux-protocol-headers",
"updater",
"window-all",
"native-tls-vendored",
"tracing",
] } ] }
directories = "5.0.1" tauri-plugin-updater = "2.0.0-beta"
tauri-plugin-dialog = { git = "https://github.com/tauri-apps/plugins-workspace", rev = "1fa4d30eabb3768e1e97fa56f275408db2955b32" } # "2.0.0-beta"
tauri-plugin-os = "2.0.0-beta"
tauri-plugin-shell = "2.0.0-beta"
serde_json.workspace = true
strum = { workspace = true, features = ["derive"] }
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
sd-desktop-linux = { path = "../crates/linux" } sd-desktop-linux = { path = "../crates/linux" }
webkit2gtk = { version = "0.18.2", features = ["v2_2"] } # https://github.com/tauri-apps/tauri/blob/tauri-v2.0.0-beta.17/core/tauri/Cargo.toml#L86
webkit2gtk = { version = "=2.0.1", features = ["v2_38"] }
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
sd-desktop-macos = { path = "../crates/macos" } sd-desktop-macos = { path = "../crates/macos" }
@ -57,9 +56,10 @@ sd-desktop-windows = { path = "../crates/windows" }
webview2-com = "0.19.1" webview2-com = "0.19.1"
[build-dependencies] [build-dependencies]
tauri-build = "1.5.0" tauri-build = "=2.0.0-beta.13"
[features] [features]
default = ["custom-protocol"] default = ["custom-protocol"]
devtools = ["tauri/devtools"]
ai-models = ["sd-core/ai"] ai-models = ["sd-core/ai"]
custom-protocol = ["tauri/custom-protocol"] custom-protocol = ["tauri/custom-protocol"]

View file

@ -0,0 +1,21 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"path:default",
"event:default",
"window:default",
"app:default",
"image:default",
"resources:default",
"menu:default",
"tray:default",
"webview:default",
"webview:allow-internal-toggle-devtools",
"os:allow-os-type",
"window:allow-start-dragging",
"dialog:allow-open"
]
}

View file

@ -3,25 +3,17 @@
windows_subsystem = "windows" windows_subsystem = "windows"
)] )]
use std::{ use std::{fs, path::PathBuf, process::Command, sync::Arc, time::Duration};
collections::HashMap,
fs,
path::PathBuf,
process::Command,
sync::{Arc, Mutex, PoisonError},
time::Duration,
};
use menu::{set_enabled, MenuEvent};
use sd_core::{Node, NodeError}; use sd_core::{Node, NodeError};
use sd_fda::DiskAccess; use sd_fda::DiskAccess;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tauri::{ use tauri::{async_runtime::block_on, webview::PlatformWebview, AppHandle, Manager, WindowEvent};
api::path, ipc::RemoteDomainAccessScope, window::PlatformWebview, AppHandle, FileDropEvent,
Manager, Window, WindowEvent,
};
use tauri_plugins::{sd_error_plugin, sd_server_plugin}; use tauri_plugins::{sd_error_plugin, sd_server_plugin};
use tauri_specta::{collect_events, ts, Event}; use tauri_specta::{collect_events, ts};
use tokio::task::block_in_place;
use tokio::time::sleep; use tokio::time::sleep;
use tracing::error; use tracing::error;
@ -34,7 +26,7 @@ mod updater;
#[tauri::command(async)] #[tauri::command(async)]
#[specta::specta] #[specta::specta]
async fn app_ready(app_handle: AppHandle) { async fn app_ready(app_handle: AppHandle) {
let window = app_handle.get_window("main").unwrap(); let window = app_handle.get_webview_window("main").unwrap();
window.show().unwrap(); window.show().unwrap();
} }
@ -47,22 +39,19 @@ async fn request_fda_macos() {
#[tauri::command(async)] #[tauri::command(async)]
#[specta::specta] #[specta::specta]
async fn set_menu_bar_item_state(_window: tauri::Window, _id: String, _enabled: bool) { async fn set_menu_bar_item_state(window: tauri::Window, event: MenuEvent, enabled: bool) {
#[cfg(target_os = "macos")] let menu = window
{ .menu()
_window .expect("unable to get menu for current window");
.menu_handle()
.get_item(&_id) set_enabled(&menu, event, enabled);
.set_enabled(_enabled)
.expect("Unable to modify menu item");
}
} }
#[tauri::command(async)] #[tauri::command(async)]
#[specta::specta] #[specta::specta]
async fn reload_webview(app_handle: AppHandle) { async fn reload_webview(app_handle: AppHandle) {
app_handle app_handle
.get_window("main") .get_webview_window("main")
.expect("Error getting window handle") .expect("Error getting window handle")
.with_webview(reload_webview_inner) .with_webview(reload_webview_inner)
.expect("Error while reloading webview"); .expect("Error while reloading webview");
@ -77,7 +66,7 @@ fn reload_webview_inner(webview: PlatformWebview) {
} }
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
use webkit2gtk::traits::WebViewExt; use webkit2gtk::WebViewExt;
webview.inner().reload(); webview.inner().reload();
} }
@ -95,8 +84,10 @@ fn reload_webview_inner(webview: PlatformWebview) {
#[tauri::command(async)] #[tauri::command(async)]
#[specta::specta] #[specta::specta]
async fn reset_spacedrive(app_handle: AppHandle) { async fn reset_spacedrive(app_handle: AppHandle) {
let data_dir = path::data_dir() let data_dir = app_handle
.unwrap_or_else(|| PathBuf::from("./")) .path()
.data_dir()
.unwrap_or_else(|_| PathBuf::from("./"))
.join("spacedrive"); .join("spacedrive");
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
@ -112,25 +103,9 @@ async fn reset_spacedrive(app_handle: AppHandle) {
#[tauri::command(async)] #[tauri::command(async)]
#[specta::specta] #[specta::specta]
async fn refresh_menu_bar( async fn refresh_menu_bar(node: tauri::State<'_, Arc<Node>>, app: AppHandle) -> Result<(), ()> {
_node: tauri::State<'_, Arc<Node>>, let has_library = !node.libraries.get_all().await.is_empty();
_app_handle: AppHandle, menu::refresh_menu_bar(&app, has_library);
) -> Result<(), ()> {
#[cfg(target_os = "macos")]
{
let menu_handles: Vec<tauri::window::MenuHandle> = _app_handle
.windows()
.iter()
.map(|x| x.1.menu_handle())
.collect();
let has_library = !_node.libraries.get_all().await.is_empty();
for menu in menu_handles {
menu::set_library_locked_menu_items_enabled(menu, has_library);
}
}
Ok(()) Ok(())
} }
@ -199,11 +174,6 @@ pub enum DragAndDropEvent {
Cancelled, Cancelled,
} }
#[derive(Default)]
pub struct DragAndDropState {
windows: HashMap<tauri::Window, tokio::task::JoinHandle<()>>,
}
const CLIENT_ID: &str = "2abb241e-40b8-4517-a3e3-5594375c8fbb"; const CLIENT_ID: &str = "2abb241e-40b8-4517-a3e3-5594375c8fbb";
#[tokio::main] #[tokio::main]
@ -211,46 +181,8 @@ async fn main() -> tauri::Result<()> {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
sd_desktop_linux::normalize_environment(); sd_desktop_linux::normalize_environment();
let data_dir = path::data_dir() let (invoke_handler, register_events) = {
.unwrap_or_else(|| PathBuf::from("./")) let builder = ts::builder()
.join("spacedrive");
#[cfg(debug_assertions)]
let data_dir = data_dir.join("dev");
// The `_guard` must be assigned to variable for flushing remaining logs on main exit through Drop
let (_guard, result) = match Node::init_logger(&data_dir) {
Ok(guard) => (
Some(guard),
Node::new(data_dir, sd_core::Env::new(CLIENT_ID)).await,
),
Err(err) => (None, Err(NodeError::Logger(err))),
};
let app = tauri::Builder::default();
let (node_router, app) = match result {
Ok((node, router)) => (Some((node, router)), app),
Err(err) => {
error!("Error starting up the node: {err:#?}");
(None, app.plugin(sd_error_plugin(err)))
}
};
let (node, router) = node_router.expect("Unable to get the node or router");
let should_clear_localstorage = node.libraries.get_all().await.is_empty();
let app = app
.plugin(rspc::integrations::tauri::plugin(router, {
let node = node.clone();
move || node.clone()
}))
.plugin(sd_server_plugin(node.clone()).await.unwrap()) // TODO: Handle `unwrap`
.manage(node.clone());
let specta_builder = {
let specta_builder = ts::builder()
.events(collect_events![DragAndDropEvent]) .events(collect_events![DragAndDropEvent])
.commands(tauri_specta::collect_commands![ .commands(tauri_specta::collect_commands![
app_ready, app_ready,
@ -269,224 +201,145 @@ async fn main() -> tauri::Result<()> {
file::open_ephemeral_file_with, file::open_ephemeral_file_with,
file::reveal_items, file::reveal_items,
theme::lock_app_theme, theme::lock_app_theme,
// TODO: move to plugin w/tauri-specta
updater::check_for_update, updater::check_for_update,
updater::install_update updater::install_update
]) ])
.config(specta::ts::ExportConfig::default().formatter(specta::ts::formatter::prettier)); .config(specta::ts::ExportConfig::default().formatter(specta::ts::formatter::prettier));
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let specta_builder = specta_builder.path("../src/commands.ts"); let builder = builder.path("../src/commands.ts");
specta_builder.into_plugin() builder.build().unwrap()
}; };
let file_drop_status = Arc::new(Mutex::new(DragAndDropState::default())); tauri::Builder::default()
let app = app .invoke_handler(invoke_handler)
.plugin(updater::plugin())
// .plugin(tauri_plugin_window_state::Builder::default().build())
.plugin(specta_builder)
.setup(move |app| { .setup(move |app| {
let app = app.handle(); // We need a the app handle to determine the data directory now.
// This means all the setup code has to be within `setup`, however it doesn't support async so we `block_on`.
block_in_place(|| {
block_on(async move {
register_events(app);
println!("setup"); let data_dir = app
.path()
.data_dir()
.unwrap_or_else(|_| PathBuf::from("./"))
.join("spacedrive");
app.windows().iter().for_each(|(_, window)| { #[cfg(debug_assertions)]
if should_clear_localstorage { let data_dir = data_dir.join("dev");
println!("bruh?");
window.eval("localStorage.clear();").ok();
}
tokio::spawn({ // The `_guard` must be assigned to variable for flushing remaining logs on main exit through Drop
let window = window.clone(); let (_guard, result) = match Node::init_logger(&data_dir) {
async move { Ok(guard) => (
sleep(Duration::from_secs(3)).await; Some(guard),
if !window.is_visible().unwrap_or(true) { Node::new(data_dir, sd_core::Env::new(CLIENT_ID)).await,
// This happens if the JS bundle crashes and hence doesn't send ready event. ),
println!( Err(err) => (None, Err(NodeError::Logger(err))),
"Window did not emit `app_ready` event fast enough. Showing window..." };
);
window.show().expect("Main window should show"); let handle = app.handle();
let (node, router) = match result {
Ok(r) => r,
Err(err) => {
error!("Error starting up the node: {err:#?}");
handle.plugin(sd_error_plugin(err))?;
return Ok(());
} }
} };
});
#[cfg(target_os = "windows")] let should_clear_localstorage = node.libraries.get_all().await.is_empty();
window.set_decorations(true).unwrap();
#[cfg(target_os = "macos")] handle.plugin(rspc::integrations::tauri::plugin(router, {
{ let node = node.clone();
use sd_desktop_macos::{blur_window_background, set_titlebar_style}; move || node.clone()
}))?;
handle.plugin(sd_server_plugin(node.clone(), handle).await.unwrap())?; // TODO: Handle `unwrap`
handle.manage(node.clone());
let nswindow = window.ns_window().unwrap(); handle.windows().iter().for_each(|(_, window)| {
if should_clear_localstorage {
unsafe { set_titlebar_style(&nswindow, false) }; println!("bruh?");
unsafe { blur_window_background(&nswindow) }; for webview in window.webviews() {
webview.eval("localStorage.clear();").ok();
tokio::spawn({
let libraries = node.libraries.clone();
let menu_handle = window.menu_handle();
async move {
if libraries.get_all().await.is_empty() {
menu::set_library_locked_menu_items_enabled(menu_handle, false);
} }
} }
tokio::spawn({
let window = window.clone();
async move {
sleep(Duration::from_secs(3)).await;
if !window.is_visible().unwrap_or(true) {
// This happens if the JS bundle crashes and hence doesn't send ready event.
println!(
"Window did not emit `app_ready` event fast enough. Showing window..."
);
window.show().expect("Main window should show");
}
}
});
#[cfg(target_os = "windows")]
window.set_decorations(true).unwrap();
#[cfg(target_os = "macos")]
{
use sd_desktop_macos::{blur_window_background, set_titlebar_style};
let nswindow = window.ns_window().unwrap();
unsafe { set_titlebar_style(&nswindow, false) };
unsafe { blur_window_background(&nswindow) };
}
}); });
}
});
// Configure IPC for custom protocol Ok(())
app.ipc_scope().configure_remote_access( })
RemoteDomainAccessScope::new("localhost") })
.allow_on_scheme("spacedrive")
.add_window("main"),
);
Ok(())
}) })
.on_menu_event(menu::handle_menu_event) .on_window_event(move |window, event| match event {
.on_window_event(move |event| match event.event() {
// macOS expected behavior is for the app to not exit when the main window is closed. // macOS expected behavior is for the app to not exit when the main window is closed.
// Instead, the window is hidden and the dock icon remains so that on user click it should show the window again. // Instead, the window is hidden and the dock icon remains so that on user click it should show the window again.
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
WindowEvent::CloseRequested { api, .. } => { WindowEvent::CloseRequested { api, .. } => {
// TODO: make this multi-window compatible in the future // TODO: make this multi-window compatible in the future
event window
.window()
.app_handle() .app_handle()
.hide() .hide()
.expect("Window should hide on macOS"); .expect("Window should hide on macOS");
api.prevent_close(); api.prevent_close();
} }
WindowEvent::FileDrop(drop) => {
let window = event.window();
let mut file_drop_status = file_drop_status
.lock()
.unwrap_or_else(PoisonError::into_inner);
match drop {
FileDropEvent::Hovered(paths) => {
// Look this shouldn't happen but let's be sure we don't leak threads.
if file_drop_status.windows.contains_key(window) {
return;
}
// We setup a thread to keep emitting the updated position of the cursor
// It will be killed when the `FileDropEvent` is finished or cancelled.
let paths = paths.clone();
file_drop_status.windows.insert(window.clone(), {
let window = window.clone();
tokio::spawn(async move {
let (mut last_x, mut last_y) = (0.0, 0.0);
loop {
let (x, y) = mouse_position(&window);
let x_diff = difference(x, last_x);
let y_diff = difference(y, last_y);
// If the mouse hasn't moved much we will "debounce" the event
if x_diff > 28.0 || y_diff > 28.0 {
last_x = x;
last_y = y;
DragAndDropEvent::Hovered {
paths: paths
.iter()
.filter_map(|x| x.to_str().map(|x| x.to_string()))
.collect(),
x,
y,
}
.emit(&window)
.ok();
}
sleep(Duration::from_millis(125)).await;
}
})
});
}
FileDropEvent::Dropped(paths) => {
if let Some(handle) = file_drop_status.windows.remove(window) {
handle.abort();
}
let (x, y) = mouse_position(window);
DragAndDropEvent::Dropped {
paths: paths
.iter()
.filter_map(|x| x.to_str().map(|x| x.to_string()))
.collect(),
x,
y,
}
.emit(window)
.ok();
}
FileDropEvent::Cancelled => {
if let Some(handle) = file_drop_status.windows.remove(window) {
handle.abort();
}
DragAndDropEvent::Cancelled.emit(window).ok();
}
_ => unreachable!(),
}
}
WindowEvent::Resized(_) => { WindowEvent::Resized(_) => {
let (_state, command) = if event let (_state, command) =
.window() if window.is_fullscreen().expect("Can't get fullscreen state") {
.is_fullscreen() (true, "window_fullscreened")
.expect("Can't get fullscreen state") } else {
{ (false, "window_not_fullscreened")
(true, "window_fullscreened") };
} else {
(false, "window_not_fullscreened")
};
event window
.window()
.emit("keybind", command) .emit("keybind", command)
.expect("Unable to emit window event"); .expect("Unable to emit window event");
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
let nswindow = event.window().ns_window().unwrap(); let nswindow = window.ns_window().unwrap();
unsafe { sd_desktop_macos::set_titlebar_style(&nswindow, _state) }; unsafe { sd_desktop_macos::set_titlebar_style(&nswindow, _state) };
} }
} }
_ => {} _ => {}
}) })
.menu(menu::get_menu()) .menu(menu::setup_menu)
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_shell::init())
// TODO: Bring back Tauri Plugin Window State - it was buggy so we removed it.
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(updater::plugin())
.manage(updater::State::default()) .manage(updater::State::default())
.build(tauri::generate_context!())?; .build(tauri::generate_context!())?
.run(|_, _| {});
app.run(|_, _| {});
Ok(()) Ok(())
} }
// Get the mouse position relative to the window
fn mouse_position(window: &Window) -> (f64, f64) {
// We apply the OS scaling factor.
// Tauri/Webkit *should* be responsible for this but it would seem it is bugged on the current webkit/tauri/wry/tao version.
// Using newer Webkit did fix this automatically but I can't for the life of me work out how to get the right glibc versions in CI so we can't ship it.
let scale_factor = window.scale_factor().unwrap();
let window_pos = window.outer_position().unwrap();
let cursor_pos = window.cursor_position().unwrap();
(
(cursor_pos.x - window_pos.x as f64) / scale_factor,
(cursor_pos.y - window_pos.y as f64) / scale_factor,
)
}
// The distance between two numbers as a positive integer.
fn difference(a: f64, b: f64) -> f64 {
let x = a - b;
if x < 0.0 {
x * -1.0
} else {
x
}
}

View file

@ -1,191 +1,276 @@
use tauri::{Manager, Menu, WindowMenuEvent, Wry}; use std::str::FromStr;
#[cfg(target_os = "macos")] use serde::Deserialize;
use tauri::{AboutMetadata, CustomMenuItem, MenuItem, Submenu}; use specta::Type;
use strum::{AsRefStr, EnumString};
use tauri::{
menu::{Menu, MenuItemKind},
AppHandle, Manager, Wry,
};
use tracing::error;
pub fn get_menu() -> Menu { #[derive(Debug, Clone, Copy, EnumString, AsRefStr, Type, Deserialize)]
#[cfg(target_os = "macos")] pub enum MenuEvent {
{ NewLibrary,
custom_menu_bar() NewFile,
} NewDirectory,
#[cfg(not(target_os = "macos"))] AddLocation,
{ OpenOverview,
Menu::new() OpenSearch,
OpenSettings,
ReloadExplorer,
SetLayoutGrid,
SetLayoutList,
SetLayoutMedia,
ToggleDeveloperTools,
NewWindow,
ReloadWebview,
}
impl ToString for MenuEvent {
fn to_string(&self) -> String {
self.as_ref().to_string()
} }
} }
// update this whenever you add something which requires a valid library to use /// Menu items which require a library to be open to use.
#[cfg(target_os = "macos")] /// They will be disabled/enabled automatically.
const LIBRARY_LOCKED_MENU_IDS: [&str; 12] = [ const LIBRARY_LOCKED_MENU_IDS: &[MenuEvent] = &[
"new_window", MenuEvent::NewWindow,
"open_overview", MenuEvent::OpenOverview,
"open_search", MenuEvent::OpenSearch,
"open_settings", MenuEvent::OpenSettings,
"reload_explorer", MenuEvent::ReloadExplorer,
"layout_grid", MenuEvent::SetLayoutGrid,
"layout_list", MenuEvent::SetLayoutList,
"layout_media", MenuEvent::SetLayoutMedia,
"new_file", MenuEvent::NewFile,
"new_directory", MenuEvent::NewDirectory,
"new_library", // disabled because the first one should at least be done via onboarding MenuEvent::NewLibrary,
"add_location", MenuEvent::AddLocation,
]; ];
#[cfg(target_os = "macos")] pub fn setup_menu(app: &AppHandle) -> tauri::Result<Menu<Wry>> {
fn custom_menu_bar() -> Menu { app.on_menu_event(move |app, event| {
let app_menu = Menu::new() if let Ok(event) = MenuEvent::from_str(&event.id().0) {
.add_native_item(MenuItem::About( handle_menu_event(event, app);
"Spacedrive".to_string(), } else {
AboutMetadata::new() println!("Unknown menu event: {}", event.id().0);
.authors(vec!["Spacedrive Technology Inc.".to_string()]) }
.license("AGPL-3.0-only") });
.version(env!("CARGO_PKG_VERSION"))
.website("https://spacedrive.com/")
.website_label("Spacedrive.com"),
))
.add_native_item(MenuItem::Separator)
.add_item(CustomMenuItem::new("new_library", "New Library").disabled()) // TODO(brxken128): add keybind handling here
.add_submenu(Submenu::new(
"Library",
Menu::new()
.add_item(CustomMenuItem::new("library_<uuid>", "Library 1").disabled())
.add_item(CustomMenuItem::new("library_<uuid2>", "Library 2").disabled()), // TODO: enumerate libraries and make this a library selector
))
.add_native_item(MenuItem::Separator)
.add_native_item(MenuItem::Hide)
.add_native_item(MenuItem::HideOthers)
.add_native_item(MenuItem::ShowAll)
.add_native_item(MenuItem::Separator)
.add_native_item(MenuItem::Quit);
let file_menu = Menu::new() #[cfg(not(target_os = "macos"))]
.add_item( {
CustomMenuItem::new("new_file", "New File") Menu::new(app)
.accelerator("CmdOrCtrl+N") }
.disabled(), // TODO(brxken128): add keybind handling here #[cfg(target_os = "macos")]
) {
.add_item( use tauri::menu::{AboutMetadataBuilder, MenuBuilder, MenuItemBuilder, SubmenuBuilder};
CustomMenuItem::new("new_directory", "New Directory")
.accelerator("CmdOrCtrl+D")
.disabled(), // TODO(brxken128): add keybind handling here
)
.add_item(CustomMenuItem::new("add_location", "Add Location").disabled()); // TODO(brxken128): add keybind handling here;
let edit_menu = Menu::new() let app_menu = SubmenuBuilder::new(app, "Spacedrive")
.add_native_item(MenuItem::Separator) .about(Some(
.add_native_item(MenuItem::Copy) AboutMetadataBuilder::new()
.add_native_item(MenuItem::Cut) .authors(Some(vec!["Spacedrive Technology Inc.".to_string()]))
.add_native_item(MenuItem::Paste) .license(Some(env!("CARGO_PKG_VERSION")))
.add_native_item(MenuItem::Redo) .version(Some(env!("CARGO_PKG_VERSION")))
.add_native_item(MenuItem::Undo) .website(Some("https://spacedrive.com/"))
.add_native_item(MenuItem::SelectAll); .website_label(Some("Spacedrive.com"))
.build(),
))
.separator()
.item(
&MenuItemBuilder::with_id(MenuEvent::NewLibrary, "New Library")
.accelerator("Cmd+Shift+T")
.build(app)?,
)
// .item(
// &SubmenuBuilder::new(app, "Libraries")
// // TODO: Implement this
// .items(&[])
// .build()?,
// )
.separator()
.hide()
.hide_others()
.show_all()
.separator()
.quit()
.build()?;
let view_menu = Menu::new() let file_menu = SubmenuBuilder::new(app, "File")
.add_item(CustomMenuItem::new("open_overview", "Overview").accelerator("CmdOrCtrl+.")) .item(
.add_item(CustomMenuItem::new("open_search", "Search").accelerator("CmdOrCtrl+F")) &MenuItemBuilder::with_id(MenuEvent::NewFile, "New File")
.add_item(CustomMenuItem::new("open_settings", "Settings").accelerator("CmdOrCtrl+Comma")) .accelerator("CmdOrCtrl+N")
.add_item( .build(app)?,
CustomMenuItem::new("reload_explorer", "Reload explorer").accelerator("CmdOrCtrl+R"), )
) .item(
.add_submenu(Submenu::new( &MenuItemBuilder::with_id(MenuEvent::NewDirectory, "New Directory")
"Layout", .accelerator("CmdOrCtrl+D")
Menu::new() .build(app)?,
.add_item(CustomMenuItem::new("layout_grid", "Grid (Default)").disabled()) )
.add_item(CustomMenuItem::new("layout_list", "List").disabled()) .item(
.add_item(CustomMenuItem::new("layout_media", "Media").disabled()), &MenuItemBuilder::with_id(MenuEvent::AddLocation, "Add Location")
)); // .accelerator("") // TODO
// .add_item( .build(app)?,
// CustomMenuItem::new("command_pallete", "Command Pallete") )
// .accelerator("CmdOrCtrl+P"), .build()?;
// )
#[cfg(debug_assertions)] let edit_menu = SubmenuBuilder::new(app, "Edit")
let view_menu = view_menu.add_native_item(MenuItem::Separator).add_item( .copy()
CustomMenuItem::new("toggle_devtools", "Toggle Developer Tools") .cut()
.accelerator("CmdOrCtrl+Shift+Alt+I"), .paste()
); .redo()
.undo()
.select_all()
.build()?;
let window_menu = Menu::new() let view_menu = SubmenuBuilder::new(app, "View")
.add_native_item(MenuItem::Minimize) .item(
.add_native_item(MenuItem::Zoom) &MenuItemBuilder::with_id(MenuEvent::OpenOverview, "Open Overview")
.add_item( .accelerator("CmdOrCtrl+.")
CustomMenuItem::new("new_window", "New Window") .build(app)?,
.accelerator("CmdOrCtrl+Shift+N") )
.disabled(), .item(
) &MenuItemBuilder::with_id(MenuEvent::OpenSearch, "Search")
.add_item(CustomMenuItem::new("close_window", "Close Window").accelerator("CmdOrCtrl+W")) .accelerator("CmdOrCtrl+F")
.add_native_item(MenuItem::EnterFullScreen) .build(app)?,
.add_native_item(MenuItem::Separator) )
.add_item( .item(
CustomMenuItem::new("reload_app", "Reload Webview").accelerator("CmdOrCtrl+Shift+R"), &MenuItemBuilder::with_id(MenuEvent::OpenSettings, "Settings")
.accelerator("CmdOrCtrl+Comma")
.build(app)?,
)
.item(
&MenuItemBuilder::with_id(MenuEvent::ReloadExplorer, "Open Explorer")
.accelerator("CmdOrCtrl+R")
.build(app)?,
)
.item(
&SubmenuBuilder::new(app, "Layout")
.item(
&MenuItemBuilder::with_id(MenuEvent::SetLayoutGrid, "Grid (Default)")
// .accelerator("") // TODO
.build(app)?,
)
.item(
&MenuItemBuilder::with_id(MenuEvent::SetLayoutList, "List")
// .accelerator("") // TODO
.build(app)?,
)
.item(
&MenuItemBuilder::with_id(MenuEvent::SetLayoutMedia, "Media")
// .accelerator("") // TODO
.build(app)?,
)
.build()?,
);
#[cfg(debug_assertions)]
let view_menu = view_menu.separator().item(
&MenuItemBuilder::with_id(MenuEvent::ToggleDeveloperTools, "Toggle Developer Tools")
.accelerator("CmdOrCtrl+Shift+Alt+I")
.build(app)?,
); );
Menu::new() let view_menu = view_menu.build()?;
.add_submenu(Submenu::new("Spacedrive", app_menu))
.add_submenu(Submenu::new("File", file_menu)) let window_menu = SubmenuBuilder::new(app, "Window")
.add_submenu(Submenu::new("Edit", edit_menu)) .minimize()
.add_submenu(Submenu::new("View", view_menu)) .item(
.add_submenu(Submenu::new("Window", window_menu)) &MenuItemBuilder::with_id(MenuEvent::NewWindow, "New Window")
.accelerator("CmdOrCtrl+Shift+N")
.build(app)?,
)
.close_window()
.fullscreen()
.item(
&MenuItemBuilder::with_id(MenuEvent::ReloadWebview, "Reload Webview")
.accelerator("CmdOrCtrl+Shift+R")
.build(app)?,
)
.build()?;
let menu = MenuBuilder::new(app)
.item(&app_menu)
.item(&file_menu)
.item(&edit_menu)
.item(&view_menu)
.item(&window_menu)
.build()?;
for event in LIBRARY_LOCKED_MENU_IDS {
set_enabled(&menu, *event, false);
}
Ok(menu)
}
} }
pub fn handle_menu_event(event: WindowMenuEvent<Wry>) { pub fn handle_menu_event(event: MenuEvent, app: &AppHandle) {
match event.menu_item_id() { let webview = app
"quit" => { .get_webview_window("main")
let app = event.window().app_handle(); .expect("unable to find window");
app.exit(0);
}
"reload_explorer" => event.window().emit("keybind", "reload_explorer").unwrap(),
"open_settings" => event.window().emit("keybind", "open_settings").unwrap(),
"open_overview" => event.window().emit("keybind", "open_overview").unwrap(),
"close_window" => {
#[cfg(target_os = "macos")]
tauri::AppHandle::hide(&event.window().app_handle()).unwrap();
#[cfg(not(target_os = "macos"))] match event {
{ // TODO: Use Tauri Specta with frontend instead of this
let window = event.window(); MenuEvent::NewLibrary => webview.emit("keybind", "new_library").unwrap(),
MenuEvent::NewFile => webview.emit("keybind", "new_file").unwrap(),
#[cfg(debug_assertions)] MenuEvent::NewDirectory => webview.emit("keybind", "new_directory").unwrap(),
if window.is_devtools_open() { MenuEvent::AddLocation => webview.emit("keybind", "add_location").unwrap(),
window.close_devtools(); MenuEvent::OpenOverview => webview.emit("keybind", "open_overview").unwrap(),
} else { MenuEvent::OpenSearch => webview.emit("keybind", "open_search".to_string()).unwrap(),
window.close().unwrap(); MenuEvent::OpenSettings => webview.emit("keybind", "open_settings").unwrap(),
} MenuEvent::ReloadExplorer => webview.emit("keybind", "reload_explorer").unwrap(),
MenuEvent::SetLayoutGrid => webview.emit("keybind", "set_layout_grid").unwrap(),
#[cfg(not(debug_assertions))] MenuEvent::SetLayoutList => webview.emit("keybind", "set_layout_list").unwrap(),
window.close().unwrap(); MenuEvent::SetLayoutMedia => webview.emit("keybind", "set_layout_media").unwrap(),
MenuEvent::ToggleDeveloperTools =>
{
#[cfg(feature = "devtools")]
if webview.is_devtools_open() {
webview.close_devtools();
} else {
webview.open_devtools();
} }
} }
"open_search" => event MenuEvent::NewWindow => {
.window() // TODO: Implement this
.emit("keybind", "open_search".to_string()) }
.unwrap(), MenuEvent::ReloadWebview => {
"reload_app" => { webview
event
.window()
.with_webview(crate::reload_webview_inner) .with_webview(crate::reload_webview_inner)
.expect("Error while reloading webview"); .expect("Error while reloading webview");
} }
#[cfg(debug_assertions)]
"toggle_devtools" => {
let window = event.window();
if window.is_devtools_open() {
window.close_devtools();
} else {
window.open_devtools();
}
}
_ => {}
} }
} }
/// If any are explicitly marked with `.disabled()` in the `custom_menu_bar()` function, this won't have an effect. // Enable/disable all items in `LIBRARY_LOCKED_MENU_IDS`
/// We include them in the locked menu IDs anyway for future-proofing, in-case someone forgets. pub fn refresh_menu_bar(app: &AppHandle, enabled: bool) {
#[cfg(target_os = "macos")] let menu = app
pub fn set_library_locked_menu_items_enabled(handle: tauri::window::MenuHandle, enabled: bool) { .get_window("main")
LIBRARY_LOCKED_MENU_IDS .expect("unable to find window")
.iter() .menu()
.try_for_each(|id| handle.get_item(id).set_enabled(enabled)) .expect("unable to get menu for current window");
.expect("Unable to disable menu items (there are no libraries present, so certain options should be hidden)");
for event in LIBRARY_LOCKED_MENU_IDS {
set_enabled(&menu, *event, enabled);
}
}
pub fn set_enabled(menu: &Menu<Wry>, event: MenuEvent, enabled: bool) {
let result = match menu.get(event.as_ref()) {
Some(MenuItemKind::MenuItem(i)) => i.set_enabled(enabled),
Some(MenuItemKind::Submenu(i)) => i.set_enabled(enabled),
Some(MenuItemKind::Predefined(_)) => return,
Some(MenuItemKind::Check(i)) => i.set_enabled(enabled),
Some(MenuItemKind::Icon(i)) => i.set_enabled(enabled),
None => {
error!("Unable to get menu item: {event:?}");
return;
}
};
if let Err(e) = result {
error!("Error setting menu item state: {e:#?}");
}
} }

View file

@ -18,7 +18,7 @@ use hyper::server::{accept::Accept, conn::AddrIncoming};
use rand::{distributions::Alphanumeric, Rng}; use rand::{distributions::Alphanumeric, Rng};
use sd_core::{custom_uri, Node, NodeError}; use sd_core::{custom_uri, Node, NodeError};
use serde::Deserialize; use serde::Deserialize;
use tauri::{async_runtime::block_on, plugin::TauriPlugin, RunEvent, Runtime}; use tauri::{async_runtime::block_on, plugin::TauriPlugin, AppHandle, Manager, RunEvent, Runtime};
use thiserror::Error; use thiserror::Error;
use tokio::{net::TcpListener, task::block_in_place}; use tokio::{net::TcpListener, task::block_in_place};
use tracing::info; use tracing::info;
@ -51,6 +51,7 @@ pub enum SdServerPluginError {
/// We also spin up multiple servers so we can load balance image requests between them to avoid any issue with browser connection limits. /// We also spin up multiple servers so we can load balance image requests between them to avoid any issue with browser connection limits.
pub async fn sd_server_plugin<R: Runtime>( pub async fn sd_server_plugin<R: Runtime>(
node: Arc<Node>, node: Arc<Node>,
app_handle: &AppHandle,
) -> Result<TauriPlugin<R>, SdServerPluginError> { ) -> Result<TauriPlugin<R>, SdServerPluginError> {
let auth_token: String = rand::thread_rng() let auth_token: String = rand::thread_rng()
.sample_iter(&Alphanumeric) .sample_iter(&Alphanumeric)
@ -95,15 +96,21 @@ pub async fn sd_server_plugin<R: Runtime>(
.expect("Error with HTTP server!"); // TODO: Panic handling .expect("Error with HTTP server!"); // TODO: Panic handling
}); });
let script = format!(
r#"window.__SD_CUSTOM_SERVER_AUTH_TOKEN__ = "{auth_token}"; window.__SD_CUSTOM_URI_SERVER__ = [{}];"#,
[listen_addra, listen_addrb, listen_addrc, listen_addrd]
.iter()
.map(|addr| format!("'http://{addr}'"))
.collect::<Vec<_>>()
.join(","),
);
for (_, window) in app_handle.webview_windows() {
window.eval(&script).ok();
}
Ok(tauri::plugin::Builder::new("sd-server") Ok(tauri::plugin::Builder::new("sd-server")
.js_init_script(format!( .js_init_script(script)
r#"window.__SD_CUSTOM_SERVER_AUTH_TOKEN__ = "{auth_token}"; window.__SD_CUSTOM_URI_SERVER__ = [{}];"#,
[listen_addra, listen_addrb, listen_addrc, listen_addrd]
.iter()
.map(|addr| format!("'http://{addr}'"))
.collect::<Vec<_>>()
.join(","),
))
.on_event(move |_app, e| { .on_event(move |_app, e| {
if let RunEvent::Exit { .. } = e { if let RunEvent::Exit { .. } = e {
block_in_place(|| { block_in_place(|| {

View file

@ -1,17 +1,16 @@
use tauri::{plugin::TauriPlugin, Manager, Runtime}; use tauri::{plugin::TauriPlugin, Manager, Runtime};
use tauri_plugin_updater::{Update as TauriPluginUpdate, UpdaterExt};
use tokio::sync::Mutex; use tokio::sync::Mutex;
#[derive(Debug, Clone, specta::Type, serde::Serialize)] #[derive(Debug, Clone, specta::Type, serde::Serialize)]
pub struct Update { pub struct Update {
pub version: String, pub version: String,
pub body: Option<String>,
} }
impl Update { impl Update {
fn new(update: &tauri::updater::UpdateResponse<impl tauri::Runtime>) -> Self { fn new(update: &TauriPluginUpdate) -> Self {
Self { Self {
version: update.latest_version().to_string(), version: update.version.clone(),
body: update.body().map(ToString::to_string),
} }
} }
} }
@ -21,12 +20,12 @@ pub struct State {
install_lock: Mutex<()>, install_lock: Mutex<()>,
} }
async fn get_update( async fn get_update(app: tauri::AppHandle) -> Result<Option<TauriPluginUpdate>, String> {
app: tauri::AppHandle, app.updater_builder()
) -> Result<tauri::updater::UpdateResponse<impl tauri::Runtime>, String> {
tauri::updater::builder(app)
.header("X-Spacedrive-Version", "stable") .header("X-Spacedrive-Version", "stable")
.map_err(|e| e.to_string())? .map_err(|e| e.to_string())?
.build()
.map_err(|e| e.to_string())?
.check() .check()
.await .await
.map_err(|e| e.to_string()) .map_err(|e| e.to_string())
@ -45,19 +44,19 @@ pub enum UpdateEvent {
#[tauri::command] #[tauri::command]
#[specta::specta] #[specta::specta]
pub async fn check_for_update(app: tauri::AppHandle) -> Result<Option<Update>, String> { pub async fn check_for_update(app: tauri::AppHandle) -> Result<Option<Update>, String> {
app.emit_all("updater", UpdateEvent::Loading).ok(); app.emit("updater", UpdateEvent::Loading).ok();
let update = match get_update(app.clone()).await { let update = match get_update(app.clone()).await {
Ok(update) => update, Ok(update) => update,
Err(e) => { Err(e) => {
app.emit_all("updater", UpdateEvent::Error(e.clone())).ok(); app.emit("updater", UpdateEvent::Error(e.clone())).ok();
return Err(e); return Err(e);
} }
}; };
let update = update.is_update_available().then(|| Update::new(&update)); let update = update.map(|update| Update::new(&update));
app.emit_all( app.emit(
"updater", "updater",
update update
.clone() .clone()
@ -81,11 +80,12 @@ pub async fn install_update(
Err(_) => return Err("Update already installing".into()), Err(_) => return Err("Update already installing".into()),
}; };
app.emit_all("updater", UpdateEvent::Installing).ok(); app.emit("updater", UpdateEvent::Installing).ok();
get_update(app.clone()) get_update(app.clone())
.await? .await?
.download_and_install() .ok_or_else(|| "No update required".to_string())?
.download_and_install(|_, _| {}, || {})
.await .await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;

View file

@ -1,88 +1,16 @@
{ {
"package": { "productName": "Spacedrive",
"productName": "Spacedrive" "version": "0.2.13",
}, "identifier": "com.spacedrive.desktop",
"build": { "build": {
"distDir": "../dist",
"devPath": "http://localhost:8001",
"beforeDevCommand": "pnpm dev", "beforeDevCommand": "pnpm dev",
"beforeBuildCommand": "pnpm turbo run build --filter=@sd/desktop..." "devUrl": "http://localhost:8001",
"beforeBuildCommand": "pnpm turbo run build --filter=@sd/desktop...",
"frontendDist": "../dist"
}, },
"tauri": { "app": {
"withGlobalTauri": true,
"macOSPrivateApi": true, "macOSPrivateApi": true,
"bundle": {
"active": true,
"publisher": "Spacedrive Technology Inc.",
"category": "Productivity",
"targets": ["deb", "msi", "dmg", "updater"],
"identifier": "com.spacedrive.desktop",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"resources": {},
"externalBin": [],
"copyright": "Spacedrive Technology Inc.",
"shortDescription": "Spacedrive",
"longDescription": "Cross-platform universal file explorer, powered by an open-source virtual distributed filesystem.",
"deb": {
"files": {
"/usr/share/spacedrive/models/yolov8s.onnx": "../../.deps/models/yolov8s.onnx"
},
"depends": ["libc6"]
},
"macOS": {
"minimumSystemVersion": "10.15",
"exceptionDomain": null,
"entitlements": null,
"frameworks": ["../../.deps/Spacedrive.framework"]
},
"windows": {
"certificateThumbprint": null,
"webviewInstallMode": { "type": "embedBootstrapper", "silent": true },
"digestAlgorithm": "sha256",
"timestampUrl": "",
"wix": {
"dialogImagePath": "icons/WindowsDialogImage.bmp",
"bannerPath": "icons/WindowsBanner.bmp"
}
}
},
"updater": {
"active": true,
"dialog": false,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEZBMURCMkU5NEU3NDAyOEMKUldTTUFuUk82YklkK296dlkxUGkrTXhCT3ZMNFFVOWROcXNaS0RqWU1kMUdRV2tDdFdIS0Y3YUsK",
"endpoints": [
"https://spacedrive.com/api/releases/tauri/{{version}}/{{target}}/{{arch}}"
]
},
"allowlist": {
"all": false,
"window": {
"all": true
},
"path": {
"all": true
},
"shell": {
"all": true
},
"protocol": {
"all": true,
"assetScope": ["*"]
},
"os": {
"all": true
},
"dialog": {
"all": true,
"open": true,
"save": true
}
},
"windows": [ "windows": [
{ {
"title": "Spacedrive", "title": "Spacedrive",
@ -96,14 +24,65 @@
"alwaysOnTop": false, "alwaysOnTop": false,
"focus": false, "focus": false,
"visible": false, "visible": false,
"fileDropEnabled": true, "dragDropEnabled": true,
"decorations": true, "decorations": true,
"transparent": true, "transparent": true,
"center": true "center": true
} }
], ],
"security": { "security": {
"csp": "default-src spacedrive: webkit-pdfjs-viewer: asset: https://asset.localhost blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'" "csp": "default-src webkit-pdfjs-viewer: asset: https://asset.localhost blob: data: filesystem: ws: wss: http: https: tauri: 'unsafe-eval' 'unsafe-inline' 'self' img-src: 'self'"
}
},
"bundle": {
"active": true,
"targets": ["deb", "msi", "dmg", "updater"],
"publisher": "Spacedrive Technology Inc.",
"copyright": "Spacedrive Technology Inc.",
"category": "Productivity",
"shortDescription": "Spacedrive",
"longDescription": "Cross-platform universal file explorer, powered by an open-source virtual distributed filesystem.",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"linux": {
"deb": {
"files": {
"/usr/share/spacedrive/models/yolov8s.onnx": "../../.deps/models/yolov8s.onnx"
},
"depends": ["libc6", "libxdo3"]
}
},
"macOS": {
"minimumSystemVersion": "10.15",
"exceptionDomain": null,
"entitlements": null,
"frameworks": ["../../.deps/Spacedrive.framework"]
},
"windows": {
"certificateThumbprint": null,
"webviewInstallMode": { "type": "embedBootstrapper", "silent": true },
"digestAlgorithm": "sha256",
"timestampUrl": "",
"wix": {
"dialogImagePath": "icons/WindowsDialogImage.bmp",
"bannerPath": "icons/WindowsBanner.bmp"
}
}
},
"plugins": {
"updater": {
"active": true,
"dialog": false,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEZBMURCMkU5NEU3NDAyOEMKUldTTUFuUk82YklkK296dlkxUGkrTXhCT3ZMNFFVOWROcXNaS0RqWU1kMUdRV2tDdFdIS0Y3YUsK",
"endpoints": [
"https://spacedrive.com/api/releases/tauri/{{version}}/{{target}}/{{arch}}"
]
} }
} }
} }

View file

@ -1,52 +1,46 @@
/** tauri-specta globals **/ /** tauri-specta globals **/
import { invoke as TAURI_INVOKE } from '@tauri-apps/api'; import { invoke as TAURI_INVOKE } from '@tauri-apps/api/core';
import * as TAURI_API_EVENT from '@tauri-apps/api/event'; import * as TAURI_API_EVENT from '@tauri-apps/api/event';
import { type WebviewWindowHandle as __WebviewWindowHandle__ } from '@tauri-apps/api/window'; import { type WebviewWindow as __WebviewWindow__ } from '@tauri-apps/api/webviewWindow';
// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. // This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.
export const commands = { export const commands = {
async appReady(): Promise<null> { async appReady(): Promise<void> {
return await TAURI_INVOKE('plugin:tauri-specta|app_ready'); await TAURI_INVOKE('app_ready');
}, },
async resetSpacedrive(): Promise<null> { async resetSpacedrive(): Promise<void> {
return await TAURI_INVOKE('plugin:tauri-specta|reset_spacedrive'); await TAURI_INVOKE('reset_spacedrive');
}, },
async openLogsDir(): Promise<__Result__<null, null>> { async openLogsDir(): Promise<Result<null, null>> {
try { try {
return { status: 'ok', data: await TAURI_INVOKE('plugin:tauri-specta|open_logs_dir') }; return { status: 'ok', data: await TAURI_INVOKE('open_logs_dir') };
} catch (e) { } catch (e) {
if (e instanceof Error) throw e; if (e instanceof Error) throw e;
else return { status: 'error', error: e as any }; else return { status: 'error', error: e as any };
} }
}, },
async refreshMenuBar(): Promise<__Result__<null, null>> { async refreshMenuBar(): Promise<Result<null, null>> {
try { try {
return { return { status: 'ok', data: await TAURI_INVOKE('refresh_menu_bar') };
status: 'ok',
data: await TAURI_INVOKE('plugin:tauri-specta|refresh_menu_bar')
};
} catch (e) { } catch (e) {
if (e instanceof Error) throw e; if (e instanceof Error) throw e;
else return { status: 'error', error: e as any }; else return { status: 'error', error: e as any };
} }
}, },
async reloadWebview(): Promise<null> { async reloadWebview(): Promise<void> {
return await TAURI_INVOKE('plugin:tauri-specta|reload_webview'); await TAURI_INVOKE('reload_webview');
}, },
async setMenuBarItemState(id: string, enabled: boolean): Promise<null> { async setMenuBarItemState(event: MenuEvent, enabled: boolean): Promise<void> {
return await TAURI_INVOKE('plugin:tauri-specta|set_menu_bar_item_state', { id, enabled }); await TAURI_INVOKE('set_menu_bar_item_state', { event, enabled });
}, },
async requestFdaMacos(): Promise<null> { async requestFdaMacos(): Promise<void> {
return await TAURI_INVOKE('plugin:tauri-specta|request_fda_macos'); await TAURI_INVOKE('request_fda_macos');
}, },
async openTrashInOsExplorer(): Promise<__Result__<null, null>> { async openTrashInOsExplorer(): Promise<Result<null, null>> {
try { try {
return { return { status: 'ok', data: await TAURI_INVOKE('open_trash_in_os_explorer') };
status: 'ok',
data: await TAURI_INVOKE('plugin:tauri-specta|open_trash_in_os_explorer')
};
} catch (e) { } catch (e) {
if (e instanceof Error) throw e; if (e instanceof Error) throw e;
else return { status: 'error', error: e as any }; else return { status: 'error', error: e as any };
@ -55,36 +49,17 @@ export const commands = {
async openFilePaths( async openFilePaths(
library: string, library: string,
ids: number[] ids: number[]
): Promise< ): Promise<Result<OpenFilePathResult[], null>> {
__Result__<
(
| { t: 'NoLibrary' }
| { t: 'NoFile'; c: number }
| { t: 'OpenError'; c: [number, string] }
| { t: 'AllGood'; c: number }
| { t: 'Internal'; c: string }
)[],
null
>
> {
try { try {
return { return { status: 'ok', data: await TAURI_INVOKE('open_file_paths', { library, ids }) };
status: 'ok',
data: await TAURI_INVOKE('plugin:tauri-specta|open_file_paths', { library, ids })
};
} catch (e) { } catch (e) {
if (e instanceof Error) throw e; if (e instanceof Error) throw e;
else return { status: 'error', error: e as any }; else return { status: 'error', error: e as any };
} }
}, },
async openEphemeralFiles( async openEphemeralFiles(paths: string[]): Promise<Result<EphemeralFileOpenResult[], null>> {
paths: string[]
): Promise<__Result__<({ t: 'Ok'; c: string } | { t: 'Err'; c: string })[], null>> {
try { try {
return { return { status: 'ok', data: await TAURI_INVOKE('open_ephemeral_files', { paths }) };
status: 'ok',
data: await TAURI_INVOKE('plugin:tauri-specta|open_ephemeral_files', { paths })
};
} catch (e) { } catch (e) {
if (e instanceof Error) throw e; if (e instanceof Error) throw e;
else return { status: 'error', error: e as any }; else return { status: 'error', error: e as any };
@ -93,14 +68,11 @@ export const commands = {
async getFilePathOpenWithApps( async getFilePathOpenWithApps(
library: string, library: string,
ids: number[] ids: number[]
): Promise<__Result__<{ url: string; name: string }[], null>> { ): Promise<Result<OpenWithApplication[], null>> {
try { try {
return { return {
status: 'ok', status: 'ok',
data: await TAURI_INVOKE('plugin:tauri-specta|get_file_path_open_with_apps', { data: await TAURI_INVOKE('get_file_path_open_with_apps', { library, ids })
library,
ids
})
}; };
} catch (e) { } catch (e) {
if (e instanceof Error) throw e; if (e instanceof Error) throw e;
@ -109,13 +81,11 @@ export const commands = {
}, },
async getEphemeralFilesOpenWithApps( async getEphemeralFilesOpenWithApps(
paths: string[] paths: string[]
): Promise<__Result__<{ url: string; name: string }[], null>> { ): Promise<Result<OpenWithApplication[], null>> {
try { try {
return { return {
status: 'ok', status: 'ok',
data: await TAURI_INVOKE('plugin:tauri-specta|get_ephemeral_files_open_with_apps', { data: await TAURI_INVOKE('get_ephemeral_files_open_with_apps', { paths })
paths
})
}; };
} catch (e) { } catch (e) {
if (e instanceof Error) throw e; if (e instanceof Error) throw e;
@ -125,63 +95,50 @@ export const commands = {
async openFilePathWith( async openFilePathWith(
library: string, library: string,
fileIdsAndUrls: [number, string][] fileIdsAndUrls: [number, string][]
): Promise<__Result__<null, null>> { ): Promise<Result<null, null>> {
try { try {
return { return {
status: 'ok', status: 'ok',
data: await TAURI_INVOKE('plugin:tauri-specta|open_file_path_with', { data: await TAURI_INVOKE('open_file_path_with', { library, fileIdsAndUrls })
library,
fileIdsAndUrls
})
}; };
} catch (e) { } catch (e) {
if (e instanceof Error) throw e; if (e instanceof Error) throw e;
else return { status: 'error', error: e as any }; else return { status: 'error', error: e as any };
} }
}, },
async openEphemeralFileWith(pathsAndUrls: [string, string][]): Promise<__Result__<null, null>> { async openEphemeralFileWith(pathsAndUrls: [string, string][]): Promise<Result<null, null>> {
try { try {
return { return {
status: 'ok', status: 'ok',
data: await TAURI_INVOKE('plugin:tauri-specta|open_ephemeral_file_with', { data: await TAURI_INVOKE('open_ephemeral_file_with', { pathsAndUrls })
pathsAndUrls
})
}; };
} catch (e) { } catch (e) {
if (e instanceof Error) throw e; if (e instanceof Error) throw e;
else return { status: 'error', error: e as any }; else return { status: 'error', error: e as any };
} }
}, },
async revealItems(library: string, items: RevealItem[]): Promise<__Result__<null, null>> { async revealItems(library: string, items: RevealItem[]): Promise<Result<null, null>> {
try { try {
return { return { status: 'ok', data: await TAURI_INVOKE('reveal_items', { library, items }) };
status: 'ok',
data: await TAURI_INVOKE('plugin:tauri-specta|reveal_items', { library, items })
};
} catch (e) { } catch (e) {
if (e instanceof Error) throw e; if (e instanceof Error) throw e;
else return { status: 'error', error: e as any }; else return { status: 'error', error: e as any };
} }
}, },
async lockAppTheme(themeType: AppThemeType): Promise<null> { async lockAppTheme(themeType: AppThemeType): Promise<void> {
return await TAURI_INVOKE('plugin:tauri-specta|lock_app_theme', { themeType }); await TAURI_INVOKE('lock_app_theme', { themeType });
}, },
async checkForUpdate(): Promise< async checkForUpdate(): Promise<Result<Update | null, string>> {
__Result__<{ version: string; body: string | null } | null, string>
> {
try { try {
return { return { status: 'ok', data: await TAURI_INVOKE('check_for_update') };
status: 'ok',
data: await TAURI_INVOKE('plugin:tauri-specta|check_for_update')
};
} catch (e) { } catch (e) {
if (e instanceof Error) throw e; if (e instanceof Error) throw e;
else return { status: 'error', error: e as any }; else return { status: 'error', error: e as any };
} }
}, },
async installUpdate(): Promise<__Result__<null, string>> { async installUpdate(): Promise<Result<null, string>> {
try { try {
return { status: 'ok', data: await TAURI_INVOKE('plugin:tauri-specta|install_update') }; return { status: 'ok', data: await TAURI_INVOKE('install_update') };
} catch (e) { } catch (e) {
if (e instanceof Error) throw e; if (e instanceof Error) throw e;
else return { status: 'error', error: e as any }; else return { status: 'error', error: e as any };
@ -192,7 +149,7 @@ export const commands = {
export const events = __makeEvents__<{ export const events = __makeEvents__<{
dragAndDropEvent: DragAndDropEvent; dragAndDropEvent: DragAndDropEvent;
}>({ }>({
dragAndDropEvent: 'plugin:tauri-specta:drag-and-drop-event' dragAndDropEvent: 'drag-and-drop-event'
}); });
/** user-defined types **/ /** user-defined types **/
@ -202,10 +159,34 @@ export type DragAndDropEvent =
| { type: 'Hovered'; paths: string[]; x: number; y: number } | { type: 'Hovered'; paths: string[]; x: number; y: number }
| { type: 'Dropped'; paths: string[]; x: number; y: number } | { type: 'Dropped'; paths: string[]; x: number; y: number }
| { type: 'Cancelled' }; | { type: 'Cancelled' };
export type EphemeralFileOpenResult = { t: 'Ok'; c: string } | { t: 'Err'; c: string };
export type MenuEvent =
| 'NewLibrary'
| 'NewFile'
| 'NewDirectory'
| 'AddLocation'
| 'OpenOverview'
| 'OpenSearch'
| 'OpenSettings'
| 'ReloadExplorer'
| 'SetLayoutGrid'
| 'SetLayoutList'
| 'SetLayoutMedia'
| 'ToggleDeveloperTools'
| 'NewWindow'
| 'ReloadWebview';
export type OpenFilePathResult =
| { t: 'NoLibrary' }
| { t: 'NoFile'; c: number }
| { t: 'OpenError'; c: [number, string] }
| { t: 'AllGood'; c: number }
| { t: 'Internal'; c: string };
export type OpenWithApplication = { url: string; name: string };
export type RevealItem = export type RevealItem =
| { Location: { id: number } } | { Location: { id: number } }
| { FilePath: { id: number } } | { FilePath: { id: number } }
| { Ephemeral: { path: string } }; | { Ephemeral: { path: string } };
export type Update = { version: string };
type __EventObj__<T> = { type __EventObj__<T> = {
listen: (cb: TAURI_API_EVENT.EventCallback<T>) => ReturnType<typeof TAURI_API_EVENT.listen<T>>; listen: (cb: TAURI_API_EVENT.EventCallback<T>) => ReturnType<typeof TAURI_API_EVENT.listen<T>>;
@ -215,13 +196,13 @@ type __EventObj__<T> = {
: (payload: T) => ReturnType<typeof TAURI_API_EVENT.emit>; : (payload: T) => ReturnType<typeof TAURI_API_EVENT.emit>;
}; };
type __Result__<T, E> = { status: 'ok'; data: T } | { status: 'error'; error: E }; export type Result<T, E> = { status: 'ok'; data: T } | { status: 'error'; error: E };
function __makeEvents__<T extends Record<string, any>>(mappings: Record<keyof T, string>) { function __makeEvents__<T extends Record<string, any>>(mappings: Record<keyof T, string>) {
return new Proxy( return new Proxy(
{} as unknown as { {} as unknown as {
[K in keyof T]: __EventObj__<T[K]> & { [K in keyof T]: __EventObj__<T[K]> & {
(handle: __WebviewWindowHandle__): __EventObj__<T[K]>; (handle: __WebviewWindow__): __EventObj__<T[K]>;
}; };
}, },
{ {
@ -229,7 +210,7 @@ function __makeEvents__<T extends Record<string, any>>(mappings: Record<keyof T,
const name = mappings[event as keyof T]; const name = mappings[event as keyof T];
return new Proxy((() => {}) as any, { return new Proxy((() => {}) as any, {
apply: (_, __, [window]: [__WebviewWindowHandle__]) => ({ apply: (_, __, [window]: [__WebviewWindow__]) => ({
listen: (arg: any) => window.listen(name, arg), listen: (arg: any) => window.listen(name, arg),
once: (arg: any) => window.once(name, arg), once: (arg: any) => window.once(name, arg),
emit: (arg: any) => window.emit(name, arg) emit: (arg: any) => window.emit(name, arg)

View file

@ -1,7 +1,8 @@
import { dialog, invoke, os, shell } from '@tauri-apps/api'; import { invoke } from '@tauri-apps/api/core';
import { confirm } from '@tauri-apps/api/dialog';
import { homeDir } from '@tauri-apps/api/path'; import { homeDir } from '@tauri-apps/api/path';
import { open } from '@tauri-apps/api/shell'; import { confirm, open as dialogOpen, save as dialogSave } from '@tauri-apps/plugin-dialog';
import { type } from '@tauri-apps/plugin-os';
import { open as shellOpen } from '@tauri-apps/plugin-shell';
// @ts-expect-error: Doesn't have a types package. // @ts-expect-error: Doesn't have a types package.
import ConsistentHash from 'consistent-hash'; import ConsistentHash from 'consistent-hash';
import { OperatingSystem, Platform } from '@sd/interface'; import { OperatingSystem, Platform } from '@sd/interface';
@ -16,12 +17,12 @@ const customUriServerUrl = (window as any).__SD_CUSTOM_URI_SERVER__ as string[]
const queryParams = customUriAuthToken ? `?token=${encodeURIComponent(customUriAuthToken)}` : ''; const queryParams = customUriAuthToken ? `?token=${encodeURIComponent(customUriAuthToken)}` : '';
async function getOs(): Promise<OperatingSystem> { async function getOs(): Promise<OperatingSystem> {
switch (await os.type()) { switch (await type()) {
case 'Linux': case 'linux':
return 'linux'; return 'linux';
case 'Windows_NT': case 'windows':
return 'windows'; return 'windows';
case 'Darwin': case 'macos':
return 'macOS'; return 'macOS';
default: default:
return 'unknown'; return 'unknown';
@ -64,15 +65,18 @@ export const platform = {
constructServerUrl( constructServerUrl(
`/remote/${encodeURIComponent(remote_identity)}/uri/${path}?token=${customUriAuthToken}` `/remote/${encodeURIComponent(remote_identity)}/uri/${path}?token=${customUriAuthToken}`
), ),
openLink: shell.open, openLink: shellOpen,
getOs, getOs,
openDirectoryPickerDialog: (opts) => { openDirectoryPickerDialog: (opts) => {
const result = dialog.open({ directory: true, ...opts }); const result = dialogOpen({ directory: true, ...opts });
if (opts?.multiple) return result as any; // Tauri don't properly type narrow on `multiple` argument if (opts?.multiple) return result as any; // Tauri don't properly type narrow on `multiple` argument
return result; return result;
}, },
openFilePickerDialog: () => dialog.open(), openFilePickerDialog: () =>
saveFilePickerDialog: (opts) => dialog.save(opts), dialogOpen({
multiple: true
}).then((result) => result?.map((r) => r.path) ?? null),
saveFilePickerDialog: (opts) => dialogSave(opts),
showDevtools: () => invoke('show_devtools'), showDevtools: () => invoke('show_devtools'),
confirm: (msg, cb) => confirm(msg).then(cb), confirm: (msg, cb) => confirm(msg).then(cb),
subscribeToDragAndDropEvents: (cb) => subscribeToDragAndDropEvents: (cb) =>

View file

@ -12,6 +12,7 @@ export type Platform = {
version?: string; version?: string;
links?: Array<{ name: string; arch: string }>; links?: Array<{ name: string; arch: string }>;
disabled?: boolean; disabled?: boolean;
note?: string;
}; };
export const platforms = { export const platforms = {
@ -37,7 +38,8 @@ export const platforms = {
os: 'linux', os: 'linux',
icon: LinuxLogo, icon: LinuxLogo,
version: 'deb', version: 'deb',
links: [{ name: 'x86_64', arch: 'x86_64' }] links: [{ name: 'x86_64', arch: 'x86_64' }],
note: 'Supports Ubuntu 22.04+, Debian Bookworm+, Linux Mint 21+, PopOS 22.04+'
}, },
docker: { name: 'Docker', icon: Docker }, docker: { name: 'Docker', icon: Docker },
android: { name: 'Android', icon: AndroidLogo, version: '10+', disabled: true }, android: { name: 'Android', icon: AndroidLogo, version: '10+', disabled: true },

View file

@ -22,13 +22,17 @@ export function Downloads({ latestVersion }: Props) {
const plausible = usePlausible(); const plausible = usePlausible();
const formattedVersion = (() => { const [formattedVersion, note] = (() => {
const platform = selectedPlatform ?? currentPlatform; const platform = selectedPlatform ?? currentPlatform;
return platform
if (!platform?.version) return; ? [
if (platform.name === 'Linux') return platform.version; platform.version &&
(platform.name === 'Linux'
return `${platform.name} ${platform.version}`; ? platform.version
: `${platform.name} ${platform.version}`),
platform.note
]
: [];
})(); })();
return ( return (
@ -95,6 +99,12 @@ export function Downloads({ latestVersion }: Props) {
{formattedVersion} {formattedVersion}
</> </>
)} )}
{note && (
<>
<span className="mx-2 opacity-50">|</span>
{note}
</>
)}
</p> </p>
{/* Platform icons */} {/* Platform icons */}
<div className="relative z-10 mt-5 flex gap-3"> <div className="relative z-10 mt-5 flex gap-3">

View file

@ -399,11 +399,11 @@ impl IndexerRuler {
} }
} }
pub async fn serialize(&self) -> Result<Vec<u8>, rmp_serde::encode::Error> { pub async fn serialize(&self) -> Result<Vec<u8>, encode::Error> {
rmp_serde::to_vec_named(&*self.rules.read().await) rmp_serde::to_vec_named(&*self.rules.read().await)
} }
pub fn deserialize(data: &[u8]) -> Result<Self, rmp_serde::decode::Error> { pub fn deserialize(data: &[u8]) -> Result<Self, decode::Error> {
rmp_serde::from_slice(data).map(Self::new) rmp_serde::from_slice(data).map(Self::new)
} }

View file

@ -28,7 +28,7 @@ pub(crate) fn mount() -> AlphaRouter<Ctx> {
.procedure("setApiOrigin", { .procedure("setApiOrigin", {
R.mutation(|node, origin: String| async move { R.mutation(|node, origin: String| async move {
let mut origin_env = node.env.api_url.lock().await; let mut origin_env = node.env.api_url.lock().await;
*origin_env = origin.clone(); origin_env.clone_from(&origin);
node.config node.config
.write(|c| { .write(|c| {

View file

@ -214,7 +214,7 @@ pub(crate) fn mount() -> Arc<Router> {
type_map, type_map,
); );
type_map.insert( type_map.insert(
<sd_prisma::prisma::object::Data as specta::NamedType>::SID, <sd_prisma::prisma::object::Data as specta::NamedType>::sid(),
def, def,
); );
}); });

View file

@ -321,7 +321,7 @@ impl Worker {
old.task_count = report.task_count; old.task_count = report.task_count;
old.completed_task_count = report.completed_task_count; old.completed_task_count = report.completed_task_count;
old.estimated_completion = report.estimated_completion; old.estimated_completion = report.estimated_completion;
old.message = report.message.clone(); old.message.clone_from(&report.message);
}); });
*last_report_watch_update = Instant::now(); *last_report_watch_update = Instant::now();
} }

View file

@ -3,7 +3,7 @@ use std::pin::pin;
use async_stream::stream; use async_stream::stream;
use futures::{Stream, StreamExt}; use futures::{Stream, StreamExt};
use serde::Serialize; use serde::Serialize;
use specta::{reference::Reference, DataType, Type, TypeMap}; use specta::{reference::Reference, DataType, Generics, Type, TypeMap};
#[derive(Serialize)] #[derive(Serialize)]
#[serde(untagged)] #[serde(untagged)]
@ -13,14 +13,10 @@ pub enum Output<T> {
} }
impl<T: Type> Type for Output<T> { impl<T: Type> Type for Output<T> {
fn inline(type_map: &mut TypeMap, generics: &[DataType]) -> DataType { fn inline(type_map: &mut TypeMap, generics: Generics) -> DataType {
T::inline(type_map, generics) T::inline(type_map, generics)
} }
fn definition(type_map: &mut TypeMap) -> DataType {
T::definition(type_map)
}
fn reference(type_map: &mut TypeMap, generics: &[DataType]) -> Reference { fn reference(type_map: &mut TypeMap, generics: &[DataType]) -> Reference {
T::reference(type_map, generics) T::reference(type_map, generics)
} }

View file

@ -91,11 +91,12 @@ uuid = { version = "1.7.0", features = ["v4"] }
linux-keyutils = { version = "0.2.4", features = ["std"], optional = true } linux-keyutils = { version = "0.2.4", features = ["std"], optional = true }
secret-service = { version = "3.0.1", features = [ secret-service = { version = "3.0.1", features = [
"crypto-rust", "crypto-rust",
"rt-tokio-crypto-rust",
], optional = true } ], optional = true }
# this needs to remain at versions < 4, as they made some changes and i can't get it # this needs to remain at versions < 4, as they made some changes and i can't get it
# to compile for the time being # to compile for the time being
zbus = { version = "3.15.2", default_features = false, features = [ zbus = { version = "4.0", default_features = false, features = [
"tokio", "tokio",
"blocking", "blocking",
], optional = true } ], optional = true }

View file

@ -37,7 +37,7 @@ pub enum FlashMode {
/// The data is present, but we're unable to determine what they mean /// The data is present, but we're unable to determine what they mean
#[default] #[default]
Unknown, Unknown,
/// FLash was on /// `FLash` was on
On, On,
/// Flash was off /// Flash was off
Off, Off,

View file

@ -35,6 +35,11 @@ export default ({ redirectToSearch, defaultFilters, defaultTarget }: Props) => {
) { ) {
searchRef.current?.focus(); searchRef.current?.focus();
} }
const handler = () => searchRef.current?.focus();
document.addEventListener('open_search', handler);
return () => document.removeEventListener('open_search', handler);
}, },
[os] [os]
); );

View file

@ -19,6 +19,18 @@ export const useKeybindEventHandler = (libraryId?: string) => {
e.preventDefault(); e.preventDefault();
switch (e.detail.action) { switch (e.detail.action) {
case 'new_library':
console.log('New Library!'); // TODO: Implement
break;
case 'new_file':
console.log('New File!'); // TODO: Implement
break;
case 'new_directory':
console.log('New Directory!'); // TODO: Implement
break;
case 'add_location':
console.log('Add Location!'); // TODO: Implement
break;
case 'open_settings': case 'open_settings':
libraryId && navigate(`/${libraryId}/settings/client/general`); libraryId && navigate(`/${libraryId}/settings/client/general`);
break; break;
@ -29,7 +41,7 @@ export const useKeybindEventHandler = (libraryId?: string) => {
// libraryId && navigate(`/${libraryId}/overview`); // libraryId && navigate(`/${libraryId}/overview`);
// break; // break;
case 'open_search': case 'open_search':
// somehow emit ctrl/cmd+f document.dispatchEvent(new CustomEvent('open_search'));
break; break;
case 'window_fullscreened': case 'window_fullscreened':
windowState.isFullScreen = true; windowState.isFullScreen = true;

View file

@ -69,7 +69,7 @@ export type Platform = {
landingApiOrigin: string; landingApiOrigin: string;
}; };
export type Update = { version: string; body: string | null }; export type Update = { version: string };
export type UpdateStore = export type UpdateStore =
| { status: 'idle' } | { status: 'idle' }
| { status: 'loading' } | { status: 'loading' }

View file

@ -35,7 +35,8 @@
"pnpm": { "pnpm": {
"patchedDependencies": { "patchedDependencies": {
"@remix-run/router@1.13.1": "patches/@remix-run__router@1.13.1.patch", "@remix-run/router@1.13.1": "patches/@remix-run__router@1.13.1.patch",
"@contentlayer/cli@0.3.4": "patches/@contentlayer__cli@0.3.4.patch" "@contentlayer/cli@0.3.4": "patches/@contentlayer__cli@0.3.4.patch",
"@oscartbeaumont-sd/rspc-tauri@0.0.0-main-dc31e5b2": "patches/@oscartbeaumont-sd__rspc-tauri@0.0.0-main-dc31e5b2.patch"
}, },
"overrides": { "overrides": {
"@types/node": ">18.18.x", "@types/node": ">18.18.x",

View file

@ -7,11 +7,11 @@ export type Procedures = {
{ key: "backups.getAll", input: never, result: GetAll } | { key: "backups.getAll", input: never, result: GetAll } |
{ key: "buildInfo", input: never, result: BuildInfo } | { key: "buildInfo", input: never, result: BuildInfo } |
{ key: "cloud.getApiOrigin", input: never, result: string } | { key: "cloud.getApiOrigin", input: never, result: string } |
{ key: "cloud.library.get", input: LibraryArgs<null>, result: { id: string; uuid: string; name: string; instances: CloudInstance[]; ownerId: string } | null } | { key: "cloud.library.get", input: LibraryArgs<null>, result: CloudLibrary | null } |
{ key: "cloud.library.list", input: never, result: CloudLibrary[] } | { key: "cloud.library.list", input: never, result: CloudLibrary[] } |
{ key: "cloud.locations.list", input: never, result: CloudLocation[] } | { key: "cloud.locations.list", input: never, result: CloudLocation[] } |
{ key: "ephemeralFiles.getMediaData", input: string, result: ({ type: "Image" } & ImageMetadata) | ({ type: "Video" } & VideoMetadata) | ({ type: "Audio" } & AudioMetadata) | null } | { key: "ephemeralFiles.getMediaData", input: string, result: MediaMetadata | null } |
{ key: "files.get", input: LibraryArgs<number>, result: { id: number; pub_id: number[]; kind: number | null; key_id: number | null; hidden: boolean | null; favorite: boolean | null; important: boolean | null; note: string | null; date_created: string | null; date_accessed: string | null; file_paths: ({ id: number; pub_id: number[]; is_dir: boolean | null; cas_id: string | null; integrity_checksum: string | null; location_id: number | null; materialized_path: string | null; name: string | null; extension: string | null; hidden: boolean | null; size_in_bytes: string | null; size_in_bytes_bytes: number[] | null; inode: number[] | null; object_id: number | null; key_id: number | null; date_created: string | null; date_modified: string | null; date_indexed: string | null; object: { id: number; pub_id: number[]; kind: number | null; key_id: number | null; hidden: boolean | null; favorite: boolean | null; important: boolean | null; note: string | null; date_created: string | null; date_accessed: string | null; media_data: { resolution: number[] | null; media_date: number[] | null; media_location: number[] | null; camera_data: number[] | null; artist: string | null; description: string | null; copyright: string | null; exif_version: string | null } | null } | null })[] } | null } | { key: "files.get", input: LibraryArgs<number>, result: ObjectWithFilePaths2 | null } |
{ key: "files.getConvertibleImageExtensions", input: never, result: string[] } | { key: "files.getConvertibleImageExtensions", input: never, result: string[] } |
{ key: "files.getMediaData", input: LibraryArgs<number>, result: MediaMetadata } | { key: "files.getMediaData", input: LibraryArgs<number>, result: MediaMetadata } |
{ key: "files.getPath", input: LibraryArgs<number>, result: string | null } | { key: "files.getPath", input: LibraryArgs<number>, result: string | null } |
@ -19,7 +19,7 @@ export type Procedures = {
{ key: "jobs.isActive", input: LibraryArgs<null>, result: boolean } | { key: "jobs.isActive", input: LibraryArgs<null>, result: boolean } |
{ key: "jobs.reports", input: LibraryArgs<null>, result: JobGroup[] } | { key: "jobs.reports", input: LibraryArgs<null>, result: JobGroup[] } |
{ key: "labels.count", input: LibraryArgs<null>, result: number } | { key: "labels.count", input: LibraryArgs<null>, result: number } |
{ key: "labels.get", input: LibraryArgs<number>, result: { id: number; name: string; date_created: string | null; date_modified: string | null } | null } | { key: "labels.get", input: LibraryArgs<number>, result: Label | null } |
{ key: "labels.getForObject", input: LibraryArgs<number>, result: Label[] } | { key: "labels.getForObject", input: LibraryArgs<number>, result: Label[] } |
{ key: "labels.getWithObjects", input: LibraryArgs<number[]>, result: { [key in number]: { date_created: string; object: { id: number } }[] } } | { key: "labels.getWithObjects", input: LibraryArgs<number[]>, result: { [key in number]: { date_created: string; object: { id: number } }[] } } |
{ key: "labels.list", input: LibraryArgs<null>, result: Label[] } | { key: "labels.list", input: LibraryArgs<null>, result: Label[] } |
@ -27,8 +27,8 @@ export type Procedures = {
{ key: "library.kindStatistics", input: LibraryArgs<null>, result: KindStatistics } | { key: "library.kindStatistics", input: LibraryArgs<null>, result: KindStatistics } |
{ key: "library.list", input: never, result: LibraryConfigWrapped[] } | { key: "library.list", input: never, result: LibraryConfigWrapped[] } |
{ key: "library.statistics", input: LibraryArgs<null>, result: StatisticsResponse } | { key: "library.statistics", input: LibraryArgs<null>, result: StatisticsResponse } |
{ key: "locations.get", input: LibraryArgs<number>, result: { id: number; pub_id: number[]; name: string | null; path: string | null; total_capacity: number | null; available_capacity: number | null; size_in_bytes: number[] | null; is_archived: boolean | null; generate_preview_media: boolean | null; sync_preview_media: boolean | null; hidden: boolean | null; date_created: string | null; scan_state: number; instance_id: number | null } | null } | { key: "locations.get", input: LibraryArgs<number>, result: Location | null } |
{ key: "locations.getWithRules", input: LibraryArgs<number>, result: { id: number; pub_id: number[]; name: string | null; path: string | null; total_capacity: number | null; available_capacity: number | null; size_in_bytes: number[] | null; is_archived: boolean | null; generate_preview_media: boolean | null; sync_preview_media: boolean | null; hidden: boolean | null; date_created: string | null; instance_id: number | null; indexer_rules: IndexerRule[] } | null } | { key: "locations.getWithRules", input: LibraryArgs<number>, result: LocationWithIndexerRule | null } |
{ key: "locations.indexer_rules.get", input: LibraryArgs<number>, result: IndexerRule } | { key: "locations.indexer_rules.get", input: LibraryArgs<number>, result: IndexerRule } |
{ key: "locations.indexer_rules.list", input: LibraryArgs<null>, result: IndexerRule[] } | { key: "locations.indexer_rules.list", input: LibraryArgs<null>, result: IndexerRule[] } |
{ key: "locations.indexer_rules.listForLocation", input: LibraryArgs<number>, result: IndexerRule[] } | { key: "locations.indexer_rules.listForLocation", input: LibraryArgs<number>, result: IndexerRule[] } |
@ -47,11 +47,11 @@ export type Procedures = {
{ key: "search.objectsCount", input: LibraryArgs<{ filters?: SearchFilterArgs[] }>, result: number } | { key: "search.objectsCount", input: LibraryArgs<{ filters?: SearchFilterArgs[] }>, result: number } |
{ key: "search.paths", input: LibraryArgs<FilePathSearchArgs>, result: SearchData<ExplorerItem> } | { key: "search.paths", input: LibraryArgs<FilePathSearchArgs>, result: SearchData<ExplorerItem> } |
{ key: "search.pathsCount", input: LibraryArgs<{ filters?: SearchFilterArgs[] }>, result: number } | { key: "search.pathsCount", input: LibraryArgs<{ filters?: SearchFilterArgs[] }>, result: number } |
{ key: "search.saved.get", input: LibraryArgs<number>, result: { id: number; pub_id: number[]; target: string | null; search: string | null; filters: string | null; name: string | null; icon: string | null; description: string | null; date_created: string | null; date_modified: string | null } | null } | { key: "search.saved.get", input: LibraryArgs<number>, result: SavedSearch | null } |
{ key: "search.saved.list", input: LibraryArgs<null>, result: SavedSearch[] } | { key: "search.saved.list", input: LibraryArgs<null>, result: SavedSearch[] } |
{ key: "sync.enabled", input: LibraryArgs<null>, result: boolean } | { key: "sync.enabled", input: LibraryArgs<null>, result: boolean } |
{ key: "sync.messages", input: LibraryArgs<null>, result: CRDTOperation[] } | { key: "sync.messages", input: LibraryArgs<null>, result: CRDTOperation[] } |
{ key: "tags.get", input: LibraryArgs<number>, result: { id: number; pub_id: number[]; name: string | null; color: string | null; is_hidden: boolean | null; date_created: string | null; date_modified: string | null } | null } | { key: "tags.get", input: LibraryArgs<number>, result: Tag | null } |
{ key: "tags.getForObject", input: LibraryArgs<number>, result: Tag[] } | { key: "tags.getForObject", input: LibraryArgs<number>, result: Tag[] } |
{ key: "tags.getWithObjects", input: LibraryArgs<number[]>, result: { [key in number]: ({ date_created: string | null; object: { id: number } })[] } } | { key: "tags.getWithObjects", input: LibraryArgs<number[]>, result: { [key in number]: ({ date_created: string | null; object: { id: number } })[] } } |
{ key: "tags.list", input: LibraryArgs<null>, result: Tag[] } | { key: "tags.list", input: LibraryArgs<null>, result: Tag[] } |
@ -312,7 +312,7 @@ export type FlashMode =
*/ */
"Unknown" | "Unknown" |
/** /**
* FLash was on * `FLash` was on
*/ */
"On" | "On" |
/** /**
@ -442,6 +442,8 @@ export type LocationSettings = { explorer: ExplorerSettings<FilePathOrder> }
*/ */
export type LocationUpdateArgs = { id: number; name: string | null; generate_preview_media: boolean | null; sync_preview_media: boolean | null; hidden: boolean | null; indexer_rules_ids: number[]; path: string | null } export type LocationUpdateArgs = { id: number; name: string | null; generate_preview_media: boolean | null; sync_preview_media: boolean | null; hidden: boolean | null; indexer_rules_ids: number[]; path: string | null }
export type LocationWithIndexerRule = { id: number; pub_id: number[]; name: string | null; path: string | null; total_capacity: number | null; available_capacity: number | null; size_in_bytes: number[] | null; is_archived: boolean | null; generate_preview_media: boolean | null; sync_preview_media: boolean | null; hidden: boolean | null; date_created: string | null; instance_id: number | null; indexer_rules: IndexerRule[] }
export type MaybeUndefined<T> = null | T export type MaybeUndefined<T> = null | T
export type MediaDataOrder = { field: "epochTime"; value: SortOrder } export type MediaDataOrder = { field: "epochTime"; value: SortOrder }
@ -503,6 +505,8 @@ export type ObjectValidatorArgs = { id: number; path: string }
export type ObjectWithFilePaths = { id: number; pub_id: number[]; kind: number | null; key_id: number | null; hidden: boolean | null; favorite: boolean | null; important: boolean | null; note: string | null; date_created: string | null; date_accessed: string | null; file_paths: ({ id: number; pub_id: number[]; is_dir: boolean | null; cas_id: string | null; integrity_checksum: string | null; location_id: number | null; materialized_path: string | null; name: string | null; extension: string | null; hidden: boolean | null; size_in_bytes: string | null; size_in_bytes_bytes: number[] | null; inode: number[] | null; object_id: number | null; key_id: number | null; date_created: string | null; date_modified: string | null; date_indexed: string | null; object: { id: number; pub_id: number[]; kind: number | null; key_id: number | null; hidden: boolean | null; favorite: boolean | null; important: boolean | null; note: string | null; date_created: string | null; date_accessed: string | null; media_data: { resolution: number[] | null; media_date: number[] | null; media_location: number[] | null; camera_data: number[] | null; artist: string | null; description: string | null; copyright: string | null; exif_version: string | null } | null } | null })[] } export type ObjectWithFilePaths = { id: number; pub_id: number[]; kind: number | null; key_id: number | null; hidden: boolean | null; favorite: boolean | null; important: boolean | null; note: string | null; date_created: string | null; date_accessed: string | null; file_paths: ({ id: number; pub_id: number[]; is_dir: boolean | null; cas_id: string | null; integrity_checksum: string | null; location_id: number | null; materialized_path: string | null; name: string | null; extension: string | null; hidden: boolean | null; size_in_bytes: string | null; size_in_bytes_bytes: number[] | null; inode: number[] | null; object_id: number | null; key_id: number | null; date_created: string | null; date_modified: string | null; date_indexed: string | null; object: { id: number; pub_id: number[]; kind: number | null; key_id: number | null; hidden: boolean | null; favorite: boolean | null; important: boolean | null; note: string | null; date_created: string | null; date_accessed: string | null; media_data: { resolution: number[] | null; media_date: number[] | null; media_location: number[] | null; camera_data: number[] | null; artist: string | null; description: string | null; copyright: string | null; exif_version: string | null } | null } | null })[] }
export type ObjectWithFilePaths2 = { id: number; pub_id: number[]; kind: number | null; key_id: number | null; hidden: boolean | null; favorite: boolean | null; important: boolean | null; note: string | null; date_created: string | null; date_accessed: string | null; file_paths: ({ id: number; pub_id: number[]; is_dir: boolean | null; cas_id: string | null; integrity_checksum: string | null; location_id: number | null; materialized_path: string | null; name: string | null; extension: string | null; hidden: boolean | null; size_in_bytes: string | null; size_in_bytes_bytes: number[] | null; inode: number[] | null; object_id: number | null; key_id: number | null; date_created: string | null; date_modified: string | null; date_indexed: string | null; object: { id: number; pub_id: number[]; kind: number | null; key_id: number | null; hidden: boolean | null; favorite: boolean | null; important: boolean | null; note: string | null; date_created: string | null; date_accessed: string | null; media_data: { resolution: number[] | null; media_date: number[] | null; media_location: number[] | null; camera_data: number[] | null; artist: string | null; description: string | null; copyright: string | null; exif_version: string | null } | null } | null })[] }
export type OldFileCopierJobInit = { source_location_id: number; target_location_id: number; sources_file_path_ids: number[]; target_location_relative_directory_path: string } export type OldFileCopierJobInit = { source_location_id: number; target_location_id: number; sources_file_path_ids: number[]; target_location_relative_directory_path: string }
export type OldFileCutterJobInit = { source_location_id: number; target_location_id: number; sources_file_path_ids: number[]; target_location_relative_directory_path: string } export type OldFileCutterJobInit = { source_location_id: number; target_location_id: number; sources_file_path_ids: number[]; target_location_relative_directory_path: string }

View file

@ -0,0 +1,68 @@
diff --git a/dist/index.js b/dist/index.js
index df70b20e9381e7bf44f9a35b11174e40ede80376..bc06927b7290891717c48999f79d0705ef6b8df7 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -17,7 +17,7 @@ class TauriTransport {
});
// @ts-ignore
this.requestMap.set(id, resolve);
- await window.appWindow.emit("plugin:rspc:transport", {
+ await window.getCurrent().emit("plugin:rspc:transport", {
id,
method: operation,
params: {
diff --git a/dist/index.mjs b/dist/index.mjs
index a80ac6155a4a920173c442b4d7b458a46ab63624..03b3d9ee6d3691980584c1fd58c17f7c81dbab39 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -1,6 +1,6 @@
import { randomId, RSPCError } from '@oscartbeaumont-sd/rspc-client';
import { listen } from '@tauri-apps/api/event';
-import { appWindow } from '@tauri-apps/api/window';
+import { getCurrent } from '@tauri-apps/api/window';
// @ts-nocheck No one asked
class TauriTransport {
@@ -15,7 +15,7 @@ class TauriTransport {
});
// @ts-ignore
this.requestMap.set(id, resolve);
- await appWindow.emit("plugin:rspc:transport", {
+ await getCurrent().emit("plugin:rspc:transport", {
id,
method: operation,
params: {
diff --git a/dist/v2.js b/dist/v2.js
index e597db7bf00a7e62f32266814345b257b8f8d0da..fa683640a0c0c9f1796ed1af1fd0d04c2df7c68b 100644
--- a/dist/v2.js
+++ b/dist/v2.js
@@ -44,7 +44,7 @@ var window = require('@tauri-apps/api/window');
if (!listener) {
await listener;
}
- await window.appWindow.emit("plugin:rspc:transport", currentBatch);
+ await window.getCurrent().emit("plugin:rspc:transport", currentBatch);
})();
});
}
diff --git a/dist/v2.mjs b/dist/v2.mjs
index bd7ceb6927d187dd2ff7cf9a9364d7c312a75b88..844495e36aef0c1337e7393685c1a33fedb88c2c 100644
--- a/dist/v2.mjs
+++ b/dist/v2.mjs
@@ -1,6 +1,6 @@
import { AlphaRSPCError } from '@oscartbeaumont-sd/rspc-client/v2';
import { listen } from '@tauri-apps/api/event';
-import { appWindow } from '@tauri-apps/api/window';
+import { getCurrent } from '@tauri-apps/api/window';
// @ts-nocheck No one asked
/**
@@ -42,7 +42,7 @@ import { appWindow } from '@tauri-apps/api/window';
if (!listener) {
await listener;
}
- await appWindow.emit("plugin:rspc:transport", currentBatch);
+ await getCurrent().emit("plugin:rspc:transport", currentBatch);
})();
});
}

View file

@ -15,6 +15,9 @@ patchedDependencies:
'@contentlayer/cli@0.3.4': '@contentlayer/cli@0.3.4':
hash: r22rdxqpl6d5mdpkhdt6tw4evu hash: r22rdxqpl6d5mdpkhdt6tw4evu
path: patches/@contentlayer__cli@0.3.4.patch path: patches/@contentlayer__cli@0.3.4.patch
'@oscartbeaumont-sd/rspc-tauri@0.0.0-main-dc31e5b2':
hash: 3ozd223mr7o4cioyf7af7qd56a
path: patches/@oscartbeaumont-sd__rspc-tauri@0.0.0-main-dc31e5b2.patch
'@remix-run/router@1.13.1': '@remix-run/router@1.13.1':
hash: wdk5klbkacqsve2yzdckjvtjz4 hash: wdk5klbkacqsve2yzdckjvtjz4
path: patches/@remix-run__router@1.13.1.patch path: patches/@remix-run__router@1.13.1.patch
@ -86,7 +89,7 @@ importers:
version: 0.0.0-main-dc31e5b2 version: 0.0.0-main-dc31e5b2
'@oscartbeaumont-sd/rspc-tauri': '@oscartbeaumont-sd/rspc-tauri':
specifier: '=0.0.0-main-dc31e5b2' specifier: '=0.0.0-main-dc31e5b2'
version: 0.0.0-main-dc31e5b2(@tauri-apps/api@1.5.1) version: 0.0.0-main-dc31e5b2(patch_hash=3ozd223mr7o4cioyf7af7qd56a)(@tauri-apps/api@2.0.0-beta.11)
'@remix-run/router': '@remix-run/router':
specifier: '=1.13.1' specifier: '=1.13.1'
version: 1.13.1(patch_hash=wdk5klbkacqsve2yzdckjvtjz4) version: 1.13.1(patch_hash=wdk5klbkacqsve2yzdckjvtjz4)
@ -106,8 +109,17 @@ importers:
specifier: ^4.36.1 specifier: ^4.36.1
version: 4.36.1(react-dom@18.2.0(react@18.2.0))(react-native@0.73.4(@babel/core@7.24.0)(@babel/preset-env@7.24.0(@babel/core@7.24.0))(react@18.2.0))(react@18.2.0) version: 4.36.1(react-dom@18.2.0(react@18.2.0))(react-native@0.73.4(@babel/core@7.24.0)(@babel/preset-env@7.24.0(@babel/core@7.24.0))(react@18.2.0))(react@18.2.0)
'@tauri-apps/api': '@tauri-apps/api':
specifier: 1.5.1 specifier: next
version: 1.5.1 version: 2.0.0-beta.11
'@tauri-apps/plugin-dialog':
specifier: 2.0.0-beta.2
version: 2.0.0-beta.2
'@tauri-apps/plugin-os':
specifier: 2.0.0-beta.2
version: 2.0.0-beta.2
'@tauri-apps/plugin-shell':
specifier: 2.0.0-beta.2
version: 2.0.0-beta.2
consistent-hash: consistent-hash:
specifier: ^1.2.2 specifier: ^1.2.2
version: 1.2.2 version: 1.2.2
@ -134,8 +146,8 @@ importers:
specifier: ^2.16.0 specifier: ^2.16.0
version: 2.16.0 version: 2.16.0
'@tauri-apps/cli': '@tauri-apps/cli':
specifier: ^1.5.11 specifier: next
version: 1.5.11 version: 2.0.0-beta.15
'@types/react': '@types/react':
specifier: ^18.2.67 specifier: ^18.2.67
version: 18.2.67 version: 18.2.67
@ -5060,75 +5072,88 @@ packages:
'@tanstack/virtual-core@3.2.0': '@tanstack/virtual-core@3.2.0':
resolution: {integrity: sha512-P5XgYoAw/vfW65byBbJQCw+cagdXDT/qH6wmABiLt4v4YBT2q2vqCOhihe+D1Nt325F/S/0Tkv6C5z0Lv+VBQQ==} resolution: {integrity: sha512-P5XgYoAw/vfW65byBbJQCw+cagdXDT/qH6wmABiLt4v4YBT2q2vqCOhihe+D1Nt325F/S/0Tkv6C5z0Lv+VBQQ==}
'@tauri-apps/api@1.5.1': '@tauri-apps/api@2.0.0-beta.11':
resolution: {integrity: sha512-6unsZDOdlXTmauU3NhWhn+Cx0rODV+rvNvTdvolE5Kls5ybA6cqndQENDt1+FS0tF7ozCP66jwWoH6a5h90BrA==} resolution: {integrity: sha512-wJRY+fBUm3KpqZDHMIz5HRv+1vlnvRJ/dFxiyY3NlINTx2qXqDou5qWYcP1CuZXsd39InWVPV3FAZvno/kGCkA==}
engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} engines: {node: '>= 18', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
'@tauri-apps/cli-darwin-arm64@1.5.11': '@tauri-apps/api@2.0.0-beta.4':
resolution: {integrity: sha512-2NLSglDb5VfvTbMtmOKWyD+oaL/e8Z/ZZGovHtUFyUSFRabdXc6cZOlcD1BhFvYkHqm+TqGaz5qtPR5UbqDs8A==} resolution: {integrity: sha512-Nxtj28NYUo5iwYkpYslxmOPkdI2WkELU2e3UH9nbJm9Ydki2CQwJVGQxx4EANtdZcMNsEsUzRqaDTvEUYH1l6w==}
engines: {node: '>= 18', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
'@tauri-apps/cli-darwin-arm64@2.0.0-beta.15':
resolution: {integrity: sha512-M4owBLoRdJb2/IK48KOQDU3j5xrjqGxa539rDXMjvaKydBk8x+aLdk3xZNsk/owHTI1GnrQZsPCMQaOgetYHaw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
'@tauri-apps/cli-darwin-x64@1.5.11': '@tauri-apps/cli-darwin-x64@2.0.0-beta.15':
resolution: {integrity: sha512-/RQllHiJRH2fJOCudtZlaUIjofkHzP3zZgxi71ZUm7Fy80smU5TDfwpwOvB0wSVh0g/ciDjMArCSTo0MRvL+ag==} resolution: {integrity: sha512-ECpatfJdT4xKyFoE7tNEtTUIRxjQ2XSXa0TQkP3g7Kn7H/jRse+7pYe69jASA7shixajatAwmD4bXNT8jYRyNA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
'@tauri-apps/cli-linux-arm-gnueabihf@1.5.11': '@tauri-apps/cli-linux-arm-gnueabihf@2.0.0-beta.15':
resolution: {integrity: sha512-IlBuBPKmMm+a5LLUEK6a21UGr9ZYd6zKuKLq6IGM4tVweQa8Sf2kP2Nqs74dMGIUrLmMs0vuqdURpykQg+z4NQ==} resolution: {integrity: sha512-GQz2nnPwIamzDbmmfGWvmmoLthOkOBs0RO5u72KYAa78ZRFTx7S6AovnxJv48Fq+zeGGdDKoD9+ZG2Ue+sCL4w==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
'@tauri-apps/cli-linux-arm64-gnu@1.5.11': '@tauri-apps/cli-linux-arm64-gnu@2.0.0-beta.15':
resolution: {integrity: sha512-w+k1bNHCU/GbmXshtAhyTwqosThUDmCEFLU4Zkin1vl2fuAtQry2RN7thfcJFepblUGL/J7yh3Q/0+BCjtspKQ==} resolution: {integrity: sha512-YBIfq0GbmIsWmRy6dVuDv3oMJN7a3R8HGVPMsa1W526AdCxoZDiPOQpSQN4VihJlbebUHxS/HyYF6maCY8uGzA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
'@tauri-apps/cli-linux-arm64-musl@1.5.11': '@tauri-apps/cli-linux-arm64-musl@2.0.0-beta.15':
resolution: {integrity: sha512-PN6/dl+OfYQ/qrAy4HRAfksJ2AyWQYn2IA/2Wwpaa7SDRz2+hzwTQkvajuvy0sQ5L2WCG7ymFYRYMbpC6Hk9Pg==} resolution: {integrity: sha512-2ZBXoShz7UfqVGmc85mhwjI6ckdtrk15V69adxt/x+VS68yK6Ddbj+yqlffpeFNL90fZrsVhFoRIDqgkxtwksQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
'@tauri-apps/cli-linux-x64-gnu@1.5.11': '@tauri-apps/cli-linux-x64-gnu@2.0.0-beta.15':
resolution: {integrity: sha512-MTVXLi89Nj7Apcvjezw92m7ZqIDKT5SFKZtVPCg6RoLUBTzko/BQoXYIRWmdoz2pgkHDUHgO2OMJ8oKzzddXbw==} resolution: {integrity: sha512-cwJqIIdc4Kq9sBl/vYc+Y95iMe+mlTYUj7ZnSn4YAbLKFz432bGg6uBn2qHXFN5jzwXtEOVZiB1zDZ2kveVoAQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
'@tauri-apps/cli-linux-x64-musl@1.5.11': '@tauri-apps/cli-linux-x64-musl@2.0.0-beta.15':
resolution: {integrity: sha512-kwzAjqFpz7rvTs7WGZLy/a5nS5t15QKr3E9FG95MNF0exTl3d29YoAUAe1Mn0mOSrTJ9Z+vYYAcI/QdcsGBP+w==} resolution: {integrity: sha512-nNuxZ8/qs0vQbdLO2hovskZGxwGn2z4x1QFJuL4xwd6Tryy9vVcznvyZS+t/72dCLoIkY9pKZQq5nYtAHYfTEg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
'@tauri-apps/cli-win32-arm64-msvc@1.5.11': '@tauri-apps/cli-win32-arm64-msvc@2.0.0-beta.15':
resolution: {integrity: sha512-L+5NZ/rHrSUrMxjj6YpFYCXp6wHnq8c8SfDTBOX8dO8x+5283/vftb4vvuGIsLS4UwUFXFnLt3XQr44n84E67Q==} resolution: {integrity: sha512-DXiXMTE00INjBkTgq1CYduMWgUwQ0NvLw+uXfu8BUupA+aOlv9ODhsGu7bZSaxKx4/glwxNAGZum4kQ0E0AxUg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
'@tauri-apps/cli-win32-ia32-msvc@1.5.11': '@tauri-apps/cli-win32-ia32-msvc@2.0.0-beta.15':
resolution: {integrity: sha512-oVlD9IVewrY0lZzTdb71kNXkjdgMqFq+ohb67YsJb4Rf7o8A9DTlFds1XLCe3joqLMm4M+gvBKD7YnGIdxQ9vA==} resolution: {integrity: sha512-ajEQdW2jx2raPp7eDYryJkbBrgI8PIY1dz5ro8FweRrRmbotaUlclsro1kfNMQrfDah8+qfwnRvW3MahOBE5Wg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
'@tauri-apps/cli-win32-x64-msvc@1.5.11': '@tauri-apps/cli-win32-x64-msvc@2.0.0-beta.15':
resolution: {integrity: sha512-1CexcqUFCis5ypUIMOKllxUBrna09McbftWENgvVXMfA+SP+yPDPAVb8fIvUcdTIwR/yHJwcIucmTB4anww4vg==} resolution: {integrity: sha512-yzsSgoiY0PmFiR5LvVOFr1b7h9l3aLPPQFlDG6+kRMrxCo7x7Pbyh4D5cqiMUuZO0QacwSP38EH6w0F88Y+4OA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@tauri-apps/cli@1.5.11': '@tauri-apps/cli@2.0.0-beta.15':
resolution: {integrity: sha512-B475D7phZrq5sZ3kDABH4g2mEoUIHtnIO+r4ZGAAfsjMbZCwXxR/jlMGTEL+VO3YzjpF7gQe38IzB4vLBbVppw==} resolution: {integrity: sha512-3pCvc54QfsRY+i9B7w3Q5jPAGtf8p+g7N/BamWPeiW6YqDqbHi9rNVI3SzrHkRRNOJnzMW8E5a8G0HziOluZGg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
hasBin: true hasBin: true
'@tauri-apps/plugin-dialog@2.0.0-beta.2':
resolution: {integrity: sha512-WugTn/8d5jYA0GL1JRIJgA1OSxG0h2V4PSZZzehgA3v7rPlIU6w9s2+dSRqj55aMj6hm3Az9YbQqC18nuaHkpw==}
'@tauri-apps/plugin-os@2.0.0-beta.2':
resolution: {integrity: sha512-rhJ/sEYvEAeMUQt6UiFODi5dT6F/ciNZYBQrbFTwhIqwQ2fp87dmzsscMy7FQ9LOor8AW+kL1KWoadfgzA/mSA==}
'@tauri-apps/plugin-shell@2.0.0-beta.2':
resolution: {integrity: sha512-9rWsfN7Wt+EuWmpmNnK8bCs+04fzhEYrHtWyLIAYxb9diFdcJrEoctCP9YM2v+Uf8/y8qFC7VCbZ/9VQHANymQ==}
'@testing-library/dom@9.3.4': '@testing-library/dom@9.3.4':
resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==} resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==}
engines: {node: '>=14'} engines: {node: '>=14'}
@ -16174,10 +16199,10 @@ snapshots:
'@tanstack/react-query': 4.36.1(react-dom@18.2.0(react@18.2.0))(react-native@0.73.4(@babel/core@7.24.0)(@babel/preset-env@7.24.0(@babel/core@7.24.0))(react@18.2.0))(react@18.2.0) '@tanstack/react-query': 4.36.1(react-dom@18.2.0(react@18.2.0))(react-native@0.73.4(@babel/core@7.24.0)(@babel/preset-env@7.24.0(@babel/core@7.24.0))(react@18.2.0))(react@18.2.0)
react: 18.2.0 react: 18.2.0
'@oscartbeaumont-sd/rspc-tauri@0.0.0-main-dc31e5b2(@tauri-apps/api@1.5.1)': '@oscartbeaumont-sd/rspc-tauri@0.0.0-main-dc31e5b2(patch_hash=3ozd223mr7o4cioyf7af7qd56a)(@tauri-apps/api@2.0.0-beta.11)':
dependencies: dependencies:
'@oscartbeaumont-sd/rspc-client': 0.0.0-main-dc31e5b2 '@oscartbeaumont-sd/rspc-client': 0.0.0-main-dc31e5b2
'@tauri-apps/api': 1.5.1 '@tauri-apps/api': 2.0.0-beta.11
'@phosphor-icons/core@2.0.8': {} '@phosphor-icons/core@2.0.8': {}
@ -18631,50 +18656,64 @@ snapshots:
'@tanstack/virtual-core@3.2.0': {} '@tanstack/virtual-core@3.2.0': {}
'@tauri-apps/api@1.5.1': {} '@tauri-apps/api@2.0.0-beta.11': {}
'@tauri-apps/cli-darwin-arm64@1.5.11': '@tauri-apps/api@2.0.0-beta.4': {}
'@tauri-apps/cli-darwin-arm64@2.0.0-beta.15':
optional: true optional: true
'@tauri-apps/cli-darwin-x64@1.5.11': '@tauri-apps/cli-darwin-x64@2.0.0-beta.15':
optional: true optional: true
'@tauri-apps/cli-linux-arm-gnueabihf@1.5.11': '@tauri-apps/cli-linux-arm-gnueabihf@2.0.0-beta.15':
optional: true optional: true
'@tauri-apps/cli-linux-arm64-gnu@1.5.11': '@tauri-apps/cli-linux-arm64-gnu@2.0.0-beta.15':
optional: true optional: true
'@tauri-apps/cli-linux-arm64-musl@1.5.11': '@tauri-apps/cli-linux-arm64-musl@2.0.0-beta.15':
optional: true optional: true
'@tauri-apps/cli-linux-x64-gnu@1.5.11': '@tauri-apps/cli-linux-x64-gnu@2.0.0-beta.15':
optional: true optional: true
'@tauri-apps/cli-linux-x64-musl@1.5.11': '@tauri-apps/cli-linux-x64-musl@2.0.0-beta.15':
optional: true optional: true
'@tauri-apps/cli-win32-arm64-msvc@1.5.11': '@tauri-apps/cli-win32-arm64-msvc@2.0.0-beta.15':
optional: true optional: true
'@tauri-apps/cli-win32-ia32-msvc@1.5.11': '@tauri-apps/cli-win32-ia32-msvc@2.0.0-beta.15':
optional: true optional: true
'@tauri-apps/cli-win32-x64-msvc@1.5.11': '@tauri-apps/cli-win32-x64-msvc@2.0.0-beta.15':
optional: true optional: true
'@tauri-apps/cli@1.5.11': '@tauri-apps/cli@2.0.0-beta.15':
optionalDependencies: optionalDependencies:
'@tauri-apps/cli-darwin-arm64': 1.5.11 '@tauri-apps/cli-darwin-arm64': 2.0.0-beta.15
'@tauri-apps/cli-darwin-x64': 1.5.11 '@tauri-apps/cli-darwin-x64': 2.0.0-beta.15
'@tauri-apps/cli-linux-arm-gnueabihf': 1.5.11 '@tauri-apps/cli-linux-arm-gnueabihf': 2.0.0-beta.15
'@tauri-apps/cli-linux-arm64-gnu': 1.5.11 '@tauri-apps/cli-linux-arm64-gnu': 2.0.0-beta.15
'@tauri-apps/cli-linux-arm64-musl': 1.5.11 '@tauri-apps/cli-linux-arm64-musl': 2.0.0-beta.15
'@tauri-apps/cli-linux-x64-gnu': 1.5.11 '@tauri-apps/cli-linux-x64-gnu': 2.0.0-beta.15
'@tauri-apps/cli-linux-x64-musl': 1.5.11 '@tauri-apps/cli-linux-x64-musl': 2.0.0-beta.15
'@tauri-apps/cli-win32-arm64-msvc': 1.5.11 '@tauri-apps/cli-win32-arm64-msvc': 2.0.0-beta.15
'@tauri-apps/cli-win32-ia32-msvc': 1.5.11 '@tauri-apps/cli-win32-ia32-msvc': 2.0.0-beta.15
'@tauri-apps/cli-win32-x64-msvc': 1.5.11 '@tauri-apps/cli-win32-x64-msvc': 2.0.0-beta.15
'@tauri-apps/plugin-dialog@2.0.0-beta.2':
dependencies:
'@tauri-apps/api': 2.0.0-beta.4
'@tauri-apps/plugin-os@2.0.0-beta.2':
dependencies:
'@tauri-apps/api': 2.0.0-beta.4
'@tauri-apps/plugin-shell@2.0.0-beta.2':
dependencies:
'@tauri-apps/api': 2.0.0-beta.4
'@testing-library/dom@9.3.4': '@testing-library/dom@9.3.4':
dependencies: dependencies:

View file

@ -135,14 +135,15 @@ case "$(uname)" in
echo echo
fi fi
;; ;;
"Linux") # https://github.com/tauri-apps/tauri-docs/blob/dev/docs/guides/getting-started/prerequisites.md#setting-up-linux "Linux")
# https://github.com/tauri-apps/tauri-docs/blob/dev/docs/guides/getting-started/prerequisites.md#setting-up-linux
if has apt-get; then if has apt-get; then
echo "Detected apt!" echo "Detected apt!"
echo "Installing dependencies with apt..." echo "Installing dependencies with apt..."
# Tauri dependencies # Tauri dependencies
set -- build-essential curl wget file openssl libssl-dev libgtk-3-dev librsvg2-dev \ set -- build-essential curl wget file openssl libssl-dev libgtk-3-dev librsvg2-dev \
libwebkit2gtk-4.0-dev libayatana-appindicator3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev libxdo-dev
# Webkit2gtk requires gstreamer plugins for video playback to work # Webkit2gtk requires gstreamer plugins for video playback to work
set -- "$@" gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev set -- "$@" gstreamer1.0-plugins-good gstreamer1.0-plugins-ugly libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
@ -160,7 +161,7 @@ case "$(uname)" in
echo "Installing dependencies with pacman..." echo "Installing dependencies with pacman..."
# Tauri dependencies # Tauri dependencies
set -- base-devel curl wget file openssl gtk3 librsvg webkit2gtk libayatana-appindicator set -- base-devel curl wget file openssl gtk3 librsvg webkit2gtk-4.1 libayatana-appindicator xdotool
# Webkit2gtk requires gstreamer plugins for video playback to work # Webkit2gtk requires gstreamer plugins for video playback to work
set -- "$@" gst-plugins-base gst-plugins-good gst-plugins-ugly set -- "$@" gst-plugins-base gst-plugins-good gst-plugins-ugly
@ -183,15 +184,8 @@ case "$(uname)" in
'https://github.com/spacedriveapp/spacedrive/issues' 'https://github.com/spacedriveapp/spacedrive/issues'
fi fi
# For Fedora 36 and below, and all Enterprise Linux Distributions, you need to install webkit2gtk3-devel instead of webkit2gtk4.0-devel
if ! { sudo dnf install webkit2gtk4.0-devel || sudo dnf install webkit2gtk3-devel; }; then
err 'We were unable to install the webkit2gtk4.0-devel/webkit2gtk3-devel package.' \
'Please open an issue if you feel that this is incorrect.' \
'https://github.com/spacedriveapp/spacedrive/issues'
fi
# Tauri dependencies # Tauri dependencies
set -- openssl openssl-dev curl wget file libappindicator-gtk3-devel librsvg2-devel set -- openssl webkit2gtk4.1-devel openssl-dev curl wget file libappindicator-gtk3-devel librsvg2-devel libxdo-devel
# Webkit2gtk requires gstreamer plugins for video playback to work # Webkit2gtk requires gstreamer plugins for video playback to work
set -- "$@" gstreamer1-devel gstreamer1-plugins-base-devel gstreamer1-plugins-good \ set -- "$@" gstreamer1-devel gstreamer1-plugins-base-devel gstreamer1-plugins-good \
@ -211,7 +205,7 @@ case "$(uname)" in
# Tauri dependencies # Tauri dependencies
set -- build-base curl wget file openssl-dev gtk+3.0-dev librsvg-dev \ set -- build-base curl wget file openssl-dev gtk+3.0-dev librsvg-dev \
webkit2gtk-dev libayatana-indicator-dev webkit2gtk-4.1-dev libayatana-indicator-dev xdotool
# Webkit2gtk requires gstreamer plugins for video playback to work # Webkit2gtk requires gstreamer plugins for video playback to work
set -- "$@" gst-plugins-base-dev gst-plugins-good gst-plugins-ugly set -- "$@" gst-plugins-base-dev gst-plugins-good gst-plugins-ugly

View file

@ -17,7 +17,7 @@ const __debug = env.NODE_ENV === 'debug'
* @returns {Promise<string?>} * @returns {Promise<string?>}
*/ */
export async function tauriUpdaterKey(nativeDeps) { export async function tauriUpdaterKey(nativeDeps) {
if (env.TAURI_PRIVATE_KEY) return null if (env.TAURI_SIGNING_PRIVATE_KEY) return null
// pnpm exec tauri signer generate -w // pnpm exec tauri signer generate -w
const privateKeyPath = path.join(nativeDeps, 'tauri.key') const privateKeyPath = path.join(nativeDeps, 'tauri.key')
@ -44,8 +44,8 @@ export async function tauriUpdaterKey(nativeDeps) {
if (!(publicKey && privateKey)) throw new Error('Empty keys') if (!(publicKey && privateKey)) throw new Error('Empty keys')
} }
env.TAURI_PRIVATE_KEY = privateKey env.TAURI_SIGNING_PRIVATE_KEY = privateKey
env.TAURI_KEY_PASSWORD = '' env.TAURI_SIGNING_PRIVATE_KEY_PASSWORD = ''
return publicKey return publicKey
} }
@ -64,19 +64,22 @@ export async function patchTauri(root, nativeDeps, targets, bundles, args) {
const osType = os.type() const osType = os.type()
const tauriPatch = { const tauriPatch = {
tauri: { build: {
bundle: { features: /** @type {string[]} */ ([]),
macOS: { minimumSystemVersion: '' }, },
resources: {}, bundle: {
}, macOS: { minimumSystemVersion: '' },
resources: {},
},
plugins: {
updater: /** @type {{ pubkey?: string }} */ ({}), updater: /** @type {{ pubkey?: string }} */ ({}),
}, },
} }
if (osType === 'Linux') { if (osType === 'Linux') {
tauriPatch.tauri.bundle.resources = await linuxLibs(nativeDeps) tauriPatch.bundle.resources = await linuxLibs(nativeDeps)
} else if (osType === 'Windows_NT') { } else if (osType === 'Windows_NT') {
tauriPatch.tauri.bundle.resources = { tauriPatch.bundle.resources = {
...(await windowsDLLs(nativeDeps)), ...(await windowsDLLs(nativeDeps)),
[path.join(nativeDeps, 'models', 'yolov8s.onnx')]: './models/yolov8s.onnx', [path.join(nativeDeps, 'models', 'yolov8s.onnx')]: './models/yolov8s.onnx',
} }
@ -89,22 +92,28 @@ export async function patchTauri(root, nativeDeps, targets, bundles, args) {
.then(JSON.parse) .then(JSON.parse)
if (bundles.length === 0) { if (bundles.length === 0) {
const defaultBundles = tauriConfig.tauri?.bundle?.targets const defaultBundles = tauriConfig?.bundle?.targets
if (Array.isArray(defaultBundles)) bundles.push(...defaultBundles) if (Array.isArray(defaultBundles)) bundles.push(...defaultBundles)
if (bundles.length === 0) bundles.push('all') if (bundles.length === 0) bundles.push('all')
} }
if (args[0] === 'build') { switch (args[0]) {
if (tauriConfig?.tauri?.updater?.active) { case 'dev':
const pubKey = await tauriUpdaterKey(nativeDeps) tauriPatch.build.features.push('devtools')
if (pubKey != null) tauriPatch.tauri.updater.pubkey = pubKey break
case 'build': {
if (tauriConfig?.plugins?.updater?.active) {
const pubKey = await tauriUpdaterKey(nativeDeps)
if (pubKey != null) tauriPatch.plugins.updater.pubkey = pubKey
}
break
} }
} }
if (osType === 'Darwin') { if (osType === 'Darwin') {
const macOSArm64MinimumVersion = '11.0' const macOSArm64MinimumVersion = '11.0'
let macOSMinimumVersion = tauriConfig?.tauri?.bundle?.macOS?.minimumSystemVersion let macOSMinimumVersion = tauriConfig?.bundle?.macOS?.minimumSystemVersion
if ( if (
(targets.includes('aarch64-apple-darwin') || (targets.includes('aarch64-apple-darwin') ||
@ -125,7 +134,7 @@ export async function patchTauri(root, nativeDeps, targets, bundles, args) {
if (macOSMinimumVersion) { if (macOSMinimumVersion) {
env.MACOSX_DEPLOYMENT_TARGET = macOSMinimumVersion env.MACOSX_DEPLOYMENT_TARGET = macOSMinimumVersion
tauriPatch.tauri.bundle.macOS.minimumSystemVersion = macOSMinimumVersion tauriPatch.bundle.macOS.minimumSystemVersion = macOSMinimumVersion
} else { } else {
throw new Error('No minimum macOS version detected, please review tauri.conf.json') throw new Error('No minimum macOS version detected, please review tauri.conf.json')
} }