mirror of
https://github.com/Vomitblood/stort.git
synced 2025-03-26 16:51:00 +08:00
Compare commits
11 commits
commit-202
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
b1da210a9f | ||
|
9036e5f189 | ||
|
063b27a7eb | ||
|
166b90444d | ||
|
79f7cbc759 | ||
|
2e261a8f10 | ||
|
f237031eb3 | ||
|
e48611b11c | ||
|
72f9121593 | ||
|
2337661fed | ||
|
205b0e593c |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -42,7 +42,7 @@ jobs:
|
|||
mise install
|
||||
mise settings set experimental true
|
||||
|
||||
- name: Install Bun Packages
|
||||
- name: Install bun Packages
|
||||
run: mise exec bun --command 'bun install'
|
||||
|
||||
- name: Build Tauri
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
bun 1.1.21
|
||||
bun 1.1.22
|
||||
nodejs 20.6.1
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Stort
|
||||
|
||||
[](https://github.com/Vomitblood/stort/actions/workflows/build-validation.yml)
|
||||
[](https://github.com/Vomitblood/stort/actions/workflows/build.yml)
|
||||
A launcher for Steam Deck to be used in Game Mode.
|
||||
|
|
26
build2.sh
26
build2.sh
|
@ -1,26 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
IMAGE_NAME="ubuntu:22.04"
|
||||
HOST_PROJECT_DIR="$PWD"
|
||||
CONTAINER_PROJECT_DIR="/app"
|
||||
HOST_OUTPUT_DIR="$PWD/src-tauri/target/release/bundle/appimage"
|
||||
CONTAINER_OUTPUT_DIR="$CONTAINER_PROJECT_DIR/src-tauri/target/release/bundle/appimage"
|
||||
USER_ID=$(id -u)
|
||||
GROUP_ID=$(id -g)
|
||||
|
||||
docker pull $IMAGE_NAME
|
||||
|
||||
# run the docker image and remove on completion
|
||||
docker run --rm -it \
|
||||
-v "$HOST_PROJECT_DIR":$CONTAINER_PROJECT_DIR \
|
||||
$IMAGE_NAME \
|
||||
/bin/bash -c "
|
||||
|
||||
# update packages
|
||||
apt update && apt install curl -y && \
|
||||
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && \
|
||||
echo $HOME && \
|
||||
source "/root/.cargo/env" && \
|
||||
cargo --version
|
||||
"
|
7033
package-lock.json
generated
7033
package-lock.json
generated
File diff suppressed because it is too large
Load diff
11
package.json
11
package.json
|
@ -17,18 +17,23 @@
|
|||
"@mui/icons-material": "^5.16.6",
|
||||
"@mui/lab": "^5.0.0-alpha.173",
|
||||
"@mui/material": "^5.16.6",
|
||||
"@tauri-apps/api": "^1.6.0",
|
||||
"@tauri-apps/api": "^2.0.0",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.1",
|
||||
"@tauri-apps/plugin-fs": "^2.0.1",
|
||||
"@tauri-apps/plugin-notification": "^2.0.0",
|
||||
"@tauri-apps/plugin-process": "^2.0.0",
|
||||
"jotai": "^2.9.1",
|
||||
"lodash": "^4.17.21",
|
||||
"next": "14.2.5",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"smol-toml": "^1.3.0",
|
||||
"tauri": "^0.15.0",
|
||||
"zustand": "^4.5.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.0.4",
|
||||
"@types/lodash": "^4.17.7",
|
||||
"@tauri-apps/cli": "^1.6.0",
|
||||
"@types/node": "^20.14.14",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
|
@ -36,4 +41,4 @@
|
|||
"eslint-config-next": "14.2.5",
|
||||
"typescript": "^5.5.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 15 MiB |
BIN
public/images/cry.webp
Normal file
BIN
public/images/cry.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
2530
src-tauri/Cargo.lock
generated
2530
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -12,15 +12,19 @@ rust-version = "1.60"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.5.3", features = [] }
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.7.0", features = [ "protocol-all", "fs-all", "path-all", "window-all", "process-all", "notification-all", "dialog-all"] }
|
||||
tauri = { version = "2", features = ["protocol-asset"] }
|
||||
image = "0.25.2"
|
||||
webp = "0.3.0"
|
||||
gif = "0.13.1"
|
||||
tauri-plugin-notification = "2"
|
||||
tauri-plugin-process = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-fs = "2"
|
||||
|
||||
[features]
|
||||
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
|
||||
|
|
77
src-tauri/capabilities/migrated.json
Normal file
77
src-tauri/capabilities/migrated.json
Normal file
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"identifier": "migrated",
|
||||
"description": "permissions that were migrated from v1",
|
||||
"local": true,
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"fs:allow-read-file",
|
||||
"fs:allow-write-file",
|
||||
"fs:allow-read-dir",
|
||||
"fs:allow-copy-file",
|
||||
"fs:allow-mkdir",
|
||||
"fs:allow-remove",
|
||||
"fs:allow-remove",
|
||||
"fs:allow-rename",
|
||||
"fs:allow-exists",
|
||||
{
|
||||
"identifier": "fs:scope",
|
||||
"allow": [
|
||||
"**",
|
||||
"**/*",
|
||||
"/**/*",
|
||||
"$CONFIG/stort/",
|
||||
"$CONFIG/stort/**",
|
||||
"$HOME/.local/share/stort/*",
|
||||
"$HOME/.local/share/stort/**"
|
||||
]
|
||||
},
|
||||
"core:window:allow-create",
|
||||
"core:window:allow-center",
|
||||
"core:window:allow-request-user-attention",
|
||||
"core:window:allow-set-resizable",
|
||||
"core:window:allow-set-maximizable",
|
||||
"core:window:allow-set-minimizable",
|
||||
"core:window:allow-set-closable",
|
||||
"core:window:allow-set-title",
|
||||
"core:window:allow-maximize",
|
||||
"core:window:allow-unmaximize",
|
||||
"core:window:allow-minimize",
|
||||
"core:window:allow-unminimize",
|
||||
"core:window:allow-show",
|
||||
"core:window:allow-hide",
|
||||
"core:window:allow-close",
|
||||
"core:window:allow-set-decorations",
|
||||
"core:window:allow-set-always-on-top",
|
||||
"core:window:allow-set-content-protected",
|
||||
"core:window:allow-set-size",
|
||||
"core:window:allow-set-min-size",
|
||||
"core:window:allow-set-max-size",
|
||||
"core:window:allow-set-position",
|
||||
"core:window:allow-set-fullscreen",
|
||||
"core:window:allow-set-focus",
|
||||
"core:window:allow-set-icon",
|
||||
"core:window:allow-set-skip-taskbar",
|
||||
"core:window:allow-set-cursor-grab",
|
||||
"core:window:allow-set-cursor-visible",
|
||||
"core:window:allow-set-cursor-icon",
|
||||
"core:window:allow-set-cursor-position",
|
||||
"core:window:allow-set-ignore-cursor-events",
|
||||
"core:window:allow-start-dragging",
|
||||
"core:webview:allow-print",
|
||||
"dialog:allow-open",
|
||||
"dialog:allow-save",
|
||||
"dialog:allow-message",
|
||||
"dialog:allow-ask",
|
||||
"dialog:allow-confirm",
|
||||
"notification:default",
|
||||
"process:allow-restart",
|
||||
"process:allow-exit",
|
||||
"notification:default",
|
||||
"process:default",
|
||||
"dialog:default",
|
||||
"fs:default"
|
||||
]
|
||||
}
|
1
src-tauri/gen/schemas/acl-manifests.json
Normal file
1
src-tauri/gen/schemas/acl-manifests.json
Normal file
File diff suppressed because one or more lines are too long
1
src-tauri/gen/schemas/capabilities.json
Normal file
1
src-tauri/gen/schemas/capabilities.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"migrated":{"identifier":"migrated","description":"permissions that were migrated from v1","local":true,"windows":["main"],"permissions":["core:default","fs:allow-read-file","fs:allow-write-file","fs:allow-read-dir","fs:allow-copy-file","fs:allow-mkdir","fs:allow-remove","fs:allow-remove","fs:allow-rename","fs:allow-exists",{"identifier":"fs:scope","allow":["**","**/*","/**/*","$CONFIG/stort/","$CONFIG/stort/**","$HOME/.local/share/stort/*","$HOME/.local/share/stort/**"]},"core:window:allow-create","core:window:allow-center","core:window:allow-request-user-attention","core:window:allow-set-resizable","core:window:allow-set-maximizable","core:window:allow-set-minimizable","core:window:allow-set-closable","core:window:allow-set-title","core:window:allow-maximize","core:window:allow-unmaximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-set-decorations","core:window:allow-set-always-on-top","core:window:allow-set-content-protected","core:window:allow-set-size","core:window:allow-set-min-size","core:window:allow-set-max-size","core:window:allow-set-position","core:window:allow-set-fullscreen","core:window:allow-set-focus","core:window:allow-set-icon","core:window:allow-set-skip-taskbar","core:window:allow-set-cursor-grab","core:window:allow-set-cursor-visible","core:window:allow-set-cursor-icon","core:window:allow-set-cursor-position","core:window:allow-set-ignore-cursor-events","core:window:allow-start-dragging","core:webview:allow-print","dialog:allow-open","dialog:allow-save","dialog:allow-message","dialog:allow-ask","dialog:allow-confirm","notification:default","process:allow-restart","process:allow-exit","notification:default","process:default","dialog:default","fs:default"]}}
|
4933
src-tauri/gen/schemas/desktop-schema.json
Normal file
4933
src-tauri/gen/schemas/desktop-schema.json
Normal file
File diff suppressed because it is too large
Load diff
4933
src-tauri/gen/schemas/linux-schema.json
Normal file
4933
src-tauri/gen/schemas/linux-schema.json
Normal file
File diff suppressed because it is too large
Load diff
2
src-tauri/src/constants.rs
Normal file
2
src-tauri/src/constants.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub const APP_NAME: &str = "stort";
|
||||
pub const APP_TITLE: &str = "Stort";
|
|
@ -104,3 +104,8 @@ pub fn delete_file(file_path: &std::path::Path) -> std::io::Result<()> {
|
|||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_directory(directory_path: &std::path::Path) -> std::io::Result<()> {
|
||||
// attempt to create the directory
|
||||
std::fs::create_dir_all(directory_path)
|
||||
}
|
||||
|
|
11
src-tauri/src/initialize.rs
Normal file
11
src-tauri/src/initialize.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
pub fn initialize_directories() {
|
||||
// getting the directories will also create them if they dont exist
|
||||
// get the app data directory
|
||||
crate::paths::get_app_data_dir().expect("Failed to get or create app data directory");
|
||||
|
||||
// get the app cache directory
|
||||
crate::paths::get_app_data_dir().expect("Failed to get or create app data directory");
|
||||
|
||||
// get the app config directory
|
||||
crate::paths::get_app_data_dir().expect("Failed to get or create app data directory");
|
||||
}
|
|
@ -1,3 +1,6 @@
|
|||
pub mod constants;
|
||||
pub mod fs;
|
||||
pub mod initialize;
|
||||
pub mod paths;
|
||||
pub mod tauri;
|
||||
pub mod wallpaper;
|
||||
|
|
|
@ -2,11 +2,6 @@
|
|||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
app::wallpaper::process_wallpaper_image,
|
||||
app::wallpaper::delete_old_wallpaper_image,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
app::initialize::initialize_directories();
|
||||
app::tauri::run_tauri_app();
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ pub fn get_app_data_dir() -> Result<std::path::PathBuf, String> {
|
|||
// attempt to get the app data directory
|
||||
match tauri::api::path::data_dir() {
|
||||
Some(data_dir) => {
|
||||
let app_data_dir = data_dir.join("stort/");
|
||||
let app_data_dir = data_dir.join(crate::constants::APP_NAME);
|
||||
// ensure the directory exists
|
||||
if !app_data_dir.exists() {
|
||||
// attempt to create the directory
|
||||
|
@ -18,3 +18,45 @@ pub fn get_app_data_dir() -> Result<std::path::PathBuf, String> {
|
|||
None => Err("Failed to get app data directory".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_app_cache_dir() -> Result<std::path::PathBuf, String> {
|
||||
// attempt to get the app cache directory
|
||||
match tauri::api::path::cache_dir() {
|
||||
Some(cache_dir) => {
|
||||
let app_cache_dir = cache_dir.join(crate::constants::APP_NAME);
|
||||
// ensure the directory exists
|
||||
if !app_cache_dir.exists() {
|
||||
// attempt to create the directory
|
||||
std::fs::create_dir_all(&app_cache_dir).map_err(|e| {
|
||||
format!(
|
||||
"Failed to create app cache directory {:?}: {}",
|
||||
app_cache_dir, e
|
||||
)
|
||||
})?;
|
||||
}
|
||||
Ok(app_cache_dir)
|
||||
}
|
||||
None => Err("Failed to get app cache directory".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_app_config_dir() -> Result<std::path::PathBuf, String> {
|
||||
// attempt to get the app config directory
|
||||
match tauri::api::path::config_dir() {
|
||||
Some(config_dir) => {
|
||||
let app_config_dir = config_dir.join(crate::constants::APP_NAME);
|
||||
// ensure the directory exists
|
||||
if !app_config_dir.exists() {
|
||||
// attempt to create the directory
|
||||
std::fs::create_dir_all(&app_config_dir).map_err(|e| {
|
||||
format!(
|
||||
"Failed to create app config directory {:?}: {}",
|
||||
app_config_dir, e
|
||||
)
|
||||
})?;
|
||||
}
|
||||
Ok(app_config_dir)
|
||||
}
|
||||
None => Err("Failed to get app config directory".to_string()),
|
||||
}
|
||||
}
|
||||
|
|
9
src-tauri/src/tauri.rs
Normal file
9
src-tauri/src/tauri.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
pub fn run_tauri_app() {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
crate::wallpaper::process_wallpaper_image,
|
||||
crate::wallpaper::delete_old_wallpaper_images,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
|
@ -11,11 +11,13 @@ enum ImageType {
|
|||
/// function to interface with the tauri api on the javascript side
|
||||
#[tauri::command]
|
||||
pub fn process_wallpaper_image(file_path_string: String) -> Result<String, String> {
|
||||
println!("{file_path_string}");
|
||||
|
||||
// convert the string to a path
|
||||
let file_path = std::path::Path::new(&file_path_string);
|
||||
|
||||
// determine the image type and get the file extension
|
||||
let file_extension = match determine_image_type(file_path) {
|
||||
match determine_image_type(file_path) {
|
||||
Ok(image_type) => match image_type {
|
||||
ImageType::Jpeg => "jpeg".to_string(),
|
||||
ImageType::Png => "png".to_string(),
|
||||
|
@ -33,7 +35,9 @@ pub fn process_wallpaper_image(file_path_string: String) -> Result<String, Strin
|
|||
.map_err(|e| format!("Failed to get app data directory: {e}"))?;
|
||||
|
||||
// construct the destination path
|
||||
let destination_path = app_data_dir.join(format!("wallpaper.{}", file_extension));
|
||||
let destination_path = app_data_dir
|
||||
.join("wallpaper")
|
||||
.join(file_path.file_name().unwrap());
|
||||
|
||||
// move the file to the destination
|
||||
crate::fs::copy_file(file_path, &destination_path, true)
|
||||
|
@ -43,12 +47,30 @@ pub fn process_wallpaper_image(file_path_string: String) -> Result<String, Strin
|
|||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn delete_old_wallpaper_image(file_path_string: String) -> Result<(), String> {
|
||||
// convert the strings to paths
|
||||
let file_path: &std::path::Path = std::path::Path::new(&file_path_string);
|
||||
pub fn delete_old_wallpaper_images() -> Result<(), String> {
|
||||
// convert the string to a Path
|
||||
let app_data_dir = crate::paths::get_app_data_dir()
|
||||
.map_err(|e| format!("Failed to get app data directory: {e}"))?;
|
||||
let directory_path = app_data_dir.join("wallpaper");
|
||||
|
||||
// delete the old wallpaper
|
||||
crate::fs::delete_file(file_path).map_err(|e| format!("Failed to delete file: {e}"))?;
|
||||
// check if the directory exists
|
||||
if !directory_path.is_dir() {
|
||||
return Err(format!("Path is not a directory: {:?}", directory_path));
|
||||
}
|
||||
|
||||
// iterate over the files in the directory
|
||||
for entry in
|
||||
std::fs::read_dir(directory_path).map_err(|e| format!("Failed to read directory: {e}"))?
|
||||
{
|
||||
let entry = entry.map_err(|e| format!("Failed to read directory entry: {e}"))?;
|
||||
let path = entry.path();
|
||||
|
||||
// delete the file
|
||||
if path.is_file() {
|
||||
std::fs::remove_file(&path)
|
||||
.map_err(|e| format!("Failed to delete file {:?}: {e}", path))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,79 +1,42 @@
|
|||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
|
||||
"build": {
|
||||
"beforeBuildCommand": "bun run build",
|
||||
"beforeDevCommand": "bun run dev",
|
||||
"devPath": "http://localhost:3000",
|
||||
"distDir": "../out"
|
||||
"beforeBuildCommand": "npm run build",
|
||||
"beforeDevCommand": "npm run dev",
|
||||
"frontendDist": "../out",
|
||||
"devUrl": "http://localhost:3000"
|
||||
},
|
||||
"package": {
|
||||
"productName": "stort",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
"dialog": {
|
||||
"all": true
|
||||
},
|
||||
"fs": {
|
||||
"all": true,
|
||||
"scope": [
|
||||
"**",
|
||||
"**/*",
|
||||
"/**/*",
|
||||
"$CONFIG/stort/",
|
||||
"$CONFIG/stort/**"
|
||||
]
|
||||
},
|
||||
"notification": {
|
||||
"all": true
|
||||
},
|
||||
"path": {
|
||||
"all": true
|
||||
},
|
||||
"process": {
|
||||
"all": true
|
||||
},
|
||||
"protocol": {
|
||||
"all": true,
|
||||
"asset": true,
|
||||
"assetScope": [
|
||||
"**",
|
||||
"**/*",
|
||||
"/**/*"
|
||||
]
|
||||
},
|
||||
"window": {
|
||||
"all": true
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"category": "DeveloperTool",
|
||||
"copyright": "",
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"category": "DeveloperTool",
|
||||
"copyright": "",
|
||||
"shortDescription": "Launcher for Steam Deck",
|
||||
"externalBin": [],
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"targets": [
|
||||
"appimage",
|
||||
"deb"
|
||||
],
|
||||
"longDescription": "Launcher for Steam Deck",
|
||||
"resources": [],
|
||||
"linux": {
|
||||
"deb": {
|
||||
"depends": []
|
||||
},
|
||||
"externalBin": [],
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"identifier": "com.vomitblood.stort",
|
||||
"longDescription": "Launcher for Steam Deck",
|
||||
"resources": [],
|
||||
"shortDescription": "Launcher for Steam Deck",
|
||||
"targets": [
|
||||
"appimage",
|
||||
"deb"
|
||||
]
|
||||
},
|
||||
"security": {
|
||||
"csp": null
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"productName": "stort",
|
||||
"mainBinaryName": "stort",
|
||||
"version": "0.1.0",
|
||||
"identifier": "stort",
|
||||
"plugins": {},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"decorations": false,
|
||||
|
@ -83,6 +46,16 @@
|
|||
"title": "Stort",
|
||||
"width": 800
|
||||
}
|
||||
]
|
||||
],
|
||||
"security": {
|
||||
"assetProtocol": {
|
||||
"scope": [
|
||||
"$APPDATA/*",
|
||||
"$APPDATA/**"
|
||||
],
|
||||
"enable": true
|
||||
},
|
||||
"csp": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
import { Settings } from "@mui/icons-material";
|
||||
import { Box, Stack } from "@mui/material";
|
||||
import { useRouter } from "next/router";
|
||||
import { WindowButtons } from "./HeaderBar/WindowButtons";
|
||||
|
||||
export const FooterBar = () => {
|
||||
return (
|
||||
<Box
|
||||
className="titlebar"
|
||||
data-tauri-drag-region="true"
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
backdropFilter: "blur(10px)",
|
||||
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
height: "48px",
|
||||
justifyContent: "space-between",
|
||||
p: 1,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
className="titlebar"
|
||||
data-tauri-drag-region="true"
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
}}
|
||||
>
|
||||
hello this is the left side
|
||||
</Box>
|
||||
<Box
|
||||
className="titlebar"
|
||||
data-tauri-drag-region="true"
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={2}
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
}}
|
||||
>
|
||||
<Settings />
|
||||
<WindowButtons />
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,84 @@
|
|||
import { Box, Stack } from "@mui/material";
|
||||
import { useSettings } from "../../contexts/SettingsContext";
|
||||
import { hexToRgba } from "../../lib/utils/color";
|
||||
import { Settings } from "../HeaderBar/Settings/Settings";
|
||||
import { WindowButtons } from "../HeaderBar/WindowButtons";
|
||||
|
||||
export const FooterBar = () => {
|
||||
// contexts
|
||||
const { settings } = useSettings();
|
||||
|
||||
const { r, g, b, a } = hexToRgba(settings.colors.footer_color);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
height: "66px",
|
||||
// zIndex: 1000000,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
className="titlebar"
|
||||
data-tauri-drag-region="true"
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
backdropFilter: `blur(${settings.style.blur_radius}px)`,
|
||||
backgroundColor: `rgba(${r}, ${g}, ${b}, 0.5)`,
|
||||
borderRadius: settings.style.radius + "px",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
flexGrow: 1,
|
||||
m: 1,
|
||||
p: 1,
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
className="titlebar"
|
||||
data-tauri-drag-region="true"
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
}}
|
||||
>
|
||||
hello this is the left side
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
flexGrow: 1,
|
||||
justifyContent: "center",
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
className="titlebar"
|
||||
data-tauri-drag-region="true"
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={2}
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
}}
|
||||
>
|
||||
<Settings />
|
||||
<WindowButtons />
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
|
@ -1,7 +1,8 @@
|
|||
import { Close, UnfoldLess, UnfoldMore } from "@mui/icons-material";
|
||||
import { Box, IconButton, Modal, Tooltip, Typography } from "@mui/material";
|
||||
import { Box, IconButton, Modal, Tooltip, Typography, useTheme } from "@mui/material";
|
||||
import { FC, ReactNode } from "react";
|
||||
import { useSettings } from "../../contexts/SettingsContext";
|
||||
import { hexToRgba } from "../../lib/utils/color";
|
||||
|
||||
interface FloatingDialog {
|
||||
actionButtons?: ReactNode;
|
||||
|
@ -28,7 +29,11 @@ export const FloatingDialog: FC<FloatingDialog> = ({
|
|||
title,
|
||||
sx,
|
||||
}) => {
|
||||
// contexts
|
||||
const { settings } = useSettings();
|
||||
const theme = useTheme();
|
||||
|
||||
const { r, g, b, a } = hexToRgba(theme.palette.background.paper);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -40,7 +45,8 @@ export const FloatingDialog: FC<FloatingDialog> = ({
|
|||
>
|
||||
<Box
|
||||
sx={{
|
||||
bgcolor: "background.paper",
|
||||
backdropFilter: `blur(${settings.style.blur_radius}px)`,
|
||||
backgroundColor: `rgba(${r}, ${g}, ${b}, ${settings.style.opacity})`,
|
||||
borderRadius: maximisedState ? "0px" : settings.style.radius + "px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
import { createDir, exists } from "@tauri-apps/api/fs";
|
||||
import { useEffect } from "react";
|
||||
import { useSettings } from "../../contexts/SettingsContext";
|
||||
import Paths from "../../lib/path";
|
||||
|
||||
export const Initialization = () => {
|
||||
const { settings, settingsLoading } = useSettings();
|
||||
|
||||
useEffect(() => {
|
||||
const initializePaths = async () => {
|
||||
try {
|
||||
await Paths.initialize();
|
||||
} catch (error) {
|
||||
console.error(`Failed to initialize paths: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
const createDirectories = async () => {
|
||||
const configDirectoryExists = await exists(Paths.getPath("configDirectory"));
|
||||
if (!configDirectoryExists) await createDir(Paths.getPath("configDirectory"));
|
||||
};
|
||||
|
||||
const fullscreen = async () => {
|
||||
const { appWindow } = await import("@tauri-apps/api/window");
|
||||
await appWindow.setFullscreen(true);
|
||||
};
|
||||
|
||||
initializePaths().then(() => createDirectories());
|
||||
// TODO: race condition here, need to wait for settings to load
|
||||
if (settings.window.start_fullscreen) fullscreen();
|
||||
}, [settingsLoading]);
|
||||
|
||||
return null;
|
||||
};
|
|
@ -1,29 +1,34 @@
|
|||
import { Box, Button } from "@mui/material";
|
||||
import { convertFileSrc } from "@tauri-apps/api/tauri";
|
||||
import { useState } from "react";
|
||||
import { FooterBar } from "../FooterBar";
|
||||
import { HeaderBar } from "../HeaderBar/HeaderBar";
|
||||
import { Box, Button, useTheme } from "@mui/material";
|
||||
import { convertFileSrc } from "@tauri-apps/api/core";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSettings } from "../../contexts/SettingsContext";
|
||||
import { FooterBar } from "../FooterBar/FooterBar";
|
||||
import { HeaderBar } from "../HeaderBar/HeaderBar";
|
||||
|
||||
export const Layout = () => {
|
||||
// contexts
|
||||
const { settings } = useSettings();
|
||||
const theme = useTheme();
|
||||
|
||||
const [imageUrl, setImageUrl] = useState<string | null>(null);
|
||||
|
||||
const setBackground = async () => {
|
||||
const assetUrl = convertFileSrc("/home/vomitblood/Downloads/toothless-dance.gif");
|
||||
const setBackground = async (filePath: string) => {
|
||||
const assetUrl = convertFileSrc(filePath);
|
||||
setImageUrl(assetUrl);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setBackground(settings.background.background_image_path);
|
||||
}, [settings.background.background_image_path]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
// Use the URL function for background images
|
||||
backgroundColor: settings.background.background_color,
|
||||
backgroundImage: `url(${imageUrl})`,
|
||||
backgroundSize: "cover", // Cover the entire area
|
||||
backgroundPosition: "center", // Center the image
|
||||
backgroundRepeat: "no-repeat", // Do not repeat the image
|
||||
backgroundColor: theme.palette.background.default,
|
||||
backgroundImage: settings.background.background_image_path ? `url(${imageUrl})` : "",
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
backgroundRepeat: "no-repeat",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100vh",
|
||||
|
@ -35,18 +40,9 @@ export const Layout = () => {
|
|||
display: "flex",
|
||||
flexGrow: 1,
|
||||
overflow: "auto",
|
||||
p: 1,
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setBackground();
|
||||
}}
|
||||
>
|
||||
set background
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
></Box>
|
||||
<FooterBar />
|
||||
</Box>
|
||||
);
|
||||
|
|
|
@ -1,21 +1,28 @@
|
|||
import { BugReportOutlined, SettingsOutlined } from "@mui/icons-material";
|
||||
import { LoadingButton, TabContext, TabList, TabPanel } from "@mui/lab";
|
||||
import {
|
||||
BugReportOutlined,
|
||||
FormatPaintOutlined,
|
||||
PaletteOutlined,
|
||||
SettingsOutlined,
|
||||
WallpaperOutlined,
|
||||
WebAssetOutlined,
|
||||
} from "@mui/icons-material";
|
||||
import { TabContext, TabList, TabPanel } from "@mui/lab";
|
||||
import { Box, Button, IconButton, Tab, Tooltip, useTheme } from "@mui/material";
|
||||
import { useAtom } from "jotai";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSettings } from "../../../contexts/SettingsContext";
|
||||
import { stagedSettingsAtom } from "../../../lib/store/jotai/settings";
|
||||
import { FloatingDialog } from "../../Generic/FloatingDialog";
|
||||
import { SettingsTabStyle } from "./SettingsTabs/SettingsTabStyle";
|
||||
import { SettingsTabWindow } from "./SettingsTabs/SettingsTabWindow";
|
||||
import { SettingsTabBackground } from "./SettingsTabs/SettingsTabBackground";
|
||||
import { Background } from "./SettingsTabs/Background";
|
||||
import { Debug } from "./SettingsTabs/Debug";
|
||||
import { Style } from "./SettingsTabs/Style";
|
||||
import { Window } from "./SettingsTabs/Window";
|
||||
import { Colors } from "./SettingsTabs/Colors";
|
||||
|
||||
export const Settings = () => {
|
||||
// contexts
|
||||
const theme = useTheme();
|
||||
const { settings, updateSettings } = useSettings();
|
||||
const router = useRouter();
|
||||
const { fetchSettings, settings, updateSettings } = useSettings();
|
||||
|
||||
// atoms
|
||||
const [stagedSettings, setStagedSettings] = useAtom(stagedSettingsAtom);
|
||||
|
@ -24,8 +31,6 @@ export const Settings = () => {
|
|||
const [settingsOpenState, setSettingsOpenState] = useState<boolean>(false);
|
||||
const [settingsMaximisedState, setSettingsMaximisedState] = useState<boolean>(false);
|
||||
const [subTabValue, setSubTabValue] = useState("style");
|
||||
const [applyLoading, setApplyLoading] = useState<boolean>(false);
|
||||
const [saveLoading, setSaveLoading] = useState<boolean>(false);
|
||||
|
||||
const toggleSettings = () => {
|
||||
setSettingsOpenState((prevState) => !prevState);
|
||||
|
@ -40,43 +45,31 @@ export const Settings = () => {
|
|||
setSubTabValue(newTabValue);
|
||||
};
|
||||
|
||||
// set staged settings back to current settings on cancel
|
||||
const cancelClickEvent = () => {
|
||||
setStagedSettings(settings);
|
||||
setSettingsOpenState(false);
|
||||
};
|
||||
|
||||
const applyClickEvent = () => {
|
||||
setApplyLoading(true);
|
||||
|
||||
updateSettings(stagedSettings);
|
||||
|
||||
setApplyLoading(false);
|
||||
};
|
||||
|
||||
const saveClickEvent = () => {
|
||||
setSaveLoading(true);
|
||||
applyClickEvent();
|
||||
|
||||
updateSettings(stagedSettings);
|
||||
|
||||
setSaveLoading(false);
|
||||
closeSettings();
|
||||
};
|
||||
|
||||
// set staged settings back to current settings on close
|
||||
useEffect(() => {
|
||||
setStagedSettings(settings);
|
||||
}, [setStagedSettings, settings]);
|
||||
if (settingsOpenState) fetchSettings();
|
||||
if (!settingsOpenState) setStagedSettings(settings);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [settingsOpenState]);
|
||||
|
||||
return (
|
||||
<FloatingDialog
|
||||
actionButtons={
|
||||
<>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
router.push("/testing");
|
||||
}}
|
||||
sx={{
|
||||
mr: 1,
|
||||
}}
|
||||
>
|
||||
<BugReportOutlined />
|
||||
</IconButton>
|
||||
</>
|
||||
}
|
||||
body={
|
||||
<Box
|
||||
sx={{
|
||||
|
@ -98,26 +91,41 @@ export const Settings = () => {
|
|||
scrollButtons={true}
|
||||
sx={{
|
||||
borderBottom: "1px solid " + theme.palette.divider,
|
||||
height: "84px",
|
||||
}}
|
||||
variant="scrollable"
|
||||
>
|
||||
<Tab
|
||||
icon={<FormatPaintOutlined />}
|
||||
label="Style"
|
||||
value="style"
|
||||
/>
|
||||
<Tab
|
||||
icon={<PaletteOutlined />}
|
||||
label="Colors"
|
||||
value="colors"
|
||||
/>
|
||||
<Tab
|
||||
icon={<WallpaperOutlined />}
|
||||
label="Background"
|
||||
value="background"
|
||||
/>
|
||||
<Tab
|
||||
icon={<WebAssetOutlined />}
|
||||
label="Window"
|
||||
value="window"
|
||||
/>
|
||||
<Tab
|
||||
icon={<BugReportOutlined />}
|
||||
label="Debug"
|
||||
value="debug"
|
||||
/>
|
||||
</TabList>
|
||||
<Box
|
||||
overflow="auto"
|
||||
sx={{
|
||||
height: "100%",
|
||||
overflowY: "auto",
|
||||
m: 0,
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
|
@ -125,19 +133,31 @@ export const Settings = () => {
|
|||
sx={{ p: 2 }}
|
||||
value="style"
|
||||
>
|
||||
<SettingsTabStyle />
|
||||
<Style />
|
||||
</TabPanel>
|
||||
<TabPanel
|
||||
sx={{ p: 2 }}
|
||||
value="colors"
|
||||
>
|
||||
<Colors />
|
||||
</TabPanel>
|
||||
<TabPanel
|
||||
sx={{ p: 2 }}
|
||||
value="background"
|
||||
>
|
||||
<SettingsTabBackground />
|
||||
<Background />
|
||||
</TabPanel>
|
||||
<TabPanel
|
||||
sx={{ p: 2 }}
|
||||
value="window"
|
||||
>
|
||||
<SettingsTabWindow />
|
||||
<Window />
|
||||
</TabPanel>
|
||||
<TabPanel
|
||||
sx={{ p: 2 }}
|
||||
value="debug"
|
||||
>
|
||||
<Debug />
|
||||
</TabPanel>
|
||||
</Box>
|
||||
</TabContext>
|
||||
|
@ -152,7 +172,7 @@ export const Settings = () => {
|
|||
}}
|
||||
>
|
||||
<Button
|
||||
onClick={() => setSettingsOpenState(false)}
|
||||
onClick={cancelClickEvent}
|
||||
size="small"
|
||||
sx={{
|
||||
mr: 1,
|
||||
|
@ -162,8 +182,7 @@ export const Settings = () => {
|
|||
Cancel
|
||||
</Button>
|
||||
|
||||
<LoadingButton
|
||||
loading={applyLoading}
|
||||
<Button
|
||||
onClick={applyClickEvent}
|
||||
size="small"
|
||||
sx={{
|
||||
|
@ -172,15 +191,14 @@ export const Settings = () => {
|
|||
variant="outlined"
|
||||
>
|
||||
Apply
|
||||
</LoadingButton>
|
||||
<LoadingButton
|
||||
loading={saveLoading}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={saveClickEvent}
|
||||
size="small"
|
||||
variant="contained"
|
||||
>
|
||||
Save
|
||||
</LoadingButton>
|
||||
</Button>
|
||||
</Box>
|
||||
}
|
||||
close={closeSettings}
|
||||
|
|
222
src/components/HeaderBar/Settings/SettingsTabs/Background.tsx
Normal file
222
src/components/HeaderBar/Settings/SettingsTabs/Background.tsx
Normal file
|
@ -0,0 +1,222 @@
|
|||
import { DeleteOutline, FileOpenOutlined } from "@mui/icons-material";
|
||||
import { Box, Button, CircularProgress, LinearProgress, Stack, TextField, Typography } from "@mui/material";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { readBinaryFile } from "@tauri-apps/plugin-fs";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useAtom } from "jotai";
|
||||
import Image from "next/image";
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { useSettings } from "../../../../contexts/SettingsContext";
|
||||
import { defaultSettings } from "../../../../lib/settings";
|
||||
import { stagedSettingsAtom } from "../../../../lib/store/jotai/settings";
|
||||
import { CategoryTitle } from "../CategoryTitle";
|
||||
import { SettingsItem } from "../SettingsItem";
|
||||
|
||||
interface BackgroundProps {
|
||||
sx?: any;
|
||||
}
|
||||
|
||||
export const Background: FC<BackgroundProps> = ({ sx }) => {
|
||||
// contexts
|
||||
const { settings } = useSettings();
|
||||
|
||||
// atoms
|
||||
const [stagedSettings, setStagedSettings] = useAtom(stagedSettingsAtom);
|
||||
|
||||
// states
|
||||
const [oldWallpaperPath, setOldWallpaperPath] = useState<string | null>(null);
|
||||
const [targetWallpaperPath, setTargetWallpaperPath] = useState<string | null>(null);
|
||||
const [imageBlob, setImageBlob] = useState<string | null>(null);
|
||||
|
||||
const handleSettingsBackgroundValueChange = (
|
||||
settingKey: string,
|
||||
settingValue: boolean | number | string | number[],
|
||||
) => {
|
||||
const newSettings = {
|
||||
...stagedSettings,
|
||||
background: {
|
||||
...stagedSettings.background,
|
||||
[settingKey]: settingValue,
|
||||
},
|
||||
};
|
||||
setStagedSettings(newSettings);
|
||||
|
||||
return newSettings;
|
||||
};
|
||||
|
||||
const setImageSrc = async (filePath: string) => {
|
||||
const imageBlobTemp = await readBinaryFile(filePath);
|
||||
|
||||
if (imageBlobTemp) setImageBlob(URL.createObjectURL(new Blob([imageBlobTemp])));
|
||||
};
|
||||
|
||||
const selectImage = async () => {
|
||||
const { appLocalDataDir, basename } = await import("@tauri-apps/api/path");
|
||||
|
||||
// clear the states first
|
||||
setTargetWallpaperPath(null);
|
||||
setImageBlob(null);
|
||||
|
||||
let selectedFilePath = await open({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: "Images",
|
||||
extensions: ["jpg", "png", "jpeg", "webp", "gif"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// if the user somehow manages to select multiple files, take the first file
|
||||
if (Array.isArray(selectedFilePath)) {
|
||||
selectedFilePath = selectedFilePath[0];
|
||||
}
|
||||
|
||||
if (selectedFilePath) {
|
||||
setTargetWallpaperPath(selectedFilePath);
|
||||
|
||||
// construct the destination file path
|
||||
const appLocalDataDirPath = await appLocalDataDir();
|
||||
const filename = await basename(selectedFilePath);
|
||||
const destinationFilePath = appLocalDataDirPath + "wallpaper/" + filename;
|
||||
handleSettingsBackgroundValueChange("background_image_path", destinationFilePath);
|
||||
}
|
||||
};
|
||||
|
||||
const clearImage = async () => {
|
||||
handleSettingsBackgroundValueChange("background_image_path", "");
|
||||
};
|
||||
|
||||
// if settings.background.background_image_path changes, update the image
|
||||
useEffect(() => {
|
||||
const applyWallpaper = async () => {
|
||||
// apply the new wallpaper image
|
||||
try {
|
||||
// if there is already a wallpaper file, delete it
|
||||
if (settings.background.background_image_path && oldWallpaperPath) {
|
||||
try {
|
||||
await invoke("delete_old_wallpaper_images");
|
||||
} catch (error) {
|
||||
console.error("Failed to delete old wallpaper image", error);
|
||||
}
|
||||
}
|
||||
if (targetWallpaperPath)
|
||||
await invoke("process_wallpaper_image", {
|
||||
filePathString: targetWallpaperPath,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
applyWallpaper();
|
||||
|
||||
setOldWallpaperPath(settings.background.background_image_path);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [settings.background.background_image_path]);
|
||||
|
||||
// update the preview image when stagedSettings.background.background_image_path changes
|
||||
useEffect(() => {
|
||||
if (targetWallpaperPath) {
|
||||
setImageSrc(targetWallpaperPath);
|
||||
} else {
|
||||
setImageBlob(null);
|
||||
}
|
||||
}, [targetWallpaperPath]);
|
||||
|
||||
useEffect(() => {});
|
||||
|
||||
return (
|
||||
<Box sx={{ sx }}>
|
||||
<CategoryTitle title="Wallpaper" />
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
// dynamic width based on the parent container
|
||||
width: "100%",
|
||||
// 3:2 aspect ratio (2/3 = 66.67%) qwuik mafs
|
||||
paddingBottom: "66.67%",
|
||||
// Hide overflow to maintain aspect ratio
|
||||
overflow: "hidden",
|
||||
borderRadius: "8px", // Optional: rounded corners
|
||||
}}
|
||||
>
|
||||
{imageBlob ? (
|
||||
<Image
|
||||
alt="Image not found"
|
||||
// fill the box r/catsareliquid
|
||||
layout="fill"
|
||||
objectFit="cover"
|
||||
src={imageBlob}
|
||||
/>
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
backgroundColor: "rgba(0, 0, 0, 0.1)",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
justifyContent: "center",
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
{targetWallpaperPath ? (
|
||||
<CircularProgress color="primary" />
|
||||
) : (
|
||||
<>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
alt="Image not found"
|
||||
src="/images/cry.webp"
|
||||
style={{
|
||||
height: "50%",
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
color="text.disabled"
|
||||
variant="h6"
|
||||
>
|
||||
No image selected
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
mb: 2,
|
||||
mt: 1,
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={1}
|
||||
>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
selectImage();
|
||||
}}
|
||||
startIcon={<FileOpenOutlined />}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
>
|
||||
Select
|
||||
</Button>
|
||||
<Button
|
||||
color="warning"
|
||||
onClick={clearImage}
|
||||
startIcon={<DeleteOutline />}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
112
src/components/HeaderBar/Settings/SettingsTabs/Colors.tsx
Normal file
112
src/components/HeaderBar/Settings/SettingsTabs/Colors.tsx
Normal file
|
@ -0,0 +1,112 @@
|
|||
import { Box, TextField } from "@mui/material";
|
||||
import { useAtom } from "jotai";
|
||||
import { FC } from "react";
|
||||
import { defaultSettings } from "../../../../lib/settings";
|
||||
import { stagedSettingsAtom } from "../../../../lib/store/jotai/settings";
|
||||
import { CategoryTitle } from "../CategoryTitle";
|
||||
import { SettingsItem } from "../SettingsItem";
|
||||
|
||||
interface ColorsProps {
|
||||
sx?: any;
|
||||
}
|
||||
|
||||
export const Colors: FC<ColorsProps> = ({ sx }) => {
|
||||
// atoms
|
||||
const [stagedSettings, setStagedSettings] = useAtom(stagedSettingsAtom);
|
||||
|
||||
// states
|
||||
const handleSettingsColorsValueChange = (settingKey: string, settingValue: boolean | number | string | number[]) => {
|
||||
const newSettings = {
|
||||
...stagedSettings,
|
||||
colors: {
|
||||
...stagedSettings.colors,
|
||||
[settingKey]: settingValue,
|
||||
},
|
||||
};
|
||||
setStagedSettings(newSettings);
|
||||
|
||||
return newSettings;
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ sx }}>
|
||||
<CategoryTitle title="Colors" />
|
||||
<SettingsItem
|
||||
defaultText={defaultSettings.colors.accent_color}
|
||||
description="Accent color"
|
||||
input={
|
||||
<TextField
|
||||
name="accent_color"
|
||||
onChange={(e) => {
|
||||
handleSettingsColorsValueChange(e.target.name, e.target.value);
|
||||
}}
|
||||
sx={{
|
||||
width: "100%",
|
||||
}}
|
||||
size="small"
|
||||
type="color"
|
||||
value={stagedSettings.colors.accent_color}
|
||||
variant="standard"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingsItem
|
||||
defaultText={defaultSettings.colors.background_color}
|
||||
description="Background color"
|
||||
input={
|
||||
<TextField
|
||||
name="background_color"
|
||||
onChange={(e) => {
|
||||
handleSettingsColorsValueChange(e.target.name, e.target.value);
|
||||
}}
|
||||
sx={{
|
||||
width: "100%",
|
||||
}}
|
||||
size="small"
|
||||
type="color"
|
||||
value={stagedSettings.colors.background_color}
|
||||
variant="standard"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingsItem
|
||||
defaultText={defaultSettings.colors.background_color_popup}
|
||||
description="Popup background color"
|
||||
input={
|
||||
<TextField
|
||||
name="background_color_popup"
|
||||
onChange={(e) => {
|
||||
handleSettingsColorsValueChange(e.target.name, e.target.value);
|
||||
}}
|
||||
sx={{
|
||||
width: "100%",
|
||||
}}
|
||||
size="small"
|
||||
type="color"
|
||||
value={stagedSettings.colors.background_color_popup}
|
||||
variant="standard"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingsItem
|
||||
defaultText={defaultSettings.colors.footer_color}
|
||||
description="Footer color"
|
||||
input={
|
||||
<TextField
|
||||
name="footer_color"
|
||||
onChange={(e) => {
|
||||
handleSettingsColorsValueChange(e.target.name, e.target.value);
|
||||
}}
|
||||
sx={{
|
||||
width: "100%",
|
||||
}}
|
||||
size="small"
|
||||
type="color"
|
||||
value={stagedSettings.colors.footer_color}
|
||||
variant="standard"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
52
src/components/HeaderBar/Settings/SettingsTabs/Debug.tsx
Normal file
52
src/components/HeaderBar/Settings/SettingsTabs/Debug.tsx
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { Box, Typography } from "@mui/material";
|
||||
import { FC } from "react";
|
||||
import { useSettings } from "../../../../contexts/SettingsContext";
|
||||
import { CategoryTitle } from "../CategoryTitle";
|
||||
import { stagedSettingsAtom } from "../../../../lib/store/jotai/settings";
|
||||
import { useAtom } from "jotai";
|
||||
import { defaultSettings } from "../../../../lib/settings";
|
||||
|
||||
interface DebugProps {
|
||||
sx?: any;
|
||||
}
|
||||
|
||||
export const Debug: FC<DebugProps> = ({ sx }) => {
|
||||
// contexts
|
||||
const { settings, resetSettings } = useSettings();
|
||||
|
||||
// atoms
|
||||
const [stagedSettings, setStagedSettings] = useAtom(stagedSettingsAtom);
|
||||
|
||||
return (
|
||||
<Box sx={{ sx }}>
|
||||
<CategoryTitle title="Debug Panel" />
|
||||
<Typography
|
||||
color="error"
|
||||
variant="h6"
|
||||
>
|
||||
Here do be dragons
|
||||
</Typography>
|
||||
<button
|
||||
onClick={() => {
|
||||
setStagedSettings(defaultSettings);
|
||||
}}
|
||||
>
|
||||
reset settings
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
console.log(settings);
|
||||
}}
|
||||
>
|
||||
log settings
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
console.log(settings.background.background_image_path);
|
||||
}}
|
||||
>
|
||||
testing
|
||||
</button>
|
||||
</Box>
|
||||
);
|
||||
};
|
|
@ -1,148 +0,0 @@
|
|||
import { DeleteOutline, FileOpenOutlined } from "@mui/icons-material";
|
||||
import { Box, Button, Stack, TextField } from "@mui/material";
|
||||
import { open } from "@tauri-apps/api/dialog";
|
||||
import { invoke } from "@tauri-apps/api/tauri";
|
||||
import { useAtom } from "jotai";
|
||||
import Image from "next/image";
|
||||
import { FC } from "react";
|
||||
import { stagedSettingsAtom } from "../../../../lib/store/jotai/settings";
|
||||
import { CategoryTitle } from "../CategoryTitle";
|
||||
import { SettingsItem } from "../SettingsItem";
|
||||
import Path from "../../../../lib/path";
|
||||
|
||||
interface SettingsTabBackgroundProps {
|
||||
sx?: any;
|
||||
}
|
||||
|
||||
export const SettingsTabBackground: FC<SettingsTabBackgroundProps> = ({ sx }) => {
|
||||
// atoms
|
||||
const [stagedSettings, setStagedSettings] = useAtom(stagedSettingsAtom);
|
||||
|
||||
const handleSettingsBackgroundValueChange = (
|
||||
settingKey: string,
|
||||
settingValue: boolean | number | string | number[],
|
||||
) => {
|
||||
const newSettings = {
|
||||
...stagedSettings,
|
||||
background: {
|
||||
...stagedSettings.background,
|
||||
[settingKey]: settingValue,
|
||||
},
|
||||
};
|
||||
setStagedSettings(newSettings);
|
||||
};
|
||||
|
||||
const selectImage = async () => {
|
||||
const { basename } = await import("@tauri-apps/api/path");
|
||||
|
||||
let selectedFilePath = await open({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
name: "Images",
|
||||
extensions: ["jpg", "png", "jpeg", "webp", "gif"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// if the user somehow manages to select multiple files, take the first file
|
||||
if (Array.isArray(selectedFilePath)) {
|
||||
selectedFilePath = selectedFilePath[0];
|
||||
}
|
||||
|
||||
if (selectedFilePath) {
|
||||
try {
|
||||
// get the last filename from the path
|
||||
const filename = await basename(selectedFilePath);
|
||||
|
||||
console.log(filename);
|
||||
console.log(selectedFilePath);
|
||||
|
||||
// if there is already a wallpaper file, delete it
|
||||
if (stagedSettings.background.background_image_path) {
|
||||
try {
|
||||
await invoke("delete_old_wallpaper_image", {
|
||||
filePathString: stagedSettings.background.background_image_path,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to delete old wallpaper image", error);
|
||||
}
|
||||
}
|
||||
|
||||
const destinationFilePath = (await invoke("process_wallpaper_image", {
|
||||
filePathString: selectedFilePath,
|
||||
})) as string;
|
||||
|
||||
handleSettingsBackgroundValueChange("background_image_path", destinationFilePath);
|
||||
} catch (error) {
|
||||
console.error("deec nuts", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: implement
|
||||
const clearImage = async () => {};
|
||||
|
||||
return (
|
||||
<Box sx={{ sx }}>
|
||||
<CategoryTitle title="Wallpaper" />
|
||||
<Box>
|
||||
<Image
|
||||
src="/wallpaper.jpg"
|
||||
alt="Image not found"
|
||||
width={200}
|
||||
height={200}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
mb: 2,
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={1}
|
||||
>
|
||||
<Button
|
||||
color="primary"
|
||||
onClick={selectImage}
|
||||
startIcon={<FileOpenOutlined />}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
>
|
||||
Select
|
||||
</Button>
|
||||
<Button
|
||||
color="warning"
|
||||
onClick={clearImage}
|
||||
startIcon={<DeleteOutline />}
|
||||
size="small"
|
||||
variant="outlined"
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
<CategoryTitle title="Colors" />
|
||||
<SettingsItem
|
||||
defaultText="#202124"
|
||||
description="Background color"
|
||||
input={
|
||||
<TextField
|
||||
name="background_color"
|
||||
onChange={(e) => {
|
||||
handleSettingsBackgroundValueChange(e.target.name, e.target.value);
|
||||
}}
|
||||
sx={{
|
||||
width: "100%",
|
||||
}}
|
||||
size="small"
|
||||
type="color"
|
||||
value={stagedSettings.background.background_color}
|
||||
variant="standard"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
|
@ -1,17 +1,18 @@
|
|||
import { Box, InputAdornment, MenuItem, Slider, Switch, TextField, Typography } from "@mui/material";
|
||||
import { Box, InputAdornment, MenuItem, Slider, Switch, TextField } from "@mui/material";
|
||||
import { useAtom } from "jotai";
|
||||
import { FC } from "react";
|
||||
import { defaultSettings } from "../../../../lib/settings";
|
||||
import { stagedSettingsAtom } from "../../../../lib/store/jotai/settings";
|
||||
import { BetaChip } from "../BetaChip";
|
||||
import { CategoryTitle } from "../CategoryTitle";
|
||||
import { DevChip } from "../DevChip";
|
||||
import { SettingsItem } from "../SettingsItem";
|
||||
|
||||
interface SettingsTabStyleProps {
|
||||
interface StyleProps {
|
||||
sx?: any;
|
||||
}
|
||||
|
||||
export const SettingsTabStyle: FC<SettingsTabStyleProps> = ({ sx }) => {
|
||||
export const Style: FC<StyleProps> = ({ sx }) => {
|
||||
// atoms
|
||||
const [stagedSettings, setStagedSettings] = useAtom(stagedSettingsAtom);
|
||||
|
||||
|
@ -24,13 +25,15 @@ export const SettingsTabStyle: FC<SettingsTabStyleProps> = ({ sx }) => {
|
|||
},
|
||||
};
|
||||
setStagedSettings(newSettings);
|
||||
|
||||
return newSettings;
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ sx }}>
|
||||
<CategoryTitle title="Basic styles" />
|
||||
<SettingsItem
|
||||
defaultText="On"
|
||||
defaultText={defaultSettings.style.dark_mode ? "On" : "Off"}
|
||||
description={
|
||||
<>
|
||||
<BetaChip />
|
||||
|
@ -48,26 +51,7 @@ export const SettingsTabStyle: FC<SettingsTabStyleProps> = ({ sx }) => {
|
|||
}
|
||||
/>
|
||||
<SettingsItem
|
||||
defaultText="#8ab4f8"
|
||||
description="Accent color"
|
||||
input={
|
||||
<TextField
|
||||
name="accent_color"
|
||||
onChange={(e) => {
|
||||
handleSettingsStyleValueChange(e.target.name, e.target.value);
|
||||
}}
|
||||
sx={{
|
||||
width: "100%",
|
||||
}}
|
||||
size="small"
|
||||
type="color"
|
||||
value={stagedSettings.style.accent_color}
|
||||
variant="standard"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingsItem
|
||||
defaultText="Monospace"
|
||||
defaultText={defaultSettings.style.font_family}
|
||||
description="Font family"
|
||||
inputLong={
|
||||
<TextField
|
||||
|
@ -92,7 +76,7 @@ export const SettingsTabStyle: FC<SettingsTabStyleProps> = ({ sx }) => {
|
|||
}
|
||||
/>
|
||||
<SettingsItem
|
||||
defaultText="100%"
|
||||
defaultText={defaultSettings.style.font_scaling + "%"}
|
||||
description="Font scaling"
|
||||
inputBottom={
|
||||
<Slider
|
||||
|
@ -132,20 +116,20 @@ export const SettingsTabStyle: FC<SettingsTabStyleProps> = ({ sx }) => {
|
|||
/>
|
||||
<CategoryTitle title="Advanced settings" />
|
||||
<SettingsItem
|
||||
defaultText="200ms"
|
||||
defaultText={defaultSettings.style.blur_radius + "px"}
|
||||
description={
|
||||
<>
|
||||
<DevChip />
|
||||
Transition duration
|
||||
Blur radius
|
||||
</>
|
||||
}
|
||||
input={
|
||||
<TextField
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">ms</InputAdornment>,
|
||||
endAdornment: <InputAdornment position="end">px</InputAdornment>,
|
||||
inputProps: { min: 0, max: 100, step: 1 },
|
||||
}}
|
||||
name="transition_duration"
|
||||
name="blur_radius"
|
||||
onChange={(e) => {
|
||||
handleSettingsStyleValueChange(e.target.name, parseFloat(e.target.value));
|
||||
}}
|
||||
|
@ -154,13 +138,13 @@ export const SettingsTabStyle: FC<SettingsTabStyleProps> = ({ sx }) => {
|
|||
}}
|
||||
size="small"
|
||||
type="number"
|
||||
value={stagedSettings.style.transition_duration}
|
||||
value={stagedSettings.style.blur_radius}
|
||||
variant="standard"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingsItem
|
||||
defaultText="8px"
|
||||
defaultText={defaultSettings.style.radius + "px"}
|
||||
description={
|
||||
<>
|
||||
<DevChip />
|
||||
|
@ -188,7 +172,62 @@ export const SettingsTabStyle: FC<SettingsTabStyleProps> = ({ sx }) => {
|
|||
}
|
||||
/>
|
||||
<SettingsItem
|
||||
defaultText="60%"
|
||||
defaultText={defaultSettings.style.opacity.toString()}
|
||||
description={
|
||||
<>
|
||||
<DevChip />
|
||||
Opacity
|
||||
</>
|
||||
}
|
||||
input={
|
||||
<TextField
|
||||
InputProps={{
|
||||
inputProps: { min: 0, max: 1, step: 0.01 },
|
||||
}}
|
||||
name="opacity"
|
||||
onChange={(e) => {
|
||||
handleSettingsStyleValueChange(e.target.name, parseFloat(e.target.value));
|
||||
}}
|
||||
sx={{
|
||||
width: "100%",
|
||||
}}
|
||||
size="small"
|
||||
type="number"
|
||||
value={stagedSettings.style.opacity}
|
||||
variant="standard"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingsItem
|
||||
defaultText={defaultSettings.style.transition_duration.toString()}
|
||||
description={
|
||||
<>
|
||||
<DevChip />
|
||||
Transition duration
|
||||
</>
|
||||
}
|
||||
input={
|
||||
<TextField
|
||||
InputProps={{
|
||||
endAdornment: <InputAdornment position="end">ms</InputAdornment>,
|
||||
inputProps: { min: 0, max: 100, step: 1 },
|
||||
}}
|
||||
name="transition_duration"
|
||||
onChange={(e) => {
|
||||
handleSettingsStyleValueChange(e.target.name, parseFloat(e.target.value));
|
||||
}}
|
||||
sx={{
|
||||
width: "100%",
|
||||
}}
|
||||
size="small"
|
||||
type="number"
|
||||
value={stagedSettings.style.transition_duration}
|
||||
variant="standard"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingsItem
|
||||
defaultText={defaultSettings.style.window_height + "%"}
|
||||
description={
|
||||
<>
|
||||
<DevChip />
|
||||
|
@ -216,7 +255,7 @@ export const SettingsTabStyle: FC<SettingsTabStyleProps> = ({ sx }) => {
|
|||
}
|
||||
/>
|
||||
<SettingsItem
|
||||
defaultText="400px"
|
||||
defaultText={defaultSettings.style.window_width + "px"}
|
||||
description={
|
||||
<>
|
||||
<DevChip />
|
|
@ -4,12 +4,13 @@ import { FC } from "react";
|
|||
import { stagedSettingsAtom } from "../../../../lib/store/jotai/settings";
|
||||
import { CategoryTitle } from "../CategoryTitle";
|
||||
import { SettingsItem } from "../SettingsItem";
|
||||
import { defaultSettings } from "../../../../lib/settings";
|
||||
|
||||
interface SettingsTabWindowProps {
|
||||
interface WindowProps {
|
||||
sx?: any;
|
||||
}
|
||||
|
||||
export const SettingsTabWindow: FC<SettingsTabWindowProps> = ({ sx }) => {
|
||||
export const Window: FC<WindowProps> = ({ sx }) => {
|
||||
// atoms
|
||||
const [stagedSettings, setStagedSettings] = useAtom(stagedSettingsAtom);
|
||||
|
||||
|
@ -22,13 +23,15 @@ export const SettingsTabWindow: FC<SettingsTabWindowProps> = ({ sx }) => {
|
|||
},
|
||||
};
|
||||
setStagedSettings(newSettings);
|
||||
|
||||
return newSettings;
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ sx }}>
|
||||
<CategoryTitle title="Fullscreen" />
|
||||
<SettingsItem
|
||||
defaultText="On"
|
||||
defaultText={defaultSettings.window.start_fullscreen ? "On" : "Off"}
|
||||
description="Fullscreen on startup"
|
||||
input={
|
||||
<Switch
|
||||
|
@ -42,7 +45,20 @@ export const SettingsTabWindow: FC<SettingsTabWindowProps> = ({ sx }) => {
|
|||
/>
|
||||
<CategoryTitle title="Titlebar Buttons" />
|
||||
<SettingsItem
|
||||
defaultText="Off"
|
||||
defaultText={defaultSettings.window.fullscreen_button ? "On" : "Off"}
|
||||
description="Fullscreen button"
|
||||
input={
|
||||
<Switch
|
||||
checked={stagedSettings.window.fullscreen_button}
|
||||
name="fullscreen_button"
|
||||
onChange={(e) => {
|
||||
handleSettingsWindowValueChange(e.target.name, e.target.checked);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingsItem
|
||||
defaultText={defaultSettings.window.minimize_button ? "On" : "Off"}
|
||||
description="Minimize button"
|
||||
input={
|
||||
<Switch
|
||||
|
@ -55,7 +71,7 @@ export const SettingsTabWindow: FC<SettingsTabWindowProps> = ({ sx }) => {
|
|||
}
|
||||
/>
|
||||
<SettingsItem
|
||||
defaultText="Off"
|
||||
defaultText={defaultSettings.window.maximize_button ? "On" : "Off"}
|
||||
description="Maximize button"
|
||||
input={
|
||||
<Switch
|
|
@ -1,9 +1,8 @@
|
|||
import { Close, CloseFullscreen, Minimize } from "@mui/icons-material";
|
||||
import { Box, Button, ButtonGroup, IconButton, Stack, useTheme } from "@mui/material";
|
||||
import { WebviewWindow } from "@tauri-apps/api/window";
|
||||
import { Close, CloseFullscreen, Fullscreen, FullscreenExit, Minimize, WebAssetOutlined } from "@mui/icons-material";
|
||||
import { Box, IconButton, Stack, useTheme } from "@mui/material";
|
||||
import { WebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSettings } from "../../contexts/SettingsContext";
|
||||
import { exit } from "@tauri-apps/api/process";
|
||||
|
||||
export const WindowButtons = () => {
|
||||
// contexts
|
||||
|
@ -24,16 +23,20 @@ export const WindowButtons = () => {
|
|||
setAppWindow(appWindow);
|
||||
};
|
||||
|
||||
const toggleFullscreen = async () => {
|
||||
appWindow?.setFullscreen(!(await appWindow?.isFullscreen()));
|
||||
};
|
||||
|
||||
const minimize = () => {
|
||||
appWindow?.minimize();
|
||||
};
|
||||
|
||||
const maximize = () => {
|
||||
const toggleMaximize = () => {
|
||||
appWindow?.toggleMaximize();
|
||||
};
|
||||
|
||||
const close = async () => {
|
||||
await exit(1);
|
||||
appWindow?.close();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -52,6 +55,17 @@ export const WindowButtons = () => {
|
|||
direction="row"
|
||||
spacing={1}
|
||||
>
|
||||
{settings.window.fullscreen_button && (
|
||||
<IconButton
|
||||
onClick={toggleFullscreen}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: userTheme.palette.grey[800],
|
||||
}}
|
||||
>
|
||||
{appWindow?.isFullscreen() ? <FullscreenExit fontSize="inherit" /> : <Fullscreen fontSize="inherit" />}
|
||||
</IconButton>
|
||||
)}
|
||||
{settings.window.minimize_button && (
|
||||
<IconButton
|
||||
onClick={minimize}
|
||||
|
@ -60,23 +74,18 @@ export const WindowButtons = () => {
|
|||
backgroundColor: userTheme.palette.grey[800],
|
||||
}}
|
||||
>
|
||||
<Minimize
|
||||
fontSize="inherit"
|
||||
sx={{
|
||||
backgroundColor: userTheme.palette.grey[800],
|
||||
}}
|
||||
/>
|
||||
<Minimize fontSize="inherit" />
|
||||
</IconButton>
|
||||
)}
|
||||
{settings.window.maximize_button && (
|
||||
<IconButton
|
||||
onClick={maximize}
|
||||
onClick={toggleMaximize}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: userTheme.palette.grey[800],
|
||||
}}
|
||||
>
|
||||
<CloseFullscreen fontSize="inherit" />
|
||||
<WebAssetOutlined fontSize="inherit" />
|
||||
</IconButton>
|
||||
)}
|
||||
<IconButton
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import { createContext, FC, ReactNode, useContext, useEffect, useState } from "react";
|
||||
import { logcat } from "../lib/logcatService";
|
||||
import { defaultSettings, readConfigFile, SettingsType, writeConfigFile } from "../lib/settings";
|
||||
import { useAtom } from "jotai";
|
||||
import { stagedSettingsAtom } from "../lib/store/jotai/settings";
|
||||
|
||||
// settings context
|
||||
type SettingsContextProps = {
|
||||
fetchSettings: () => void;
|
||||
resetSettings: () => void;
|
||||
settings: SettingsType;
|
||||
settingsLoading: boolean;
|
||||
updateSettings: (updates: SettingsType) => void;
|
||||
resetSettings: () => void;
|
||||
};
|
||||
|
||||
const SettingsContext = createContext<SettingsContextProps | undefined>(undefined);
|
||||
|
@ -15,6 +18,9 @@ const SettingsContext = createContext<SettingsContextProps | undefined>(undefine
|
|||
export const SettingsProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
logcat.log("Initializing settings...", "INFO");
|
||||
|
||||
// atoms
|
||||
const [stagedSettings, setStagedSettings] = useAtom(stagedSettingsAtom);
|
||||
|
||||
// states
|
||||
const [settings, setSettings] = useState<SettingsType>(defaultSettings);
|
||||
const [settingsLoading, setSettingsLoading] = useState<boolean>(true);
|
||||
|
@ -22,7 +28,11 @@ export const SettingsProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
|||
const fetchSettings = async () => {
|
||||
try {
|
||||
const existingSettings = await readConfigFile();
|
||||
// set settings state to existing settings
|
||||
setSettings(existingSettings);
|
||||
// also update the settings atom
|
||||
setStagedSettings(existingSettings);
|
||||
logcat.log("Settings loaded successfully", "INFO");
|
||||
} catch (error) {
|
||||
logcat.log(`Failed to load settings: ${error}`, "ERROR");
|
||||
} finally {
|
||||
|
@ -60,10 +70,11 @@ export const SettingsProvider: FC<{ children: ReactNode }> = ({ children }) => {
|
|||
return (
|
||||
<SettingsContext.Provider
|
||||
value={{
|
||||
fetchSettings,
|
||||
resetSettings,
|
||||
settings,
|
||||
settingsLoading,
|
||||
updateSettings,
|
||||
resetSettings,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -15,12 +15,12 @@ export const UserThemeProvider: FC<UserThemeProviderProps> = ({ children }) => {
|
|||
const userPalette = {
|
||||
primary: {
|
||||
// light: '#a1c3f9',
|
||||
main: settings.style.accent_color || "#8ab4f8",
|
||||
main: settings.colors.accent_color || "#8ab4f8",
|
||||
// dark: '#4285f4',
|
||||
},
|
||||
secondary: {
|
||||
// light: '#a1c3f9',
|
||||
main: settings.style.accent_color || "#8ab4f8",
|
||||
main: settings.colors.accent_color || "#8ab4f8",
|
||||
// dark: '#4285f4',
|
||||
},
|
||||
error: {
|
||||
|
@ -60,8 +60,8 @@ export const UserThemeProvider: FC<UserThemeProviderProps> = ({ children }) => {
|
|||
A700: "#616161",
|
||||
},
|
||||
background: {
|
||||
paper: settings.style.dark_mode ? "#303134" : "#fff",
|
||||
default: settings.style.dark_mode ? "#202124" : "#fff",
|
||||
paper: settings.style.dark_mode ? settings.colors.background_color_popup : "#fff",
|
||||
default: settings.style.dark_mode ? settings.colors.background_color : "#fff",
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -9,9 +9,7 @@ export default function createEmotionCache() {
|
|||
let insertionPoint;
|
||||
|
||||
if (isBrowser) {
|
||||
const emotionInsertionPoint = document.querySelector<HTMLMetaElement>(
|
||||
'meta[name="emotion-insertion-point"]',
|
||||
);
|
||||
const emotionInsertionPoint = document.querySelector<HTMLMetaElement>('meta[name="emotion-insertion-point"]');
|
||||
insertionPoint = emotionInsertionPoint ?? undefined;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,9 +29,7 @@ export class LogcatServiceClass {
|
|||
|
||||
log(message: string, level: LogLevel = "INFO"): void {
|
||||
const currentTimestamp = new Date().getTime();
|
||||
const timeSinceLastLog = this.lastLogTimestamp
|
||||
? currentTimestamp - this.lastLogTimestamp
|
||||
: 0;
|
||||
const timeSinceLastLog = this.lastLogTimestamp ? currentTimestamp - this.lastLogTimestamp : 0;
|
||||
|
||||
const logEntry: LogEntry = { message, level, timestamp: timeSinceLastLog };
|
||||
this.logs.push(logEntry);
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
type PathsType = {
|
||||
audioDirectory: string;
|
||||
cacheDirectory: string;
|
||||
configDirectory: string;
|
||||
dataDirectory: string;
|
||||
desktopDirectory: string;
|
||||
documentDirectory: string;
|
||||
downloadDirectory: string;
|
||||
executableDirectory: string;
|
||||
fontDirectory: string;
|
||||
homeDirectory: string;
|
||||
logDirectory: string;
|
||||
pictureDirectory: string;
|
||||
templateDirectory: string;
|
||||
videoDirectory: string;
|
||||
};
|
||||
|
||||
const programTrailingPath = "stort/";
|
||||
|
||||
class Paths {
|
||||
private static instance: Paths;
|
||||
paths: PathsType | null = null;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
static getInstance(): Paths {
|
||||
if (!Paths.instance) {
|
||||
Paths.instance = new Paths();
|
||||
}
|
||||
return Paths.instance;
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
// avoid reinitialization if already doen
|
||||
if (this.paths) return;
|
||||
|
||||
const {
|
||||
audioDir,
|
||||
cacheDir,
|
||||
configDir,
|
||||
dataDir,
|
||||
desktopDir,
|
||||
documentDir,
|
||||
downloadDir,
|
||||
executableDir,
|
||||
fontDir,
|
||||
homeDir,
|
||||
logDir,
|
||||
pictureDir,
|
||||
templateDir,
|
||||
videoDir,
|
||||
} = await import("@tauri-apps/api/path");
|
||||
|
||||
try {
|
||||
this.paths = {
|
||||
audioDirectory: await audioDir(),
|
||||
cacheDirectory: (await cacheDir()) + programTrailingPath,
|
||||
configDirectory: (await configDir()) + programTrailingPath,
|
||||
dataDirectory: (await dataDir()) + programTrailingPath,
|
||||
desktopDirectory: await desktopDir(),
|
||||
documentDirectory: await documentDir(),
|
||||
downloadDirectory: await downloadDir(),
|
||||
executableDirectory: await executableDir(),
|
||||
fontDirectory: await fontDir(),
|
||||
homeDirectory: await homeDir(),
|
||||
logDirectory: await logDir(),
|
||||
pictureDirectory: await pictureDir(),
|
||||
templateDirectory: await templateDir(),
|
||||
videoDirectory: await videoDir(),
|
||||
};
|
||||
|
||||
// make paths immutable
|
||||
Object.freeze(this.paths);
|
||||
} catch (error) {
|
||||
console.error("Error initializing paths:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
getPath(key: keyof PathsType): string {
|
||||
if (!this.paths) {
|
||||
throw new Error("Paths are not initialized. Call initialize() first.");
|
||||
}
|
||||
return this.paths[key];
|
||||
}
|
||||
}
|
||||
|
||||
export default Paths.getInstance();
|
|
@ -1,23 +1,29 @@
|
|||
import { merge } from "lodash";
|
||||
import Paths from "./path";
|
||||
import { readTomlFile, writeTomlFile } from "./utils/toml";
|
||||
|
||||
export const defaultSettings = {
|
||||
background: {
|
||||
background_color: "#202124" as string,
|
||||
background_image_path: "" as string,
|
||||
},
|
||||
style: {
|
||||
colors: {
|
||||
accent_color: "#8ab4f8" as string,
|
||||
background_color: "#202124" as string,
|
||||
background_color_popup: "#303134" as string,
|
||||
footer_color: "#000000" as string,
|
||||
},
|
||||
style: {
|
||||
blur_radius: 10 as number,
|
||||
dark_mode: true as boolean,
|
||||
font_family: "monospace" as string,
|
||||
font_scaling: 100,
|
||||
opacity: 0.8 as number,
|
||||
radius: 8 as number,
|
||||
transition_duration: 200 as number,
|
||||
window_height: 80 as number,
|
||||
window_width: 400 as number,
|
||||
window_width: 500 as number,
|
||||
},
|
||||
window: {
|
||||
fullscreen_button: false as boolean,
|
||||
maximize_button: false as boolean,
|
||||
minimize_button: false as boolean,
|
||||
start_fullscreen: false as boolean, // TODO: this should be true on prod
|
||||
|
@ -30,9 +36,8 @@ export const readConfigFile = async (): Promise<SettingsType> => {
|
|||
let existingData: SettingsType = defaultSettings;
|
||||
|
||||
try {
|
||||
// try to read the config file
|
||||
await Paths.initialize();
|
||||
const data = await readTomlFile<SettingsType>(Paths.getPath("configDirectory") + "config.toml");
|
||||
const { appConfigDir } = await import("@tauri-apps/api/path");
|
||||
const data = await readTomlFile<SettingsType>((await appConfigDir()) + "/config.toml");
|
||||
if (data) {
|
||||
existingData = data;
|
||||
console.log("existing data");
|
||||
|
@ -52,6 +57,7 @@ export const readConfigFile = async (): Promise<SettingsType> => {
|
|||
};
|
||||
|
||||
export const writeConfigFile = async (settingsValues: SettingsType): Promise<void> => {
|
||||
await writeTomlFile<SettingsType>(Paths.getPath("configDirectory") + "config.toml", settingsValues);
|
||||
const { appConfigDir } = await import("@tauri-apps/api/path");
|
||||
await writeTomlFile<SettingsType>((await appConfigDir()) + "/config.toml", settingsValues);
|
||||
console.debug("Settings file written successfully.");
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { createDir } from "@tauri-apps/api/fs";
|
||||
import { createDir } from "@tauri-apps/plugin-fs";
|
||||
|
||||
export const testing = async () => {
|
||||
await createDir("/home/vomitblood/.config/stort/");
|
||||
|
|
26
src/lib/utils/color.ts
Normal file
26
src/lib/utils/color.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
export const hexToRgba = (hex: string): { r: number; g: number; b: number; a: number } => {
|
||||
// remove the hash at the start if it is there
|
||||
hex = hex.replace("#", "");
|
||||
|
||||
let r, g, b, a;
|
||||
|
||||
if (hex.length === 8) {
|
||||
// hex have alpha (rrggbbaa)
|
||||
r = parseInt(hex.substring(0, 2), 16);
|
||||
g = parseInt(hex.substring(2, 4), 16);
|
||||
b = parseInt(hex.substring(4, 6), 16);
|
||||
// convert alpha (from 0-255) to decimal (from 0-1)
|
||||
a = parseInt(hex.substring(6, 8), 16) / 255;
|
||||
} else if (hex.length === 6) {
|
||||
// hex no have alpha
|
||||
r = parseInt(hex.substring(0, 2), 16);
|
||||
g = parseInt(hex.substring(2, 4), 16);
|
||||
b = parseInt(hex.substring(4, 6), 16);
|
||||
// default alpha is 1
|
||||
a = 1;
|
||||
} else {
|
||||
throw new Error("Invalid hex color format. Must be #rrggbbaa or #rrggbb.");
|
||||
}
|
||||
|
||||
return { r, g, b, a };
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
import { readTextFile, writeTextFile } from "@tauri-apps/api/fs";
|
||||
import { readTextFile, writeTextFile } from "@tauri-apps/plugin-fs";
|
||||
|
||||
export const readJsonFile = async <T>(path: string): Promise<T | null> => {
|
||||
try {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { readTextFile, writeTextFile } from "@tauri-apps/api/fs";
|
||||
import { readTextFile, writeTextFile } from "@tauri-apps/plugin-fs";
|
||||
import { parse, stringify } from "smol-toml";
|
||||
|
||||
export const readTomlFile = async <T>(path: string): Promise<T | null> => {
|
||||
|
|
|
@ -2,7 +2,6 @@ import { CacheProvider, EmotionCache } from "@emotion/react";
|
|||
import { CssBaseline } from "@mui/material";
|
||||
import { AppProps } from "next/app";
|
||||
import Head from "next/head";
|
||||
import { Initialization } from "../components/Generic/Initialization";
|
||||
import { UserThemeProvider } from "../contexts/ThemeContext";
|
||||
import createEmotionCache from "../lib/createEmotionCache";
|
||||
import "../styles/global.css";
|
||||
|
@ -22,14 +21,13 @@ export default function MyApp(props: MyAppProps) {
|
|||
<Head>
|
||||
<title>Stort</title>
|
||||
<meta
|
||||
name='viewport'
|
||||
content='initial-scale=1, width=device-width'
|
||||
name="viewport"
|
||||
content="initial-scale=1, width=device-width"
|
||||
/>
|
||||
</Head>
|
||||
<SettingsProvider>
|
||||
<UserThemeProvider>
|
||||
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
|
||||
<Initialization />
|
||||
<CssBaseline />
|
||||
<Component {...pageProps} />
|
||||
</UserThemeProvider>
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { BugReport } from "@mui/icons-material";
|
||||
import { Box, IconButton, TextField, Typography } from "@mui/material";
|
||||
import { invoke } from "@tauri-apps/api/tauri";
|
||||
import { useRouter } from "next/router";
|
||||
import { useState } from "react";
|
||||
import { SettingsItem } from "../components/HeaderBar/Settings/SettingsItem";
|
||||
import { useSettings } from "../contexts/SettingsContext";
|
||||
import { testing } from "../lib/testing";
|
||||
|
||||
export default function Testing() {
|
||||
// contexts
|
||||
|
@ -42,27 +40,12 @@ export default function Testing() {
|
|||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
console.log(testing());
|
||||
console.log(settings.background.background_image_path);
|
||||
}}
|
||||
>
|
||||
testing
|
||||
</button>
|
||||
<button
|
||||
onClick={async () => {
|
||||
const bruh = settings.background.background_image_path;
|
||||
console.log(bruh);
|
||||
|
||||
try {
|
||||
await invoke("delete_old_wallpaper_image", {
|
||||
filePathString: "/home/vomitblood/.local/share/stort/wallpaper.jpeg",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("deec nuts", error);
|
||||
}
|
||||
}}
|
||||
>
|
||||
invoke tauri shit
|
||||
</button>
|
||||
<Typography>{text}</Typography>
|
||||
<SettingsItem
|
||||
defaultText="#8ab4f8"
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"noImplicitAny": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictNullChecks": true,
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
|
|
Loading…
Reference in a new issue