Compare commits

...

25 Commits

Author SHA1 Message Date
fbb5a4437d v1.1.25 2026-09-04 23:01:44 -07:00
dbaab9a00e fixed mismatch 2026-09-04 22:55:31 -07:00
fc49bb93a7 allow localhost subresource loads in WKWebView 2026-09-04 22:52:34 -07:00
9229f0c2dd added this 2026-09-04 22:45:47 -07:00
843abf7df4 supported devtools 2026-09-04 22:39:33 -07:00
27fda0430b v1.1.23 2026-09-04 22:37:36 -07:00
8b49d679bd add macOS ATS exception domain for localhost webview requests 2026-09-04 22:37:15 -07:00
62883b3180 v1.1.22 2026-09-04 22:27:50 -07:00
3c6ae0125b use location.replace for WKWebView navigation 2026-09-04 22:27:24 -07:00
be0810ea6d fix: only check platform-matching node binary extension; bump to 1.1.20 2026-09-04 22:02:38 -07:00
dff55060e7 fix: only check platform-matching node binary extension 2026-09-04 22:02:16 -07:00
df767b5ffc v1.1.20 2026-09-04 21:54:10 -07:00
1b70c6784a resolve bundled resources under both _up_ and up folder names 2026-09-04 21:53:44 -07:00
e25e1c93f9 chore: bump version to 1.1.19 2026-09-04 21:37:32 -07:00
4c708ad97c preserve execute bit on bundled macOS node binary 2026-09-04 21:34:41 -07:00
b81a4e044b v1.1.18 2026-09-04 21:19:06 -07:00
f6aa2daa01 fixed (os error 13) 2026-09-04 21:13:11 -07:00
e03eb38adc v1.1.17 2026-09-04 21:04:44 -07:00
2b690b47c1 fixed typo 2026-09-04 20:58:53 -07:00
dffbdb92a5 v1.1.16 2026-09-04 20:52:21 -07:00
45d6515f2e fixed some issues...? 2026-09-04 20:46:08 -07:00
a3e6a278fe v1.1.15 2026-09-04 20:15:10 -07:00
7ffc20ee48 fixed harmless warning 2026-09-04 20:11:01 -07:00
ddf3abb314 add macOS Node runtime and appdata support 2026-09-04 20:08:27 -07:00
ec50c4e07f v1.1.14 2026-09-04 19:55:59 -07:00
12 changed files with 289 additions and 162 deletions

View File

@@ -0,0 +1 @@
bash -c 'pkill -9 -x node 2>/dev/null; pkill -9 -x "Vyla Home" 2>/dev/null; pkill -9 -x "vyla-home" 2>/dev/null; rm -rf "$HOME/Library/Application Support/com.vyla.home" "$HOME/Library/Application Support/vyla-home" "$HOME/Library/Application Support/Vyla Home" "$HOME/Library/Caches/com.vyla.home" "$HOME/Library/Caches/vyla-home" "$HOME/Library/Caches/Vyla Home" "$HOME/Library/WebKit/com.vyla.home" "$HOME/Library/WebKit/vyla-home" "$HOME/Library/WebKit/Vyla Home" "$HOME/Library/Logs/Vyla Home" "$HOME/Library/Preferences/com.vyla.home.plist" "$HOME/.vyla-home" "/Applications/Vyla Home.app" "/Applications/vyla-home.app" "$HOME/Applications/Vyla Home.app" "$HOME/Applications/vyla-home.app" "src-tauri/target" "dist-build" "temp-validation-extract" "dist" "frontend" "player" "auth-proxy" "stream-api" "logs" "*.log" "download-temp.zip" "update-payload.zip" "manifest.json" "validation-report.json" "config/encrypted-credentials.json" "config/credentials.json"; echo "Vyla Home cleanup complete."'

View File

@@ -22,8 +22,8 @@ const clonedRepos = new Set();
function clonePrivateRepo(repoConfig, targetDir) { function clonePrivateRepo(repoConfig, targetDir) {
if (fs.existsSync(targetDir)) { if (fs.existsSync(targetDir)) {
console.log(`[DEV] ${repoConfig.name} already exists locally. Skipping clone to use local changes.`); console.log(`Removing stale ${repoConfig.name} before fresh clone...`);
return; fs.rmSync(targetDir, { recursive: true, force: true });
} }
const token = process.env.GITLAB_TOKEN || process.env.VYLA_GITLAB_TOKEN; const token = process.env.GITLAB_TOKEN || process.env.VYLA_GITLAB_TOKEN;

View File

@@ -27,6 +27,20 @@ workflows:
export PATH="$HOME/.cargo/bin:$PATH" export PATH="$HOME/.cargo/bin:$PATH"
PACKAGE_VERSION=$(node -p "require('./package.json').version") PACKAGE_VERSION=$(node -p "require('./package.json').version")
node release.js --installer --tag="v$PACKAGE_VERSION" node release.js --installer --tag="v$PACKAGE_VERSION"
- name: Verify bundled node permissions
script: |
if [ -f "bin/node" ]; then
chmod +x bin/node
xattr -cr bin/node || true
ls -la bin/node
file bin/node
fi
APP_NODE=$(find src-tauri/target/release/bundle -name "node" -path "*bin*" 2>/dev/null | head -1)
if [ -n "$APP_NODE" ]; then
chmod +x "$APP_NODE"
xattr -cr "$APP_NODE" || true
ls -la "$APP_NODE"
fi
artifacts: artifacts:
- src-tauri/target/release/bundle/dmg/*.dmg - src-tauri/target/release/bundle/dmg/*.dmg
- src-tauri/target/release/bundle/macos/*.app - src-tauri/target/release/bundle/macos/*.app

13
main.js
View File

@@ -6,10 +6,19 @@ const ServiceManager = require('./service-manager');
const Patcher = require('./patcher'); const Patcher = require('./patcher');
const UpdateClient = require('./update-client'); const UpdateClient = require('./update-client');
const defaultAppData = path.join(os.homedir(), 'AppData', 'Roaming', 'vyla-home', 'vyla-home-files'); function getDefaultAppData() {
if (process.platform === 'darwin') {
return path.join(os.homedir(), 'Library', 'Application Support', 'vyla-home', 'vyla-home-files');
}
if (process.platform === 'linux') {
return path.join(os.homedir(), '.local', 'share', 'vyla-home', 'vyla-home-files');
}
return path.join(os.homedir(), 'AppData', 'Roaming', 'vyla-home', 'vyla-home-files');
}
const APPDATA_DIR = (process.env.APPDATA_PATH && !process.env.APPDATA_PATH.includes('com.vyla.home')) const APPDATA_DIR = (process.env.APPDATA_PATH && !process.env.APPDATA_PATH.includes('com.vyla.home'))
? process.env.APPDATA_PATH ? process.env.APPDATA_PATH
: defaultAppData; : getDefaultAppData();
const LOGS_DIR = path.join(APPDATA_DIR, 'logs'); const LOGS_DIR = path.join(APPDATA_DIR, 'logs');
try { fs.mkdirSync(LOGS_DIR, { recursive: true }); } catch (e) { } try { fs.mkdirSync(LOGS_DIR, { recursive: true }); } catch (e) { }

View File

@@ -1,6 +1,6 @@
{ {
"name": "vyla-home", "name": "vyla-home",
"version": "1.1.13", "version": "1.1.25",
"description": "Vyla Home Desktop App Launcher", "description": "Vyla Home Desktop App Launcher",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
@@ -54,4 +54,4 @@
"installerHeaderIcon": "build/icon.ico" "installerHeaderIcon": "build/icon.ico"
} }
} }
} }

View File

@@ -295,11 +295,18 @@ function ensureBundledNode() {
try { try {
execSync(`tar -xzf node.tar.gz ${tarName}/bin/node`, { cwd: binDir }); execSync(`tar -xzf node.tar.gz ${tarName}/bin/node`, { cwd: binDir });
fs.renameSync(path.join(binDir, tarName, 'bin', 'node'), path.join(binDir, 'node')); const extractedNode = path.join(binDir, tarName, 'bin', 'node');
const finalNode = path.join(binDir, 'node');
fs.renameSync(extractedNode, finalNode);
fs.rmSync(path.join(binDir, tarName), { recursive: true, force: true }); fs.rmSync(path.join(binDir, tarName), { recursive: true, force: true });
fs.unlinkSync(tarPath); fs.unlinkSync(tarPath);
execSync(`chmod +x node`, { cwd: binDir }); fs.chmodSync(finalNode, 0o755);
try {
execSync(`xattr -d com.apple.quarantine "${finalNode}"`);
} catch (_) { }
fs.writeFileSync(versionMarker, nodeVersion, 'utf8'); fs.writeFileSync(versionMarker, nodeVersion, 'utf8');
resolve(); resolve();

2
src-tauri/Cargo.lock generated
View File

@@ -4222,7 +4222,7 @@ dependencies = [
[[package]] [[package]]
name = "vyla-home" name = "vyla-home"
version = "1.1.13" version = "1.1.25"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"serde", "serde",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "vyla-home" name = "vyla-home"
version = "1.1.13" version = "1.1.25"
description = "Vyla Home Desktop App Launcher" description = "Vyla Home Desktop App Launcher"
edition = "2021" edition = "2021"

21
src-tauri/Info.plist Normal file
View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<false/>
</dict>
</dict>
</dict>
</dict>
</plist>

View File

@@ -58,11 +58,23 @@ fn kill_child_process(child: &mut Child) {
fn find_node_binary(app: &tauri::App) -> Option<String> { fn find_node_binary(app: &tauri::App) -> Option<String> {
if let Ok(resource_dir) = app.path().resource_dir() { if let Ok(resource_dir) = app.path().resource_dir() {
#[cfg(windows)]
let candidates = [ let candidates = [
resource_dir.join("bin").join("node.exe"), resource_dir.join("bin").join("node.exe"),
resource_dir.join("node.exe"), resource_dir.join("node.exe"),
resource_dir.join("_up_").join("bin").join("node.exe"), resource_dir.join("_up_").join("bin").join("node.exe"),
resource_dir.join("_up_").join("node.exe"), resource_dir.join("_up_").join("node.exe"),
resource_dir.join("up").join("bin").join("node.exe"),
resource_dir.join("up").join("node.exe"),
];
#[cfg(not(windows))]
let candidates = [
resource_dir.join("bin").join("node"),
resource_dir.join("node"),
resource_dir.join("_up_").join("bin").join("node"),
resource_dir.join("_up_").join("node"),
resource_dir.join("up").join("bin").join("node"),
resource_dir.join("up").join("node"),
]; ];
for candidate in candidates { for candidate in candidates {
if candidate.exists() { if candidate.exists() {
@@ -75,9 +87,13 @@ fn find_node_binary(app: &tauri::App) -> Option<String> {
if let Some(parent) = exe_path.parent() { if let Some(parent) = exe_path.parent() {
let candidates = [ let candidates = [
parent.join("bin").join("node.exe"), parent.join("bin").join("node.exe"),
parent.join("bin").join("node"),
parent.join("node.exe"), parent.join("node.exe"),
parent.join("node"),
parent.join("resources").join("bin").join("node.exe"), parent.join("resources").join("bin").join("node.exe"),
parent.join("resources").join("bin").join("node"),
parent.join("resources").join("node.exe"), parent.join("resources").join("node.exe"),
parent.join("resources").join("node"),
]; ];
for candidate in candidates { for candidate in candidates {
if candidate.exists() { if candidate.exists() {
@@ -89,9 +105,13 @@ fn find_node_binary(app: &tauri::App) -> Option<String> {
if let Ok(path_var) = std::env::var("PATH") { if let Ok(path_var) = std::env::var("PATH") {
for p in std::env::split_paths(&path_var) { for p in std::env::split_paths(&path_var) {
let candidate = p.join("node.exe"); let node_exe = p.join("node.exe");
if candidate.exists() { if node_exe.exists() {
return Some(clean_path(&candidate)); return Some(clean_path(&node_exe));
}
let node = p.join("node");
if node.exists() {
return Some(clean_path(&node));
} }
} }
} }
@@ -120,6 +140,22 @@ fn find_node_binary(app: &tauri::App) -> Option<String> {
} }
} }
#[cfg(not(windows))]
{
let default_paths = [
"/usr/local/bin/node",
"/usr/bin/node",
"/opt/homebrew/bin/node",
"/opt/local/bin/node",
];
for path in default_paths {
let candidate = std::path::Path::new(path);
if candidate.exists() {
return Some(clean_path(candidate));
}
}
}
None None
} }
@@ -128,6 +164,7 @@ fn resolve_script_and_root(app: &tauri::App) -> (String, String) {
let candidates = vec![ let candidates = vec![
resource_dir.join("main.js"), resource_dir.join("main.js"),
resource_dir.join("_up_").join("main.js"), resource_dir.join("_up_").join("main.js"),
resource_dir.join("up").join("main.js"),
]; ];
for candidate in candidates { for candidate in candidates {
@@ -561,10 +598,23 @@ fn main() {
.setup(|app| { .setup(|app| {
debug_log("Tauri setup started"); debug_log("Tauri setup started");
let app_data = app.path().app_data_dir().unwrap_or_default(); let app_data = app.path().app_data_dir().unwrap_or_default();
let app_data_str = clean_path(&app_data);
let vyla_files = if let Ok(appdata_env) = std::env::var("APPDATA") { let vyla_files = if let Ok(appdata_env) = std::env::var("APPDATA") {
std::path::PathBuf::from(appdata_env).join("vyla-home").join("vyla-home-files") std::path::PathBuf::from(appdata_env)
.join("vyla-home")
.join("vyla-home-files")
} else if let Ok(home) = std::env::var("HOME") {
#[cfg(target_os = "macos")]
let base = std::path::PathBuf::from(&home)
.join("Library")
.join("Application Support");
#[cfg(target_os = "linux")]
let base = std::path::PathBuf::from(&home)
.join(".local")
.join("share");
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
let base = app_data.clone();
base.join("vyla-home").join("vyla-home-files")
} else { } else {
app_data.join("vyla-home-files") app_data.join("vyla-home-files")
}; };
@@ -603,29 +653,35 @@ fn main() {
.inner_size(1280.0, 800.0) .inner_size(1280.0, 800.0)
.decorations(false) .decorations(false)
.resizable(true) .resizable(true)
.devtools(cfg!(debug_assertions)) .devtools(true)
.initialization_script("window.__VYLA_WINDOW_LABEL = 'main';") .initialization_script("window.__VYLA_WINDOW_LABEL = 'main';")
.initialization_script(LINK_HANDLER_SCRIPT) .initialization_script(LINK_HANDLER_SCRIPT)
.build()?; .build()?;
let (main_js, root_dir) = resolve_script_and_root(app); let (main_js, root_dir) = resolve_script_and_root(app);
debug_log("Searching for node binary...");
let node_bin = match find_node_binary(app) { let node_bin = match find_node_binary(app) {
Some(bin) => bin, Some(bin) => {
debug_log(&format!("Found node binary: {}", bin));
bin
}
None => { None => {
let _ = window.emit( debug_log("FATAL: No node binary found in bundle or PATH.");
"error", std::thread::sleep(std::time::Duration::from_millis(600));
serde_json::json!({ let _ = window.eval("document.getElementById('status-message').textContent='Error: Node runtime missing. Please reinstall.';");
"message": "Node.js runtime not found. Please ensure Node.js is installed or bundled correctly." let _ = window.eval("var b=document.getElementById('debug-log-box');b.style.display='block';b.textContent='Node binary not found. bin/node missing from app bundle or not executable.';");
}),
);
return Ok(()); return Ok(());
} }
}; };
let vyla_files_str = clean_path(&vyla_files);
std::thread::sleep(std::time::Duration::from_millis(400));
let mut cmd = Command::new(&node_bin); let mut cmd = Command::new(&node_bin);
cmd.arg(&main_js) cmd.arg(&main_js)
.current_dir(&root_dir) .current_dir(&root_dir)
.env("APPDATA_PATH", &app_data_str) .env("APPDATA_PATH", &vyla_files_str)
.env("NODE_EXE", &node_bin) .env("NODE_EXE", &node_bin)
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
@@ -817,12 +873,12 @@ fn main() {
} }
Err(e) => { Err(e) => {
debug_log(&format!("Failed to spawn node: {}", e)); debug_log(&format!("Failed to spawn node: {}", e));
let _ = window.emit( let emsg = format!("Failed to spawn node: {}", e);
"error", let js = format!(
serde_json::json!({ "document.getElementById('status-message').textContent='Spawn Error';var b=document.getElementById('debug-log-box');b.style.display='block';b.textContent={};",
"message": format!("Failed to launch backend runtime: {}", e) serde_json::to_string(&emsg).unwrap_or_default()
}),
); );
let _ = window.eval(&js);
} }
} }

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "Vyla Home", "productName": "Vyla Home",
"version": "1.1.13", "version": "1.1.25",
"identifier": "com.vyla.home", "identifier": "com.vyla.home",
"build": { "build": {
"frontendDist": "../ui" "frontendDist": "../ui"
@@ -9,7 +9,7 @@
"app": { "app": {
"withGlobalTauri": true, "withGlobalTauri": true,
"security": { "security": {
"csp": null "csp": "default-src 'self' http://localhost:4890 http://127.0.0.1:4890; script-src 'self' 'unsafe-inline' 'unsafe-eval' http://localhost:4890 http://127.0.0.1:4890 https://esm.sh; style-src 'self' 'unsafe-inline' http://localhost:4890 http://127.0.0.1:4890; img-src 'self' data: https: http://localhost:4890 http://127.0.0.1:4890; font-src 'self' data: http://localhost:4890 http://127.0.0.1:4890; connect-src 'self' http://localhost: http://127.0.0.1:"
} }
}, },
"bundle": { "bundle": {
@@ -22,6 +22,10 @@
"icons/icon.icns", "icons/icon.icns",
"icons/icon.ico" "icons/icon.ico"
], ],
"macOS": {
"exceptionDomain": "localhost",
"infoPlist": "../Info.plist"
},
"windows": { "windows": {
"webviewInstallMode": { "webviewInstallMode": {
"type": "downloadBootstrapper" "type": "downloadBootstrapper"
@@ -41,4 +45,4 @@
"../node_modules/**/*" "../node_modules/**/*"
] ]
} }
} }

View File

@@ -271,61 +271,66 @@
console.warn("OGL Shader background failed to load (offline):", err); console.warn("OGL Shader background failed to load (offline):", err);
} }
if (!window.__OGL_LOADED__) { window.dispatchEvent(new Event('ogl-ready'));
document.body.style.background = "radial-gradient(circle at 80% 20%, #1e1b4b 0%, #050505 70%)"; </script>
} else {
const { Renderer, Program, Triangle, Mesh } = window.__OGL_LOADED__;
const rayContainer = document.getElementById("side-rays");
const settings = {
speed: 2.5,
rayColor1: "#EAB308",
rayColor2: "#96c8ff",
intensity: 2,
spread: 2,
origin: "top-right",
tilt: 0,
saturation: 1.5,
blend: 0.75,
falloff: 1.6,
opacity: 1
};
function hexToRgb(hex) { <script>
const match = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); window.addEventListener('ogl-ready', () => {
if (!match) return [1, 1, 1]; if (!window.__OGL_LOADED__) {
return [ document.body.style.background = "radial-gradient(circle at 80% 20%, #1e1b4b 0%, #050505 70%)";
parseInt(match[1], 16) / 255, } else {
parseInt(match[2], 16) / 255, const { Renderer, Program, Triangle, Mesh } = window.__OGL_LOADED__;
parseInt(match[3], 16) / 255 const rayContainer = document.getElementById("side-rays");
]; const settings = {
} speed: 2.5,
rayColor1: "#EAB308",
rayColor2: "#96c8ff",
intensity: 2,
spread: 2,
origin: "top-right",
tilt: 0,
saturation: 1.5,
blend: 0.75,
falloff: 1.6,
opacity: 1
};
function originToFlip(origin) { function hexToRgb(hex) {
switch (origin) { const match = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
case "top-left": return [1, 0]; if (!match) return [1, 1, 1];
case "bottom-right": return [0, 1]; return [
case "bottom-left": return [1, 1]; parseInt(match[1], 16) / 255,
default: return [0, 0]; parseInt(match[2], 16) / 255,
parseInt(match[3], 16) / 255
];
} }
}
const renderer = new Renderer({ function originToFlip(origin) {
dpr: Math.min(window.devicePixelRatio, 2), switch (origin) {
alpha: true case "top-left": return [1, 0];
}); case "bottom-right": return [0, 1];
const gl = renderer.gl; case "bottom-left": return [1, 1];
gl.canvas.style.width = "100%"; default: return [0, 0];
gl.canvas.style.height = "100%"; }
rayContainer.appendChild(gl.canvas); }
const vertexShader = ` const renderer = new Renderer({
dpr: Math.min(window.devicePixelRatio, 2),
alpha: true
});
const gl = renderer.gl;
gl.canvas.style.width = "100%";
gl.canvas.style.height = "100%";
rayContainer.appendChild(gl.canvas);
const vertexShader = `
attribute vec2 position; attribute vec2 position;
void main() { void main() {
gl_Position = vec4(position, 0.0, 1.0); gl_Position = vec4(position, 0.0, 1.0);
} }
`; `;
const fragmentShader = ` const fragmentShader = `
precision highp float; precision highp float;
uniform float iTime; uniform float iTime;
uniform vec2 iResolution; uniform vec2 iResolution;
@@ -386,77 +391,77 @@ void main() {
} }
`; `;
const [flipX, flipY] = originToFlip(settings.origin); const [flipX, flipY] = originToFlip(settings.origin);
const uniforms = { const uniforms = {
iTime: { value: 0 }, iTime: { value: 0 },
iResolution: { value: [1, 1] }, iResolution: { value: [1, 1] },
iSpeed: { value: settings.speed }, iSpeed: { value: settings.speed },
iRayColor1: { value: hexToRgb(settings.rayColor1) }, iRayColor1: { value: hexToRgb(settings.rayColor1) },
iRayColor2: { value: hexToRgb(settings.rayColor2) }, iRayColor2: { value: hexToRgb(settings.rayColor2) },
iIntensity: { value: settings.intensity }, iIntensity: { value: settings.intensity },
iSpread: { value: settings.spread }, iSpread: { value: settings.spread },
iFlipX: { value: flipX }, iFlipX: { value: flipX },
iFlipY: { value: flipY }, iFlipY: { value: flipY },
iTilt: { value: settings.tilt }, iTilt: { value: settings.tilt },
iSaturation: { value: settings.saturation }, iSaturation: { value: settings.saturation },
iBlend: { value: settings.blend }, iBlend: { value: settings.blend },
iFalloff: { value: settings.falloff }, iFalloff: { value: settings.falloff },
iOpacity: { value: settings.opacity } iOpacity: { value: settings.opacity }
}; };
const geometry = new Triangle(gl); const geometry = new Triangle(gl);
const program = new Program(gl, { vertex: vertexShader, fragment: fragmentShader, uniforms }); const program = new Program(gl, { vertex: vertexShader, fragment: fragmentShader, uniforms });
const mesh = new Mesh(gl, { geometry, program }); const mesh = new Mesh(gl, { geometry, program });
function resize() { function resize() {
const width = rayContainer.clientWidth; const width = rayContainer.clientWidth;
const height = rayContainer.clientHeight; const height = rayContainer.clientHeight;
renderer.dpr = Math.min(window.devicePixelRatio, 2); renderer.dpr = Math.min(window.devicePixelRatio, 2);
renderer.setSize(width, height); renderer.setSize(width, height);
uniforms.iResolution.value = [width * renderer.dpr, height * renderer.dpr]; uniforms.iResolution.value = [width * renderer.dpr, height * renderer.dpr];
} }
window.addEventListener("resize", resize); window.addEventListener("resize", resize);
resize(); resize();
function animate(time) { function animate(time) {
uniforms.iTime.value = time * 0.001; uniforms.iTime.value = time * 0.001;
renderer.render({ scene: mesh }); renderer.render({ scene: mesh });
requestAnimationFrame(animate);
}
requestAnimationFrame(animate); requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
function initLiquidButtons() { function initLiquidButtons() {
const borderWidth = 0.16; const borderWidth = 0.16;
const brightness = 35; const brightness = 35;
const opacity = 0.55; const opacity = 0.55;
const blur = 4; const blur = 4;
const displace = 1.2; const displace = 1.2;
const borderRadius = 9999; const borderRadius = 9999;
const distortionScale = -380; const distortionScale = -380;
const redOffset = -25; const redOffset = -25;
const greenOffset = 0; const greenOffset = 0;
const blueOffset = 25; const blueOffset = 25;
const xChannel = "R"; const xChannel = "R";
const yChannel = "G"; const yChannel = "G";
const mixBlendMode = "screen"; const mixBlendMode = "screen";
document.querySelectorAll(".liquid-glass-btn").forEach((btn, index) => { document.querySelectorAll(".liquid-glass-btn").forEach((btn, index) => {
const filterId = `glass-filter-${index + 1}`; const filterId = `glass-filter-${index + 1}`;
btn.style.setProperty("--filter-id", `url(#${filterId})`); btn.style.setProperty("--filter-id", `url(#${filterId})`);
const feImage = btn.querySelector(".fe-image"); const feImage = btn.querySelector(".fe-image");
const redChannel = btn.querySelector(".redchannel"); const redChannel = btn.querySelector(".redchannel");
const greenChannel = btn.querySelector(".greenchannel"); const greenChannel = btn.querySelector(".greenchannel");
const blueChannel = btn.querySelector(".bluechannel"); const blueChannel = btn.querySelector(".bluechannel");
const gaussianBlur = btn.querySelector(".gaussianBlur"); const gaussianBlur = btn.querySelector(".gaussianBlur");
function generateDisplacementMap() { function generateDisplacementMap() {
const rect = btn.getBoundingClientRect(); const rect = btn.getBoundingClientRect();
const actualWidth = rect.width || 140; const actualWidth = rect.width || 140;
const actualHeight = rect.height || 48; const actualHeight = rect.height || 48;
const svgContent = ` const svgContent = `
<svg viewBox="0 0 ${actualWidth} ${actualHeight}" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 ${actualWidth} ${actualHeight}" xmlns="http://www.w3.org/2000/svg">
<defs> <defs>
<linearGradient id="red-grad-${index}" x1="100%" y1="0%" x2="0%" y2="0%"> <linearGradient id="red-grad-${index}" x1="100%" y1="0%" x2="0%" y2="0%">
@@ -473,32 +478,33 @@ void main() {
<rect width="${actualWidth}" height="${actualHeight}" rx="${borderRadius}" fill="url(#blue-grad-${index})" style="mix-blend-mode:${mixBlendMode}"/> <rect width="${actualWidth}" height="${actualHeight}" rx="${borderRadius}" fill="url(#blue-grad-${index})" style="mix-blend-mode:${mixBlendMode}"/>
</svg> </svg>
`; `;
return "data:image/svg+xml," + encodeURIComponent(svgContent); return "data:image/svg+xml," + encodeURIComponent(svgContent);
} }
function update() { function update() {
feImage.setAttribute("href", generateDisplacementMap()); feImage.setAttribute("href", generateDisplacementMap());
redChannel.setAttribute("scale", distortionScale + redOffset); redChannel.setAttribute("scale", distortionScale + redOffset);
greenChannel.setAttribute("scale", distortionScale + greenOffset); greenChannel.setAttribute("scale", distortionScale + greenOffset);
blueChannel.setAttribute("scale", distortionScale + blueOffset); blueChannel.setAttribute("scale", distortionScale + blueOffset);
[redChannel, greenChannel, blueChannel].forEach((ch) => { [redChannel, greenChannel, blueChannel].forEach((ch) => {
ch.setAttribute("xChannelSelector", xChannel); ch.setAttribute("xChannelSelector", xChannel);
ch.setAttribute("yChannelSelector", yChannel); ch.setAttribute("yChannelSelector", yChannel);
}); });
gaussianBlur.setAttribute("stdDeviation", displace); gaussianBlur.setAttribute("stdDeviation", displace);
} }
update(); update();
btn.classList.remove("glass-surface--fallback"); btn.classList.remove("glass-surface--fallback");
btn.classList.add("glass-surface--svg"); btn.classList.add("glass-surface--svg");
const observer = new ResizeObserver(() => setTimeout(update, 0)); const observer = new ResizeObserver(() => setTimeout(update, 0));
observer.observe(btn); observer.observe(btn);
}); });
}
initLiquidButtons();
} }
});
initLiquidButtons();
}
</script> </script>
<script> <script>
@@ -535,8 +541,17 @@ void main() {
} }
async function initTauriEvents() { async function initTauriEvents() {
const eventApi = window.__TAURI__?.event || window.__TAURI_INTERNALS__; let eventApi = null;
if (!eventApi || typeof eventApi.listen !== 'function') {
if (window.__TAURI__?.event && typeof window.__TAURI__.event.listen === 'function') {
eventApi = window.__TAURI__.event;
} else if (window.__TAURI_INTERNALS__ && typeof window.__TAURI_INTERNALS__.listen === 'function') {
eventApi = window.__TAURI_INTERNALS__;
} else if (window.__TAURI__ && typeof window.__TAURI__.listen === 'function') {
eventApi = window.__TAURI__;
}
if (!eventApi) {
setTimeout(initTauriEvents, 50); setTimeout(initTauriEvents, 50);
return; return;
} }
@@ -572,7 +587,7 @@ void main() {
const data = e.payload || e; const data = e.payload || e;
if (data && data.url) { if (data && data.url) {
setStatus("Launching..."); setStatus("Launching...");
window.location.href = data.url; window.location.replace(data.url);
} }
}); });