diff --git a/.gitignore b/.gitignore index a547bf3..14ed924 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ pnpm-debug.log* lerna-debug.log* node_modules +Bigscreen/build/ dist dist-ssr *.local diff --git a/Bigscreen/.gitignore b/Bigscreen/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/Bigscreen/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/Bigscreen/CMakeLists.txt b/Bigscreen/CMakeLists.txt new file mode 100644 index 0000000..9c75757 --- /dev/null +++ b/Bigscreen/CMakeLists.txt @@ -0,0 +1,69 @@ +cmake_minimum_required(VERSION 3.21) + +project(NebulaBigscreen VERSION 0.1.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +find_package(Qt6 6.5 REQUIRED COMPONENTS Quick Gui) + +qt_standard_project_setup(REQUIRES 6.5) +qt_policy(SET QTP0004 NEW) + +set(BIGSCREEN_QML_FILES + qml/Main.qml + qml/ShellWindow.qml + qml/Theme.qml + qml/components/NebulaBackground.qml + qml/components/TopBar.qml + qml/components/AppTile.qml + qml/components/TileRail.qml + qml/components/PowerOverlay.qml + qml/views/HomeView.qml + qml/views/LibraryView.qml + qml/views/SettingsView.qml +) + +set_source_files_properties(qml/Theme.qml PROPERTIES QT_QML_SINGLETON_TYPE TRUE) + +qt_add_executable(nebula-bigscreen + src/main.cpp + src/InputRouter.cpp + src/InputRouter.h +) + +qt_add_qml_module(nebula-bigscreen + URI Nebula.Bigscreen + VERSION 1.0 + QML_FILES ${BIGSCREEN_QML_FILES} +) + +target_link_libraries(nebula-bigscreen + PRIVATE + Qt6::Quick + Qt6::Gui +) + +if(WIN32) + set_target_properties(nebula-bigscreen PROPERTIES WIN32_EXECUTABLE TRUE) + + get_target_property(_qmake_executable Qt6::qmake IMPORTED_LOCATION) + get_filename_component(_qt_bin_dir "${_qmake_executable}" DIRECTORY) + find_program(WINDEPLOYQT_EXECUTABLE windeployqt HINTS "${_qt_bin_dir}" REQUIRED) + + add_custom_command( + TARGET nebula-bigscreen + POST_BUILD + COMMAND "${WINDEPLOYQT_EXECUTABLE}" + $<$:--debug> + $<$>:--release> + --qmldir "${CMAKE_CURRENT_SOURCE_DIR}/qml" + "$" + COMMENT "Deploying Qt runtime DLLs next to nebula-bigscreen" + ) +endif() + +install(TARGETS nebula-bigscreen RUNTIME DESTINATION bin) diff --git a/Bigscreen/README-Bigscreen.md b/Bigscreen/README-Bigscreen.md index 5d36377..feddc27 100644 --- a/Bigscreen/README-Bigscreen.md +++ b/Bigscreen/README-Bigscreen.md @@ -304,15 +304,47 @@ The repo still contains an early **Tauri/HTML v0 prototype** under `Bigscreen/` ## Run -### Qt shell (target) +### Qt shell -Build and run instructions will be added when the Qt project lands in `Bigscreen/`. Expect a standard CMake workflow, for example: +From `Bigscreen/` (Qt 6.5+ with Quick modules installed): ```bash -cmake -S Bigscreen -B build/bigscreen -DCMAKE_BUILD_TYPE=Debug -cmake --build build/bigscreen +cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug +cmake --build build --config Debug ``` +On Windows, the build runs `windeployqt` automatically so Qt DLLs sit next to the executable. If you see missing `Qt6*.dll` errors, rebuild once from `Bigscreen/`: + +```powershell +cmake --build build --config Debug +``` + +From the repo root instead: + +```bash +cmake -S Bigscreen -B Bigscreen/build -DCMAKE_BUILD_TYPE=Debug +cmake --build Bigscreen/build --config Debug +``` + +Run the binary: + +```bash +# Linux (from Bigscreen/) +./build/nebula-bigscreen + +# Windows (from Bigscreen/) +build\Debug\nebula-bigscreen.exe +``` + +To run without redeploying, you can instead put Qt on `PATH` for that session (adjust the kit path if needed): + +```powershell +$env:PATH = "C:\Qt\6.10.2\msvc2022_64\bin;" + $env:PATH +.\build\Debug\nebula-bigscreen.exe +``` + +**Controls (v0):** arrow keys to navigate, Enter to accept, Escape/Backspace for back, Menu key (or Start when mapped later) for the power overlay. Gamepad mapping is planned next. + ### Legacy Tauri prototype To run the existing v0 web prototype while migration is in progress, from `Bigscreen/`: @@ -326,9 +358,9 @@ npm run dev ## Build -### Qt shell (target) +### Qt shell -Release builds will use the Qt/CMake project once it is in the tree. +Same CMake workflow as above with `-DCMAKE_BUILD_TYPE=Release`. ### Legacy Tauri prototype diff --git a/Bigscreen/package-lock.json b/Bigscreen/package-lock.json deleted file mode 100644 index e7e90fe..0000000 --- a/Bigscreen/package-lock.json +++ /dev/null @@ -1,252 +0,0 @@ -{ - "name": "nebula-os", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "nebula-os", - "version": "0.1.0", - "dependencies": { - "@nebulaproject/core": "^0.1.3", - "@tauri-apps/api": "^2.10.1" - }, - "devDependencies": { - "@tauri-apps/cli": "^2" - } - }, - "node_modules/@nebulaproject/core": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@nebulaproject/core/-/core-0.1.3.tgz", - "integrity": "sha512-sOjH10J1qSyqRNNi0yM3GAhkQk6lLGgmPPoyEljGfPC2Ty3iBoY44ML0sfepiiedVtedbkeDPpDcyx/UbVis6g==", - "license": "MIT" - }, - "node_modules/@tauri-apps/api": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.10.1.tgz", - "integrity": "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==", - "license": "Apache-2.0 OR MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" - } - }, - "node_modules/@tauri-apps/cli": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.10.0.tgz", - "integrity": "sha512-ZwT0T+7bw4+DPCSWzmviwq5XbXlM0cNoleDKOYPFYqcZqeKY31KlpoMW/MOON/tOFBPgi31a2v3w9gliqwL2+Q==", - "dev": true, - "license": "Apache-2.0 OR MIT", - "bin": { - "tauri": "tauri.js" - }, - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" - }, - "optionalDependencies": { - "@tauri-apps/cli-darwin-arm64": "2.10.0", - "@tauri-apps/cli-darwin-x64": "2.10.0", - "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.0", - "@tauri-apps/cli-linux-arm64-gnu": "2.10.0", - "@tauri-apps/cli-linux-arm64-musl": "2.10.0", - "@tauri-apps/cli-linux-riscv64-gnu": "2.10.0", - "@tauri-apps/cli-linux-x64-gnu": "2.10.0", - "@tauri-apps/cli-linux-x64-musl": "2.10.0", - "@tauri-apps/cli-win32-arm64-msvc": "2.10.0", - "@tauri-apps/cli-win32-ia32-msvc": "2.10.0", - "@tauri-apps/cli-win32-x64-msvc": "2.10.0" - } - }, - "node_modules/@tauri-apps/cli-darwin-arm64": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.0.tgz", - "integrity": "sha512-avqHD4HRjrMamE/7R/kzJPcAJnZs0IIS+1nkDP5b+TNBn3py7N2aIo9LIpy+VQq0AkN8G5dDpZtOOBkmWt/zjA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-darwin-x64": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.0.tgz", - "integrity": "sha512-keDmlvJRStzVFjZTd0xYkBONLtgBC9eMTpmXnBXzsHuawV2q9PvDo2x6D5mhuoMVrJ9QWjgaPKBBCFks4dK71Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.0.tgz", - "integrity": "sha512-e5u0VfLZsMAC9iHaOEANumgl6lfnJx0Dtjkd8IJpysZ8jp0tJ6wrIkto2OzQgzcYyRCKgX72aKE0PFgZputA8g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm64-gnu": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.0.tgz", - "integrity": "sha512-YrYYk2dfmBs5m+OIMCrb+JH/oo+4FtlpcrTCgiFYc7vcs6m3QDd1TTyWu0u01ewsCtK2kOdluhr/zKku+KP7HA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm64-musl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.0.tgz", - "integrity": "sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.0.tgz", - "integrity": "sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-x64-gnu": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.0.tgz", - "integrity": "sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-x64-musl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.0.tgz", - "integrity": "sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-arm64-msvc": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.0.tgz", - "integrity": "sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-ia32-msvc": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.0.tgz", - "integrity": "sha512-EHyQ1iwrWy1CwMalEm9z2a6L5isQ121pe7FcA2xe4VWMJp+GHSDDGvbTv/OPdkt2Lyr7DAZBpZHM6nvlHXEc4A==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-x64-msvc": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.0.tgz", - "integrity": "sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - } - } -} diff --git a/Bigscreen/package.json b/Bigscreen/package.json deleted file mode 100644 index 3846750..0000000 --- a/Bigscreen/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "nebula-os", - "private": true, - "version": "0.1.0", - "type": "module", - "scripts": { - "dev": "tauri dev", - "build": "tauri build", - "tauri": "tauri" - }, - "devDependencies": { - "@tauri-apps/cli": "^2" - }, - "dependencies": { - "@nebulaproject/core": "^0.1.3", - "@tauri-apps/api": "^2.10.1" - } -} diff --git a/Bigscreen/qml/Main.qml b/Bigscreen/qml/Main.qml new file mode 100644 index 0000000..8659126 --- /dev/null +++ b/Bigscreen/qml/Main.qml @@ -0,0 +1,4 @@ +import QtQuick + +ShellWindow { +} diff --git a/Bigscreen/qml/ShellWindow.qml b/Bigscreen/qml/ShellWindow.qml new file mode 100644 index 0000000..dc5ef9d --- /dev/null +++ b/Bigscreen/qml/ShellWindow.qml @@ -0,0 +1,140 @@ +import QtQuick +import QtQuick.Controls +import Nebula.Bigscreen + +ApplicationWindow { + id: window + + width: 1280 + height: 720 + visible: true + title: "Nebula Bigscreen" + color: Theme.backgroundDeep + + property string currentView: "home" + property bool powerOverlayVisible: false + property string signedInUser: "Player" + + NebulaBackground { + anchors.fill: parent + } + + Column { + anchors.fill: parent + spacing: 0 + + TopBar { + id: topBar + width: parent.width + userName: window.signedInUser + accentFocus: contentLoader.item && contentLoader.item.accentFocus !== undefined + ? contentLoader.item.accentFocus() + : 0 + } + + Loader { + id: contentLoader + width: parent.width + height: parent.height - topBar.height + sourceComponent: viewComponentFor(window.currentView) + } + } + + Connections { + target: contentLoader.item + ignoreUnknownSignals: true + function onNavigate(route) { + window.onChildNavigate(route) + } + function onGoBack() { + window.onChildGoBack() + } + } + + PowerOverlay { + id: powerOverlay + visible: window.powerOverlayVisible + z: 10 + + onDismissed: window.powerOverlayVisible = false + onActionChosen: function(actionId) { + console.log("Power action:", actionId) + window.powerOverlayVisible = false + } + } + + Connections { + target: InputRouter + function onActionTriggered(action) { + if (window.powerOverlayVisible) { + window.handlePowerInput(action) + return + } + if (action === InputRouter.Menu) { + window.powerOverlayVisible = true + return + } + if (contentLoader.item && contentLoader.item.handleInput) { + contentLoader.item.handleInput(action) + } + } + } + + Component { + id: homeComponent + HomeView {} + } + + Component { + id: libraryComponent + LibraryView {} + } + + Component { + id: settingsComponent + SettingsView {} + } + + function viewComponentFor(viewId) { + switch (viewId) { + case "library": + return libraryComponent + case "settings": + return settingsComponent + default: + return homeComponent + } + } + + function onChildNavigate(route) { + if (route === "power") { + window.powerOverlayVisible = true + return + } + window.currentView = route + } + + function onChildGoBack() { + window.currentView = "home" + } + + function handlePowerInput(action) { + switch (action) { + case InputRouter.Up: + powerOverlay.moveFocus(-1) + break + case InputRouter.Down: + powerOverlay.moveFocus(1) + break + case InputRouter.Accept: + powerOverlay.activateFocused() + break + case InputRouter.Back: + window.powerOverlayVisible = false + break + default: + break + } + } + +} diff --git a/Bigscreen/qml/Theme.qml b/Bigscreen/qml/Theme.qml new file mode 100644 index 0000000..ecf1265 --- /dev/null +++ b/Bigscreen/qml/Theme.qml @@ -0,0 +1,45 @@ +pragma Singleton +import QtQuick + +QtObject { + readonly property color backgroundDeep: "#050A17" + readonly property color backgroundMid: "#111321" + readonly property color backgroundPanel: "#181A2E" + + readonly property color accentCyan: "#4FD8FF" + readonly property color accentPurple: "#6F5CFF" + readonly property color accentLine: "#30325A" + + readonly property color textPrimary: "#F2F7FF" + readonly property color textMuted: "#A8BDD8" + + readonly property int tileWidth: 280 + readonly property int tileHeight: 160 + readonly property int tileGap: 20 + readonly property int topBarHeight: 72 + readonly property int focusScaleDuration: 180 + + readonly property real tileFocusScale: 1.06 + readonly property real tilePressScale: 0.96 + + readonly property font brandFont: Qt.font({ + family: "Segoe UI", + pixelSize: 28, + weight: Font.DemiBold + }) + readonly property font titleFont: Qt.font({ + family: "Segoe UI", + pixelSize: 22, + weight: Font.DemiBold + }) + readonly property font bodyFont: Qt.font({ + family: "Segoe UI", + pixelSize: 16, + weight: Font.Normal + }) + readonly property font metaFont: Qt.font({ + family: "Segoe UI", + pixelSize: 13, + weight: Font.Normal + }) +} diff --git a/Bigscreen/qml/components/AppTile.qml b/Bigscreen/qml/components/AppTile.qml new file mode 100644 index 0000000..658b1a4 --- /dev/null +++ b/Bigscreen/qml/components/AppTile.qml @@ -0,0 +1,89 @@ +import QtQuick +import Nebula.Bigscreen + +Item { + id: root + + property string label: "" + property string meta: "" + property string iconText: "" + property bool focused: false + property bool pressed: false + + signal activated() + + implicitWidth: Theme.tileWidth + implicitHeight: Theme.tileHeight + + scale: pressed ? Theme.tilePressScale : (focused ? Theme.tileFocusScale : 1.0) + + Behavior on scale { + NumberAnimation { duration: Theme.focusScaleDuration; easing.type: Easing.OutCubic } + } + + Rectangle { + id: card + anchors.fill: parent + radius: 14 + color: Theme.backgroundPanel + border.width: focused ? 2 : 1 + border.color: focused ? Theme.accentCyan : Theme.accentLine + + Rectangle { + anchors.fill: parent + radius: parent.radius + visible: focused + gradient: Gradient { + GradientStop { position: 0.0; color: "#224FD8FF" } + GradientStop { position: 1.0; color: "transparent" } + } + } + + Column { + anchors.fill: parent + anchors.margins: 20 + spacing: 10 + + Text { + text: root.iconText + font.pixelSize: 36 + } + + Text { + text: root.label + font: Theme.titleFont + color: Theme.textPrimary + } + + Text { + text: root.meta + font: Theme.metaFont + color: Theme.textMuted + } + } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 12 + height: 3 + radius: 2 + color: Theme.accentCyan + opacity: focused ? 1.0 : 0.0 + + Behavior on opacity { + NumberAnimation { duration: Theme.focusScaleDuration } + } + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onClicked: root.activated() + onPressed: root.pressed = true + onReleased: root.pressed = false + onCanceled: root.pressed = false + } +} diff --git a/Bigscreen/qml/components/NebulaBackground.qml b/Bigscreen/qml/components/NebulaBackground.qml new file mode 100644 index 0000000..82180e8 --- /dev/null +++ b/Bigscreen/qml/components/NebulaBackground.qml @@ -0,0 +1,53 @@ +import QtQuick + +Item { + id: root + anchors.fill: parent + + Rectangle { + anchors.fill: parent + gradient: Gradient { + GradientStop { position: 0.0; color: "#050A17" } + GradientStop { position: 0.55; color: "#0E1630" } + GradientStop { position: 1.0; color: "#1A1342" } + } + } + + Repeater { + model: 48 + Rectangle { + width: (index % 5) + 1 + height: width + radius: width / 2 + color: index % 3 === 0 ? "#88FFFFFF" : "#44B8D4FF" + x: (index * 137) % root.width + y: (index * 89) % root.height + opacity: 0.35 + (index % 7) * 0.05 + + SequentialAnimation on opacity { + loops: Animation.Infinite + running: true + NumberAnimation { + from: 0.2 + to: 0.7 + duration: 2000 + (index % 5) * 400 + } + NumberAnimation { + from: 0.7 + to: 0.2 + duration: 2000 + (index % 5) * 400 + } + } + } + } + + Rectangle { + anchors.fill: parent + opacity: 0.35 + gradient: Gradient { + orientation: Gradient.Vertical + GradientStop { position: 0.0; color: "transparent" } + GradientStop { position: 1.0; color: "#CC000000" } + } + } +} diff --git a/Bigscreen/qml/components/PowerOverlay.qml b/Bigscreen/qml/components/PowerOverlay.qml new file mode 100644 index 0000000..c510269 --- /dev/null +++ b/Bigscreen/qml/components/PowerOverlay.qml @@ -0,0 +1,92 @@ +import QtQuick +import QtQuick.Layouts +import Nebula.Bigscreen + +Rectangle { + id: root + + property int focusIndex: 0 + readonly property var actions: [ + { label: "Sleep", meta: "Suspend system" }, + { label: "Restart", meta: "Reboot NebulaOS" }, + { label: "Shut down", meta: "Power off" }, + { label: "Log out", meta: "Return to login" }, + { label: "Desktop Mode", meta: "Switch session" }, + { label: "Close", meta: "Back to Home" } + ] + + signal dismissed() + signal actionChosen(string actionId) + + anchors.fill: parent + color: "#CC050A17" + + Keys.onPressed: function(event) { event.accepted = true } + + ColumnLayout { + anchors.centerIn: parent + width: Math.min(parent.width - 80, 520) + spacing: 12 + + Text { + text: "Power" + font: Theme.brandFont + color: Theme.textPrimary + Layout.alignment: Qt.AlignHCenter + } + + Repeater { + model: root.actions + + Rectangle { + Layout.fillWidth: true + height: 56 + radius: 10 + color: index === root.focusIndex ? "#22304A66" : Theme.backgroundPanel + border.color: index === root.focusIndex ? Theme.accentCyan : Theme.accentLine + border.width: index === root.focusIndex ? 2 : 1 + + RowLayout { + anchors.fill: parent + anchors.margins: 16 + + Text { + text: modelData.label + font: Theme.titleFont + color: Theme.textPrimary + } + + Item { Layout.fillWidth: true } + + Text { + text: modelData.meta + font: Theme.metaFont + color: Theme.textMuted + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + root.focusIndex = index + root.activateFocused() + } + } + } + } + } + + function moveFocus(delta) { + focusIndex = Math.max(0, Math.min(actions.length - 1, focusIndex + delta)) + } + + function activateFocused() { + const labels = ["sleep", "restart", "shutdown", "logout", "desktop", "close"] + const id = labels[focusIndex] + if (id === "close") { + dismissed() + } else { + actionChosen(id) + } + } +} diff --git a/Bigscreen/qml/components/TileRail.qml b/Bigscreen/qml/components/TileRail.qml new file mode 100644 index 0000000..3433b90 --- /dev/null +++ b/Bigscreen/qml/components/TileRail.qml @@ -0,0 +1,52 @@ +import QtQuick +import Nebula.Bigscreen + +Item { + id: root + + property var tiles: [] + property int focusIndex: 0 + property real accentFocus: 0 + + signal tileActivated(int index) + + implicitHeight: Theme.tileHeight + 24 + + onFocusIndexChanged: root.accentFocus = focusIndex / Math.max(1, tiles.length - 1) + + Row { + id: rail + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 40 + spacing: Theme.tileGap + + Repeater { + model: root.tiles + + AppTile { + label: modelData.label + meta: modelData.meta + iconText: modelData.icon + focused: index === root.focusIndex + + onActivated: root.tileActivated(index) + } + } + } + + function moveFocus(delta) { + if (tiles.length === 0) { + return + } + const next = Math.max(0, Math.min(tiles.length - 1, focusIndex + delta)) + focusIndex = next + } + + function activateFocused() { + if (tiles.length === 0) { + return + } + tileActivated(focusIndex) + } +} diff --git a/Bigscreen/qml/components/TopBar.qml b/Bigscreen/qml/components/TopBar.qml new file mode 100644 index 0000000..c8abf84 --- /dev/null +++ b/Bigscreen/qml/components/TopBar.qml @@ -0,0 +1,80 @@ +import QtQuick +import QtQuick.Layouts +import Nebula.Bigscreen + +Item { + id: root + + property string userName: "Player" + property real accentFocus: 0 + + implicitHeight: Theme.topBarHeight + + RowLayout { + anchors.fill: parent + anchors.leftMargin: 40 + anchors.rightMargin: 40 + spacing: 16 + + Text { + text: "Nebula OS" + font: Theme.brandFont + color: Theme.textPrimary + style: Text.Outline + styleColor: Theme.accentCyan + Layout.alignment: Qt.AlignVCenter + } + + Item { Layout.fillWidth: true } + + RowLayout { + spacing: 14 + Layout.alignment: Qt.AlignVCenter + + Rectangle { + width: 36 + height: 36 + radius: 18 + color: Theme.backgroundPanel + border.color: Theme.accentCyan + border.width: 2 + + Text { + anchors.centerIn: parent + text: root.userName.length > 0 ? root.userName.charAt(0).toUpperCase() : "?" + font: Theme.titleFont + color: Theme.textPrimary + } + } + + Text { + id: clockLabel + font: Theme.bodyFont + color: Theme.textMuted + } + } + } + + Rectangle { + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.leftMargin: 40 + root.accentFocus * 320 + width: 120 + height: 2 + radius: 1 + color: Theme.accentCyan + opacity: 0.85 + + Behavior on anchors.leftMargin { + NumberAnimation { duration: Theme.focusScaleDuration; easing.type: Easing.OutCubic } + } + } + + Timer { + interval: 1000 + running: true + repeat: true + triggeredOnStart: true + onTriggered: clockLabel.text = Qt.formatTime(new Date(), "hh:mm AP") + } +} diff --git a/Bigscreen/qml/views/HomeView.qml b/Bigscreen/qml/views/HomeView.qml new file mode 100644 index 0000000..71b8234 --- /dev/null +++ b/Bigscreen/qml/views/HomeView.qml @@ -0,0 +1,59 @@ +import QtQuick +import QtQuick.Layouts +import Nebula.Bigscreen + +ColumnLayout { + id: root + + property var homeTiles: [ + { label: "Library", meta: "Games and apps", icon: "📚", route: "library" }, + { label: "Settings", meta: "System controls", icon: "⚙", route: "settings" }, + { label: "Power", meta: "Sleep and sessions", icon: "⏻", route: "power" } + ] + + signal navigate(string route) + + spacing: 28 + + Text { + text: "Home" + font: Theme.brandFont + color: Theme.textPrimary + Layout.leftMargin: 40 + } + + TileRail { + id: rail + Layout.fillWidth: true + tiles: root.homeTiles + + onTileActivated: function(index) { + const route = root.homeTiles[index].route + if (route === "power") { + root.navigate("power") + } else { + root.navigate(route) + } + } + } + + function handleInput(action) { + switch (action) { + case InputRouter.Left: + rail.moveFocus(-1) + break + case InputRouter.Right: + rail.moveFocus(1) + break + case InputRouter.Accept: + rail.activateFocused() + break + default: + break + } + } + + function accentFocus() { + return rail.accentFocus + } +} diff --git a/Bigscreen/qml/views/LibraryView.qml b/Bigscreen/qml/views/LibraryView.qml new file mode 100644 index 0000000..876de64 --- /dev/null +++ b/Bigscreen/qml/views/LibraryView.qml @@ -0,0 +1,99 @@ +import QtQuick +import QtQuick.Layouts +import Nebula.Bigscreen + +ColumnLayout { + id: root + + signal goBack() + + readonly property var entries: [ + { title: "Nebula Demo", meta: "Local • Ready", icon: "🎮" }, + { title: "Retro Runner", meta: "Wine • Installed", icon: "👾" }, + { title: "Media Hub", meta: "App • Installed", icon: "🎬" } + ] + + property int focusIndex: 0 + + spacing: 20 + + Text { + text: "Library" + font: Theme.brandFont + color: Theme.textPrimary + Layout.leftMargin: 40 + } + + Text { + text: "Mock entries for v0 — scanners and launchers come later." + font: Theme.metaFont + color: Theme.textMuted + Layout.leftMargin: 40 + Layout.bottomMargin: 8 + } + + ListView { + id: list + Layout.fillWidth: true + Layout.fillHeight: true + Layout.leftMargin: 40 + Layout.rightMargin: 40 + clip: true + spacing: 12 + model: root.entries + + delegate: Rectangle { + width: list.width + height: 72 + radius: 12 + color: index === root.focusIndex ? "#22304A66" : Theme.backgroundPanel + border.color: index === root.focusIndex ? Theme.accentCyan : Theme.accentLine + border.width: index === root.focusIndex ? 2 : 1 + + RowLayout { + anchors.fill: parent + anchors.margins: 16 + spacing: 16 + + Text { + text: modelData.icon + font.pixelSize: 28 + } + + ColumnLayout { + spacing: 2 + Text { + text: modelData.title + font: Theme.titleFont + color: Theme.textPrimary + } + Text { + text: modelData.meta + font: Theme.metaFont + color: Theme.textMuted + } + } + } + } + } + + function handleInput(action) { + switch (action) { + case InputRouter.Up: + focusIndex = Math.max(0, focusIndex - 1) + list.currentIndex = focusIndex + break + case InputRouter.Down: + focusIndex = Math.min(entries.length - 1, focusIndex + 1) + list.currentIndex = focusIndex + break + case InputRouter.Back: + root.goBack() + break + case InputRouter.Accept: + break + default: + break + } + } +} diff --git a/Bigscreen/qml/views/SettingsView.qml b/Bigscreen/qml/views/SettingsView.qml new file mode 100644 index 0000000..f165e6e --- /dev/null +++ b/Bigscreen/qml/views/SettingsView.qml @@ -0,0 +1,120 @@ +import QtQuick +import QtQuick.Layouts +import Nebula.Bigscreen + +ColumnLayout { + id: root + + signal goBack() + + readonly property var categories: [ + "Network", + "Audio", + "Display", + "Controller", + "System" + ] + + property int categoryIndex: 0 + property int itemIndex: 0 + + spacing: 20 + + Text { + text: "Settings" + font: Theme.brandFont + color: Theme.textPrimary + Layout.leftMargin: 40 + } + + Row { + Layout.leftMargin: 40 + spacing: 16 + + Repeater { + model: root.categories + + Rectangle { + height: 40 + width: categoryLabel.implicitWidth + 24 + radius: 8 + color: index === root.categoryIndex ? "#22304A66" : "transparent" + border.color: index === root.categoryIndex ? Theme.accentCyan : "transparent" + border.width: index === root.categoryIndex ? 2 : 0 + + Text { + id: categoryLabel + anchors.centerIn: parent + text: modelData + font: Theme.bodyFont + color: index === root.categoryIndex ? Theme.accentCyan : Theme.textMuted + } + } + } + } + + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.margins: 40 + radius: 16 + color: Theme.backgroundPanel + border.color: Theme.accentLine + + ColumnLayout { + anchors.fill: parent + anchors.margins: 24 + spacing: 12 + + Text { + text: root.categories[root.categoryIndex] + font: Theme.titleFont + color: Theme.textPrimary + } + + Repeater { + model: 3 + + Rectangle { + Layout.fillWidth: true + height: 52 + radius: 10 + color: index === root.itemIndex ? "#22304A66" : Theme.backgroundMid + border.color: index === root.itemIndex ? Theme.accentCyan : Theme.accentLine + border.width: index === root.itemIndex ? 2 : 1 + + Text { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 16 + text: "Placeholder setting " + (index + 1) + font: Theme.bodyFont + color: Theme.textPrimary + } + } + } + } + } + + function handleInput(action) { + switch (action) { + case InputRouter.Left: + categoryIndex = Math.max(0, categoryIndex - 1) + break + case InputRouter.Right: + categoryIndex = Math.min(categories.length - 1, categoryIndex + 1) + break + case InputRouter.Up: + itemIndex = Math.max(0, itemIndex - 1) + break + case InputRouter.Down: + itemIndex = Math.min(2, itemIndex + 1) + break + case InputRouter.Back: + root.goBack() + break + default: + break + } + } +} diff --git a/Bigscreen/src-tauri/.gitignore b/Bigscreen/src-tauri/.gitignore deleted file mode 100644 index b21bd68..0000000 --- a/Bigscreen/src-tauri/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ - -# Generated by Tauri -# will have schema files for capabilities auto-completion -/gen/schemas diff --git a/Bigscreen/src-tauri/Cargo.lock b/Bigscreen/src-tauri/Cargo.lock deleted file mode 100644 index 88dc581..0000000 --- a/Bigscreen/src-tauri/Cargo.lock +++ /dev/null @@ -1,5509 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "ahash" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" - -[[package]] -name = "async-broadcast" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" -dependencies = [ - "event-listener", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-lock" -version = "3.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-process" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" -dependencies = [ - "async-channel", - "async-io", - "async-lock", - "async-signal", - "async-task", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "async-signal" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" -dependencies = [ - "async-io", - "async-lock", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix", - "signal-hook-registry", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "atk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" -dependencies = [ - "atk-sys", - "glib", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" -dependencies = [ - "serde_core", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2", -] - -[[package]] -name = "blocking" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" -dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - -[[package]] -name = "brotli" -version = "8.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "bumpalo" -version = "3.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" - -[[package]] -name = "bytemuck" -version = "1.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -dependencies = [ - "serde", -] - -[[package]] -name = "cairo-rs" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" -dependencies = [ - "bitflags 2.11.0", - "cairo-sys-rs", - "glib", - "libc", - "once_cell", - "thiserror 1.0.69", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "camino" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" -dependencies = [ - "serde_core", -] - -[[package]] -name = "cargo-platform" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.19.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" -dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", - "thiserror 2.0.18", -] - -[[package]] -name = "cargo_toml" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" -dependencies = [ - "serde", - "toml 0.9.12+spec-1.1.0", -] - -[[package]] -name = "cc" -version = "1.2.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cfb" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" -dependencies = [ - "byteorder", - "fnv", - "uuid", -] - -[[package]] -name = "cfg-expr" -version = "0.15.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" -dependencies = [ - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "chrono" -version = "0.4.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" -dependencies = [ - "iana-time-zone", - "num-traits", - "serde", - "windows-link 0.2.1", -] - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "cookie" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" -dependencies = [ - "time", - "version_check", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "core-graphics" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" -dependencies = [ - "bitflags 2.11.0", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.11.0", - "core-foundation", - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "cssparser" -version = "0.29.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa", - "matches", - "phf 0.10.1", - "proc-macro2", - "quote", - "smallvec", - "syn 1.0.109", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" -dependencies = [ - "quote", - "syn 2.0.115", -] - -[[package]] -name = "ctor" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" -dependencies = [ - "quote", - "syn 2.0.115", -] - -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.115", -] - -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "deranged" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" -dependencies = [ - "powerfmt", - "serde_core", -] - -[[package]] -name = "derive_more" -version = "0.99.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.115", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.61.2", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "dispatch2" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" -dependencies = [ - "bitflags 2.11.0", - "objc2", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "dlopen2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" -dependencies = [ - "dlopen2_derive", - "libc", - "once_cell", - "winapi", -] - -[[package]] -name = "dlopen2_derive" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "dpi" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" -dependencies = [ - "serde", -] - -[[package]] -name = "dtoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" - -[[package]] -name = "dtoa-short" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" -dependencies = [ - "dtoa", -] - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "dyn-clone" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" - -[[package]] -name = "embed-resource" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" -dependencies = [ - "cc", - "memchr", - "rustc_version", - "toml 0.9.12+spec-1.1.0", - "vswhom", - "winreg", -] - -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" - -[[package]] -name = "endi" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" - -[[package]] -name = "enumflags2" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" -dependencies = [ - "enumflags2_derive", - "serde", -] - -[[package]] -name = "enumflags2_derive" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "erased-serde" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" -dependencies = [ - "serde", - "serde_core", - "typeid", -] - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "event-listener" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "fdeflate" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "flate2" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "gdk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" -dependencies = [ - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" -dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", - "once_cell", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gdk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkwayland-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" -dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps", -] - -[[package]] -name = "gdkx11" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" -dependencies = [ - "gdk", - "gdkx11-sys", - "gio", - "glib", - "libc", - "x11", -] - -[[package]] -name = "gdkx11-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" -dependencies = [ - "gdk-sys", - "glib-sys", - "libc", - "system-deps", - "x11", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", - "wasip3", -] - -[[package]] -name = "gio" -version = "0.18.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "once_cell", - "pin-project-lite", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "gio-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "winapi", -] - -[[package]] -name = "glib" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" -dependencies = [ - "bitflags 2.11.0", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "once_cell", - "smallvec", - "thiserror 1.0.69", -] - -[[package]] -name = "glib-macros" -version = "0.18.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" -dependencies = [ - "heck 0.4.1", - "proc-macro-crate 2.0.2", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "glib-sys" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" -dependencies = [ - "libc", - "system-deps", -] - -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - -[[package]] -name = "gobject-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - -[[package]] -name = "gtk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" -dependencies = [ - "atk", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "pango", - "pkg-config", -] - -[[package]] -name = "gtk-sys" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps", -] - -[[package]] -name = "gtk3-macros" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "hashlink" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" -dependencies = [ - "hashbrown 0.14.5", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "html5ever" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" -dependencies = [ - "log", - "mac", - "markup5ever", - "match_token", -] - -[[package]] -name = "http" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "http-range" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "hyper" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "pin-utils", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core 0.62.2", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ico" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" -dependencies = [ - "byteorder", - "png", -] - -[[package]] -name = "icu_collections" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" - -[[package]] -name = "icu_properties" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" - -[[package]] -name = "icu_provider" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" -dependencies = [ - "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", -] - -[[package]] -name = "infer" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" -dependencies = [ - "cfb", -] - -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - -[[package]] -name = "iri-string" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "is-docker" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" -dependencies = [ - "once_cell", -] - -[[package]] -name = "is-wsl" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" -dependencies = [ - "is-docker", - "once_cell", -] - -[[package]] -name = "itoa" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" - -[[package]] -name = "javascriptcore-rs" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" -dependencies = [ - "bitflags 1.3.2", - "glib", - "javascriptcore-rs-sys", -] - -[[package]] -name = "javascriptcore-rs-sys" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "js-sys" -version = "0.3.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "json-patch" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" -dependencies = [ - "jsonptr", - "serde", - "serde_json", - "thiserror 1.0.69", -] - -[[package]] -name = "jsonptr" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" -dependencies = [ - "serde", - "serde_json", -] - -[[package]] -name = "keyboard-types" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" -dependencies = [ - "bitflags 2.11.0", - "serde", - "unicode-segmentation", -] - -[[package]] -name = "kuchikiki" -version = "0.8.8-speedreader" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" -dependencies = [ - "cssparser", - "html5ever", - "indexmap 2.13.0", - "selectors", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - -[[package]] -name = "libappindicator" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" -dependencies = [ - "glib", - "gtk", - "gtk-sys", - "libappindicator-sys", - "log", -] - -[[package]] -name = "libappindicator-sys" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" -dependencies = [ - "gtk-sys", - "libloading", - "once_cell", -] - -[[package]] -name = "libc" -version = "0.2.182" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "libredox" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" -dependencies = [ - "bitflags 2.11.0", - "libc", -] - -[[package]] -name = "libsqlite3-sys" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linux-raw-sys" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" - -[[package]] -name = "litemap" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - -[[package]] -name = "markup5ever" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" -dependencies = [ - "log", - "phf 0.11.3", - "phf_codegen 0.11.3", - "string_cache", - "string_cache_codegen", - "tendril", -] - -[[package]] -name = "match_token" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "memchr" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" -dependencies = [ - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.61.2", -] - -[[package]] -name = "muda" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" -dependencies = [ - "crossbeam-channel", - "dpi", - "gtk", - "keyboard-types", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "once_cell", - "png", - "serde", - "thiserror 2.0.18", - "windows-sys 0.60.2", -] - -[[package]] -name = "ndk" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" -dependencies = [ - "bitflags 2.11.0", - "jni-sys", - "log", - "ndk-sys", - "num_enum", - "raw-window-handle", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.6.0+11769913" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" -dependencies = [ - "jni-sys", -] - -[[package]] -name = "nebula-os" -version = "0.1.0" -dependencies = [ - "rusqlite", - "serde", - "serde_json", - "tauri", - "tauri-build", - "tauri-plugin-opener", - "ureq", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - -[[package]] -name = "num-conv" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_enum" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" -dependencies = [ - "num_enum_derive", - "rustversion", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" -dependencies = [ - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "objc2" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" -dependencies = [ - "objc2-encode", - "objc2-exception-helper", -] - -[[package]] -name = "objc2-app-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" -dependencies = [ - "bitflags 2.11.0", - "block2", - "libc", - "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-core-text", - "objc2-core-video", - "objc2-foundation", - "objc2-quartz-core", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-data" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags 2.11.0", - "dispatch2", - "objc2", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" -dependencies = [ - "bitflags 2.11.0", - "dispatch2", - "objc2", - "objc2-core-foundation", - "objc2-io-surface", -] - -[[package]] -name = "objc2-core-image" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-text" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", -] - -[[package]] -name = "objc2-core-video" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-io-surface", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-exception-helper" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" -dependencies = [ - "cc", -] - -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags 2.11.0", - "block2", - "libc", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-surface" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-javascript-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a1e6550c4caed348956ce3370c9ffeca70bb1dbed4fa96112e7c6170e074586" -dependencies = [ - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc2-security" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" -dependencies = [ - "bitflags 2.11.0", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc2-web-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" -dependencies = [ - "bitflags 2.11.0", - "block2", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "objc2-javascript-core", - "objc2-security", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[package]] -name = "open" -version = "5.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" -dependencies = [ - "dunce", - "is-wsl", - "libc", - "pathdiff", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "pango" -version = "0.18.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" -dependencies = [ - "gio", - "glib", - "libc", - "once_cell", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link 0.2.1", -] - -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "phf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_shared 0.8.0", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_macros 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", -] - -[[package]] -name = "phf" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -dependencies = [ - "phf_macros 0.11.3", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", -] - -[[package]] -name = "phf_codegen" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", -] - -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.5", -] - -[[package]] -name = "phf_generator" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -dependencies = [ - "phf_shared 0.11.3", - "rand 0.8.5", -] - -[[package]] -name = "phf_macros" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "phf_macros" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -dependencies = [ - "siphasher 1.0.2", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - -[[package]] -name = "pkg-config" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" - -[[package]] -name = "plist" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" -dependencies = [ - "base64 0.22.1", - "indexmap 2.13.0", - "quick-xml", - "serde", - "time", -] - -[[package]] -name = "png" -version = "0.17.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "potential_utf" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn 2.0.115", -] - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" -dependencies = [ - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "proc-macro-crate" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" -dependencies = [ - "toml_edit 0.23.10+spec-1.0.0", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quick-xml" -version = "0.38.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" -dependencies = [ - "memchr", -] - -[[package]] -name = "quote" -version = "1.0.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "raw-window-handle" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags 2.11.0", -] - -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror 2.0.18", -] - -[[package]] -name = "ref-cast" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" - -[[package]] -name = "reqwest" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "sync_wrapper", - "tokio", - "tokio-util", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rusqlite" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" -dependencies = [ - "bitflags 2.11.0", - "fallible-iterator", - "fallible-streaming-iterator", - "hashlink", - "libsqlite3-sys", - "smallvec", -] - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" -dependencies = [ - "bitflags 2.11.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" -dependencies = [ - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" -dependencies = [ - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schemars" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" -dependencies = [ - "dyn-clone", - "indexmap 1.9.3", - "schemars_derive", - "serde", - "serde_json", - "url", - "uuid", -] - -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.115", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "selectors" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" -dependencies = [ - "bitflags 1.3.2", - "cssparser", - "derive_more", - "fxhash", - "log", - "phf 0.8.0", - "phf_codegen 0.8.0", - "precomputed-hash", - "servo_arc", - "smallvec", -] - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" -dependencies = [ - "serde", - "serde_core", -] - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde-untagged" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" -dependencies = [ - "erased-serde", - "serde", - "serde_core", - "typeid", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "serde_derive_internals" -version = "0.29.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_repr" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "serde_spanned" -version = "0.6.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_spanned" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_with" -version = "3.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.13.0", - "schemars 0.9.0", - "schemars 1.2.1", - "serde_core", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "serialize-to-javascript" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" -dependencies = [ - "serde", - "serde_json", - "serialize-to-javascript-impl", -] - -[[package]] -name = "serialize-to-javascript-impl" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "servo_arc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" -dependencies = [ - "nodrop", - "stable_deref_trait", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "simd-adler32" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "siphasher" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" -dependencies = [ - "libc", - "windows-sys 0.60.2", -] - -[[package]] -name = "softbuffer" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" -dependencies = [ - "bytemuck", - "js-sys", - "ndk", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "objc2-quartz-core", - "raw-window-handle", - "redox_syscall", - "tracing", - "wasm-bindgen", - "web-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "soup3" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" -dependencies = [ - "futures-channel", - "gio", - "glib", - "libc", - "soup3-sys", -] - -[[package]] -name = "soup3-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.11.3", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "swift-rs" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" -dependencies = [ - "base64 0.21.7", - "serde", - "serde_json", -] - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.115" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr", - "heck 0.5.0", - "pkg-config", - "toml 0.8.2", - "version-compare", -] - -[[package]] -name = "tao" -version = "0.34.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7" -dependencies = [ - "bitflags 2.11.0", - "block2", - "core-foundation", - "core-graphics", - "crossbeam-channel", - "dispatch", - "dlopen2", - "dpi", - "gdkwayland-sys", - "gdkx11-sys", - "gtk", - "jni", - "lazy_static", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "once_cell", - "parking_lot", - "raw-window-handle", - "scopeguard", - "tao-macros", - "unicode-segmentation", - "url", - "windows", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - -[[package]] -name = "tao-macros" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "target-lexicon" -version = "0.12.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" - -[[package]] -name = "tauri" -version = "2.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "463ae8677aa6d0f063a900b9c41ecd4ac2b7ca82f0b058cc4491540e55b20129" -dependencies = [ - "anyhow", - "bytes", - "cookie", - "dirs", - "dunce", - "embed_plist", - "getrandom 0.3.4", - "glob", - "gtk", - "heck 0.5.0", - "http", - "http-range", - "jni", - "libc", - "log", - "mime", - "muda", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "percent-encoding", - "plist", - "raw-window-handle", - "reqwest", - "serde", - "serde_json", - "serde_repr", - "serialize-to-javascript", - "swift-rs", - "tauri-build", - "tauri-macros", - "tauri-runtime", - "tauri-runtime-wry", - "tauri-utils", - "thiserror 2.0.18", - "tokio", - "tray-icon", - "url", - "webkit2gtk", - "webview2-com", - "window-vibrancy", - "windows", -] - -[[package]] -name = "tauri-build" -version = "2.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca7bd893329425df750813e95bd2b643d5369d929438da96d5bbb7cc2c918f74" -dependencies = [ - "anyhow", - "cargo_toml", - "dirs", - "glob", - "heck 0.5.0", - "json-patch", - "schemars 0.8.22", - "semver", - "serde", - "serde_json", - "tauri-utils", - "tauri-winres", - "toml 0.9.12+spec-1.1.0", - "walkdir", -] - -[[package]] -name = "tauri-codegen" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac423e5859d9f9ccdd32e3cf6a5866a15bedbf25aa6630bcb2acde9468f6ae3" -dependencies = [ - "base64 0.22.1", - "brotli", - "ico", - "json-patch", - "plist", - "png", - "proc-macro2", - "quote", - "semver", - "serde", - "serde_json", - "sha2", - "syn 2.0.115", - "tauri-utils", - "thiserror 2.0.18", - "time", - "url", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-macros" -version = "2.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b6a1bd2861ff0c8766b1d38b32a6a410f6dc6532d4ef534c47cfb2236092f59" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn 2.0.115", - "tauri-codegen", - "tauri-utils", -] - -[[package]] -name = "tauri-plugin" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692a77abd8b8773e107a42ec0e05b767b8d2b7ece76ab36c6c3947e34df9f53f" -dependencies = [ - "anyhow", - "glob", - "plist", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri-utils", - "toml 0.9.12+spec-1.1.0", - "walkdir", -] - -[[package]] -name = "tauri-plugin-opener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc624469b06f59f5a29f874bbc61a2ed737c0f9c23ef09855a292c389c42e83f" -dependencies = [ - "dunce", - "glob", - "objc2-app-kit", - "objc2-foundation", - "open", - "schemars 0.8.22", - "serde", - "serde_json", - "tauri", - "tauri-plugin", - "thiserror 2.0.18", - "url", - "windows", - "zbus", -] - -[[package]] -name = "tauri-runtime" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b885ffeac82b00f1f6fd292b6e5aabfa7435d537cef57d11e38a489956535651" -dependencies = [ - "cookie", - "dpi", - "gtk", - "http", - "jni", - "objc2", - "objc2-ui-kit", - "objc2-web-kit", - "raw-window-handle", - "serde", - "serde_json", - "tauri-utils", - "thiserror 2.0.18", - "url", - "webkit2gtk", - "webview2-com", - "windows", -] - -[[package]] -name = "tauri-runtime-wry" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5204682391625e867d16584fedc83fc292fb998814c9f7918605c789cd876314" -dependencies = [ - "gtk", - "http", - "jni", - "log", - "objc2", - "objc2-app-kit", - "objc2-foundation", - "once_cell", - "percent-encoding", - "raw-window-handle", - "softbuffer", - "tao", - "tauri-runtime", - "tauri-utils", - "url", - "webkit2gtk", - "webview2-com", - "windows", - "wry", -] - -[[package]] -name = "tauri-utils" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcd169fccdff05eff2c1033210b9b94acd07a47e6fa9a3431cf09cfd4f01c87e" -dependencies = [ - "anyhow", - "brotli", - "cargo_metadata", - "ctor", - "dunce", - "glob", - "html5ever", - "http", - "infer", - "json-patch", - "kuchikiki", - "log", - "memchr", - "phf 0.11.3", - "proc-macro2", - "quote", - "regex", - "schemars 0.8.22", - "semver", - "serde", - "serde-untagged", - "serde_json", - "serde_with", - "swift-rs", - "thiserror 2.0.18", - "toml 0.9.12+spec-1.1.0", - "url", - "urlpattern", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-winres" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" -dependencies = [ - "dunce", - "embed-resource", - "toml 0.9.12+spec-1.1.0", -] - -[[package]] -name = "tempfile" -version = "3.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" -dependencies = [ - "fastrand", - "getrandom 0.4.1", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "tendril" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" -dependencies = [ - "futf", - "mac", - "utf-8", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "time" -version = "0.3.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" - -[[package]] -name = "time-macros" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tokio" -version = "1.49.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" -dependencies = [ - "bytes", - "libc", - "mio", - "pin-project-lite", - "socket2", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" -dependencies = [ - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "toml_edit 0.20.2", -] - -[[package]] -name = "toml" -version = "0.9.12+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" -dependencies = [ - "indexmap 2.13.0", - "serde_core", - "serde_spanned 1.0.4", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 0.7.14", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.13.0", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" -dependencies = [ - "indexmap 2.13.0", - "serde", - "serde_spanned 0.6.9", - "toml_datetime 0.6.3", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.23.10+spec-1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" -dependencies = [ - "indexmap 2.13.0", - "toml_datetime 0.7.5+spec-1.1.0", - "toml_parser", - "winnow 0.7.14", -] - -[[package]] -name = "toml_parser" -version = "1.0.8+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" -dependencies = [ - "winnow 0.7.14", -] - -[[package]] -name = "toml_writer" -version = "1.0.6+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" -dependencies = [ - "bitflags 2.11.0", - "bytes", - "futures-util", - "http", - "http-body", - "iri-string", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "tray-icon" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c" -dependencies = [ - "crossbeam-channel", - "dirs", - "libappindicator", - "muda", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-foundation", - "once_cell", - "png", - "serde", - "thiserror 2.0.18", - "windows-sys 0.60.2", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typeid" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" - -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "uds_windows" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" -dependencies = [ - "memoffset", - "tempfile", - "winapi", -] - -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-ucd-ident" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - -[[package]] -name = "unicode-ident" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "ureq" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" -dependencies = [ - "base64 0.22.1", - "flate2", - "log", - "percent-encoding", - "rustls", - "rustls-pki-types", - "ureq-proto", - "utf8-zero", - "webpki-roots", -] - -[[package]] -name = "ureq-proto" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" -dependencies = [ - "base64 0.22.1", - "http", - "httparse", - "log", -] - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", - "serde_derive", -] - -[[package]] -name = "urlpattern" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" -dependencies = [ - "regex", - "serde", - "unic-ucd-ident", - "url", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf8-zero" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" -dependencies = [ - "getrandom 0.4.1", - "js-sys", - "serde_core", - "wasm-bindgen", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version-compare" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "vswhom" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" -dependencies = [ - "libc", - "vswhom-sys", -] - -[[package]] -name = "vswhom-sys" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.2+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" -dependencies = [ - "cfg-if", - "futures-util", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 2.0.115", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap 2.13.0", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasm-streams" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.11.0", - "hashbrown 0.15.5", - "indexmap 2.13.0", - "semver", -] - -[[package]] -name = "web-sys" -version = "0.3.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webkit2gtk" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk", - "gdk-sys", - "gio", - "gio-sys", - "glib", - "glib-sys", - "gobject-sys", - "gtk", - "gtk-sys", - "javascriptcore-rs", - "libc", - "once_cell", - "soup3", - "webkit2gtk-sys", -] - -[[package]] -name = "webkit2gtk-sys" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" -dependencies = [ - "bitflags 1.3.2", - "cairo-sys-rs", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "gtk-sys", - "javascriptcore-rs-sys", - "libc", - "pkg-config", - "soup3-sys", - "system-deps", -] - -[[package]] -name = "webpki-roots" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "webview2-com" -version = "0.38.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" -dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows", - "windows-core 0.61.2", - "windows-implement", - "windows-interface", -] - -[[package]] -name = "webview2-com-macros" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "webview2-com-sys" -version = "0.38.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" -dependencies = [ - "thiserror 2.0.18", - "windows", - "windows-core 0.61.2", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "window-vibrancy" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" -dependencies = [ - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "raw-window-handle", - "windows-sys 0.59.0", - "windows-version", -] - -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections", - "windows-core 0.61.2", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core 0.61.2", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", -] - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core 0.61.2", - "windows-link 0.1.3", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link 0.2.1", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-version" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" -dependencies = [ - "windows-link 0.2.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" -dependencies = [ - "cfg-if", - "windows-sys 0.59.0", -] - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck 0.5.0", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck 0.5.0", - "indexmap 2.13.0", - "prettyplease", - "syn 2.0.115", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.115", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.0", - "indexmap 2.13.0", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.13.0", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - -[[package]] -name = "writeable" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" - -[[package]] -name = "wry" -version = "0.54.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb26159b420aa77684589a744ae9a9461a95395b848764ad12290a14d960a11a" -dependencies = [ - "base64 0.22.1", - "block2", - "cookie", - "crossbeam-channel", - "dirs", - "dpi", - "dunce", - "gdkx11", - "gtk", - "html5ever", - "http", - "javascriptcore-rs", - "jni", - "kuchikiki", - "libc", - "ndk", - "objc2", - "objc2-app-kit", - "objc2-core-foundation", - "objc2-foundation", - "objc2-ui-kit", - "objc2-web-kit", - "once_cell", - "percent-encoding", - "raw-window-handle", - "sha2", - "soup3", - "tao-macros", - "thiserror 2.0.18", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows", - "windows-core 0.61.2", - "windows-version", - "x11-dl", -] - -[[package]] -name = "x11" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "yoke" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", - "synstructure", -] - -[[package]] -name = "zbus" -version = "5.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfeff997a0aaa3eb20c4652baf788d2dfa6d2839a0ead0b3ff69ce2f9c4bdd1" -dependencies = [ - "async-broadcast", - "async-executor", - "async-io", - "async-lock", - "async-process", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "enumflags2", - "event-listener", - "futures-core", - "futures-lite", - "hex", - "libc", - "ordered-stream", - "rustix", - "serde", - "serde_repr", - "tracing", - "uds_windows", - "uuid", - "windows-sys 0.61.2", - "winnow 0.7.14", - "zbus_macros", - "zbus_names", - "zvariant", -] - -[[package]] -name = "zbus_macros" -version = "5.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bbd5a90dbe8feee5b13def448427ae314ccd26a49cac47905cafefb9ff846f1" -dependencies = [ - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.115", - "zbus_names", - "zvariant", - "zvariant_utils", -] - -[[package]] -name = "zbus_names" -version = "4.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffd8af6d5b78619bab301ff3c560a5bd22426150253db278f164d6cf3b72c50f" -dependencies = [ - "serde", - "winnow 0.7.14", - "zvariant", -] - -[[package]] -name = "zerocopy" -version = "0.8.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" - -[[package]] -name = "zerotrie" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.115", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" - -[[package]] -name = "zvariant" -version = "5.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b64ef4f40c7951337ddc7023dd03528a57a3ce3408ee9da5e948bd29b232c4" -dependencies = [ - "endi", - "enumflags2", - "serde", - "winnow 0.7.14", - "zvariant_derive", - "zvariant_utils", -] - -[[package]] -name = "zvariant_derive" -version = "5.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "484d5d975eb7afb52cc6b929c13d3719a20ad650fea4120e6310de3fc55e415c" -dependencies = [ - "proc-macro-crate 3.4.0", - "proc-macro2", - "quote", - "syn 2.0.115", - "zvariant_utils", -] - -[[package]] -name = "zvariant_utils" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75c23a64ef8f40f13a6989991e643554d9bef1d682a281160cf0c1bc389c5e9" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn 2.0.115", - "winnow 0.7.14", -] diff --git a/Bigscreen/src-tauri/Cargo.toml b/Bigscreen/src-tauri/Cargo.toml deleted file mode 100644 index 5d207b4..0000000 --- a/Bigscreen/src-tauri/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "nebula-os" -version = "0.1.0" -description = "A Tauri App" -authors = ["you"] -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -# The `_lib` suffix may seem redundant but it is necessary -# to make the lib name unique and wouldn't conflict with the bin name. -# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 -name = "nebula_os_lib" -crate-type = ["staticlib", "cdylib", "rlib"] - -[build-dependencies] -tauri-build = { version = "2", features = [] } - -[dependencies] -tauri = { version = "2", features = ["protocol-asset"] } -tauri-plugin-opener = "2" -serde = { version = "1", features = ["derive"] } -serde_json = "1" -rusqlite = { version = "0.31", features = ["bundled"] } -ureq = "3.3.0" - diff --git a/Bigscreen/src-tauri/build.rs b/Bigscreen/src-tauri/build.rs deleted file mode 100644 index d860e1e..0000000 --- a/Bigscreen/src-tauri/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - tauri_build::build() -} diff --git a/Bigscreen/src-tauri/capabilities/default.json b/Bigscreen/src-tauri/capabilities/default.json deleted file mode 100644 index 4cdbf49..0000000 --- a/Bigscreen/src-tauri/capabilities/default.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "default", - "description": "Capability for the main window", - "windows": ["main"], - "permissions": [ - "core:default", - "opener:default" - ] -} diff --git a/Bigscreen/src-tauri/icons/128x128.png b/Bigscreen/src-tauri/icons/128x128.png deleted file mode 100644 index 6be5e50..0000000 Binary files a/Bigscreen/src-tauri/icons/128x128.png and /dev/null differ diff --git a/Bigscreen/src-tauri/icons/128x128@2x.png b/Bigscreen/src-tauri/icons/128x128@2x.png deleted file mode 100644 index e81bece..0000000 Binary files a/Bigscreen/src-tauri/icons/128x128@2x.png and /dev/null differ diff --git a/Bigscreen/src-tauri/icons/32x32.png b/Bigscreen/src-tauri/icons/32x32.png deleted file mode 100644 index a437dd5..0000000 Binary files a/Bigscreen/src-tauri/icons/32x32.png and /dev/null differ diff --git a/Bigscreen/src-tauri/icons/Square107x107Logo.png b/Bigscreen/src-tauri/icons/Square107x107Logo.png deleted file mode 100644 index 0ca4f27..0000000 Binary files a/Bigscreen/src-tauri/icons/Square107x107Logo.png and /dev/null differ diff --git a/Bigscreen/src-tauri/icons/Square142x142Logo.png b/Bigscreen/src-tauri/icons/Square142x142Logo.png deleted file mode 100644 index b81f820..0000000 Binary files a/Bigscreen/src-tauri/icons/Square142x142Logo.png and /dev/null differ diff --git a/Bigscreen/src-tauri/icons/Square150x150Logo.png b/Bigscreen/src-tauri/icons/Square150x150Logo.png deleted file mode 100644 index 624c7bf..0000000 Binary files a/Bigscreen/src-tauri/icons/Square150x150Logo.png and /dev/null differ diff --git a/Bigscreen/src-tauri/icons/Square284x284Logo.png b/Bigscreen/src-tauri/icons/Square284x284Logo.png deleted file mode 100644 index c021d2b..0000000 Binary files a/Bigscreen/src-tauri/icons/Square284x284Logo.png and /dev/null differ diff --git a/Bigscreen/src-tauri/icons/Square30x30Logo.png b/Bigscreen/src-tauri/icons/Square30x30Logo.png deleted file mode 100644 index 6219700..0000000 Binary files a/Bigscreen/src-tauri/icons/Square30x30Logo.png and /dev/null differ diff --git a/Bigscreen/src-tauri/icons/Square310x310Logo.png b/Bigscreen/src-tauri/icons/Square310x310Logo.png deleted file mode 100644 index f9bc048..0000000 Binary files a/Bigscreen/src-tauri/icons/Square310x310Logo.png and /dev/null differ diff --git a/Bigscreen/src-tauri/icons/Square44x44Logo.png b/Bigscreen/src-tauri/icons/Square44x44Logo.png deleted file mode 100644 index d5fbfb2..0000000 Binary files a/Bigscreen/src-tauri/icons/Square44x44Logo.png and /dev/null differ diff --git a/Bigscreen/src-tauri/icons/Square71x71Logo.png b/Bigscreen/src-tauri/icons/Square71x71Logo.png deleted file mode 100644 index 63440d7..0000000 Binary files a/Bigscreen/src-tauri/icons/Square71x71Logo.png and /dev/null differ diff --git a/Bigscreen/src-tauri/icons/Square89x89Logo.png b/Bigscreen/src-tauri/icons/Square89x89Logo.png deleted file mode 100644 index f3f705a..0000000 Binary files a/Bigscreen/src-tauri/icons/Square89x89Logo.png and /dev/null differ diff --git a/Bigscreen/src-tauri/icons/StoreLogo.png b/Bigscreen/src-tauri/icons/StoreLogo.png deleted file mode 100644 index 4556388..0000000 Binary files a/Bigscreen/src-tauri/icons/StoreLogo.png and /dev/null differ diff --git a/Bigscreen/src-tauri/icons/icon.icns b/Bigscreen/src-tauri/icons/icon.icns deleted file mode 100644 index 12a5bce..0000000 Binary files a/Bigscreen/src-tauri/icons/icon.icns and /dev/null differ diff --git a/Bigscreen/src-tauri/icons/icon.ico b/Bigscreen/src-tauri/icons/icon.ico deleted file mode 100644 index b3636e4..0000000 Binary files a/Bigscreen/src-tauri/icons/icon.ico and /dev/null differ diff --git a/Bigscreen/src-tauri/icons/icon.png b/Bigscreen/src-tauri/icons/icon.png deleted file mode 100644 index e1cd261..0000000 Binary files a/Bigscreen/src-tauri/icons/icon.png and /dev/null differ diff --git a/Bigscreen/src-tauri/src/lib.rs b/Bigscreen/src-tauri/src/lib.rs deleted file mode 100644 index 9b662c0..0000000 --- a/Bigscreen/src-tauri/src/lib.rs +++ /dev/null @@ -1,210 +0,0 @@ -mod library; -mod storage; - -use library::commands::{ - launch_library_game, list_library_games, scan_library_command, update_library_game, -}; -use rusqlite::{params, Connection, OptionalExtension}; -use serde::Serialize; -use std::time::{SystemTime, UNIX_EPOCH}; -use storage::AppStorage; -use tauri::Manager; - -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -struct UserRecord { - id: i64, - name: String, - first_name: String, - last_name: Option, - created_at_unix_ms: i64, -} - -fn now_unix_ms() -> i64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|duration| duration.as_millis() as i64) - .unwrap_or(0) -} - -fn sanitize_name(name: &str) -> Result { - let trimmed = name.trim(); - if trimmed.is_empty() { - return Err("User name cannot be empty.".to_string()); - } - if trimmed.chars().count() > 64 { - return Err("User name is too long (max 64 chars).".to_string()); - } - Ok(trimmed.to_string()) -} - -fn sanitize_optional_name(name: Option) -> Result, String> { - let Some(value) = name else { - return Ok(None); - }; - let trimmed = value.trim(); - if trimmed.is_empty() { - return Ok(None); - } - if trimmed.chars().count() > 64 { - return Err("Name is too long (max 64 chars).".to_string()); - } - Ok(Some(trimmed.to_string())) -} - -fn build_display_name(first_name: &str, last_name: Option<&str>) -> String { - let last = last_name.map(str::trim).unwrap_or(""); - if last.is_empty() { - first_name.to_string() - } else { - format!("{first_name} {last}") - } -} - -fn ensure_users_schema(conn: &Connection) -> Result<(), String> { - conn.execute( - "CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - created_at_unix_ms INTEGER NOT NULL - )", - [], - ) - .map_err(|err| format!("Failed to prepare users table: {err}"))?; - - let mut stmt = conn - .prepare("PRAGMA table_info(users)") - .map_err(|err| format!("Failed to inspect users schema: {err}"))?; - let mut rows = stmt - .query([]) - .map_err(|err| format!("Failed to read users schema: {err}"))?; - - let mut has_first_name = false; - let mut has_last_name = false; - while let Some(row) = rows - .next() - .map_err(|err| format!("Failed to iterate users schema: {err}"))? - { - let column_name: String = row - .get(1) - .map_err(|err| format!("Failed to parse users schema: {err}"))?; - if column_name == "first_name" { - has_first_name = true; - } - if column_name == "last_name" { - has_last_name = true; - } - } - - if !has_first_name { - conn.execute("ALTER TABLE users ADD COLUMN first_name TEXT", []) - .map_err(|err| format!("Failed to add first_name column: {err}"))?; - conn.execute( - "UPDATE users SET first_name = TRIM(name) WHERE first_name IS NULL OR TRIM(first_name) = ''", - [], - ) - .map_err(|err| format!("Failed to backfill first_name: {err}"))?; - } - - if !has_last_name { - conn.execute("ALTER TABLE users ADD COLUMN last_name TEXT", []) - .map_err(|err| format!("Failed to add last_name column: {err}"))?; - } - - Ok(()) -} - -#[tauri::command] -fn get_first_user(storage: tauri::State<'_, AppStorage>) -> Result, String> { - let conn = storage.connect_users()?; - ensure_users_schema(&conn)?; - conn.query_row( - "SELECT - id, - COALESCE( - NULLIF(TRIM(COALESCE(first_name, '') || ' ' || COALESCE(last_name, '')), ''), - name - ) AS display_name, - COALESCE(NULLIF(TRIM(first_name), ''), name) AS first_name, - NULLIF(TRIM(COALESCE(last_name, '')), '') AS last_name, - created_at_unix_ms - FROM users - ORDER BY id ASC - LIMIT 1", - [], - |row| { - Ok(UserRecord { - id: row.get(0)?, - name: row.get(1)?, - first_name: row.get(2)?, - last_name: row.get(3)?, - created_at_unix_ms: row.get(4)?, - }) - }, - ) - .optional() - .map_err(|err| format!("Failed to load user: {err}")) -} - -#[tauri::command] -fn create_user( - first_name: String, - last_name: Option, - storage: tauri::State<'_, AppStorage>, -) -> Result { - let safe_first_name = sanitize_name(&first_name)?; - let safe_last_name = sanitize_optional_name(last_name)?; - let display_name = build_display_name(&safe_first_name, safe_last_name.as_deref()); - let created_at_unix_ms = now_unix_ms(); - let conn = storage.connect_users()?; - ensure_users_schema(&conn)?; - conn.execute( - "INSERT INTO users (name, first_name, last_name, created_at_unix_ms) VALUES (?1, ?2, ?3, ?4)", - params![ - display_name, - safe_first_name, - safe_last_name, - created_at_unix_ms - ], - ) - .map_err(|err| format!("Failed to create user: {err}"))?; - - let id = conn.last_insert_rowid(); - Ok(UserRecord { - id, - name: build_display_name(&safe_first_name, safe_last_name.as_deref()), - first_name: safe_first_name, - last_name: safe_last_name, - created_at_unix_ms, - }) -} - -#[cfg_attr(mobile, tauri::mobile_entry_point)] -pub fn run() { - tauri::Builder::default() - .plugin(tauri_plugin_opener::init()) - .setup(|app| { - let app_data_dir = app - .path() - .app_data_dir() - .map_err(|err| format!("Failed to resolve app data dir: {err}"))?; - let storage = AppStorage::new(app_data_dir)?; - let users_conn = storage.connect_users()?; - ensure_users_schema(&users_conn)?; - drop(users_conn); - library::initialize(&storage)?; - - app.manage(storage); - Ok(()) - }) - .invoke_handler(tauri::generate_handler![ - get_first_user, - create_user, - list_library_games, - launch_library_game, - scan_library_command, - update_library_game - ]) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); -} diff --git a/Bigscreen/src-tauri/src/library/commands.rs b/Bigscreen/src-tauri/src/library/commands.rs deleted file mode 100644 index a02409d..0000000 --- a/Bigscreen/src-tauri/src/library/commands.rs +++ /dev/null @@ -1,98 +0,0 @@ -use super::db::{ensure_library_schema, list_games, update_customization}; -use super::models::{GameCustomizationRequest, LibraryGame, LibraryScanRequest, ScanSummary}; -use super::{list_visible_games, scan_library}; -use crate::storage::AppStorage; -use serde::Serialize; -use std::path::PathBuf; - -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct LaunchLibraryGameResult { - pub launched: bool, - pub action: String, - pub message: String, -} - -#[tauri::command] -pub async fn scan_library_command( - request: LibraryScanRequest, - storage: tauri::State<'_, AppStorage>, -) -> Result { - let storage = storage.inner().clone(); - let local_folders = request - .local_folders - .into_iter() - .map(PathBuf::from) - .collect::>(); - - tauri::async_runtime::spawn_blocking(move || scan_library(storage, local_folders)) - .await - .map_err(|err| format!("Library scanner task failed: {err}"))? -} - -#[tauri::command] -pub async fn list_library_games( - storage: tauri::State<'_, AppStorage>, -) -> Result, String> { - let storage = storage.inner().clone(); - tauri::async_runtime::spawn_blocking(move || list_visible_games(&storage)) - .await - .map_err(|err| format!("Library list task failed: {err}"))? -} - -#[tauri::command] -pub async fn update_library_game( - request: GameCustomizationRequest, - storage: tauri::State<'_, AppStorage>, -) -> Result, String> { - let storage = storage.inner().clone(); - tauri::async_runtime::spawn_blocking(move || { - let conn = storage.connect_library()?; - ensure_library_schema(&conn)?; - update_customization(&conn, request) - }) - .await - .map_err(|err| format!("Library update task failed: {err}"))? -} - -#[tauri::command] -pub async fn launch_library_game( - game_id: i64, - storage: tauri::State<'_, AppStorage>, -) -> Result { - let storage = storage.inner().clone(); - tauri::async_runtime::spawn_blocking(move || { - let conn = storage.connect_library()?; - ensure_library_schema(&conn)?; - let game = list_games(&conn, true)? - .into_iter() - .find(|game| game.id == game_id) - .ok_or_else(|| "Library app was not found.".to_string())?; - - let launch_target = game - .launch_command - .as_deref() - .or(game.executable_path.as_deref()) - .unwrap_or(game.install_path.as_str()); - - // Future provider launchers plug in here: - // Steam: steam://run/, GOG/Epic URI handlers, emulator profiles, - // and native executable spawning with per-app environment overrides. - println!( - "[NebulaOS] launch placeholder: {} via {}", - game.user_title.as_deref().unwrap_or(&game.title), - launch_target - ); - - Ok(LaunchLibraryGameResult { - launched: true, - action: "placeholder".to_string(), - message: format!( - "Launch requested for {}.", - game.user_title.unwrap_or(game.title) - ), - }) - }) - .await - .map_err(|err| format!("Library launch task failed: {err}"))? -} diff --git a/Bigscreen/src-tauri/src/library/db.rs b/Bigscreen/src-tauri/src/library/db.rs deleted file mode 100644 index f461f2a..0000000 --- a/Bigscreen/src-tauri/src/library/db.rs +++ /dev/null @@ -1,480 +0,0 @@ -use super::models::{ - infer_library_item_kind, GameCandidate, GameCustomizationRequest, GameMetadata, GameSource, - LibraryGame, LibraryItemKind, MetadataStatus, -}; -use rusqlite::{params, Connection, OptionalExtension}; -use std::time::{SystemTime, UNIX_EPOCH}; - -pub fn now_unix_ms() -> i64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|duration| duration.as_millis() as i64) - .unwrap_or(0) -} - -pub fn ensure_library_schema(conn: &Connection) -> Result<(), String> { - conn.execute_batch( - "PRAGMA foreign_keys = ON; - - CREATE TABLE IF NOT EXISTS games ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - title TEXT NOT NULL, - platform_source TEXT NOT NULL, - source_store TEXT NOT NULL, - source_app_id TEXT, - install_path TEXT NOT NULL, - executable_path TEXT, - launch_command TEXT, - description TEXT, - app_kind TEXT NOT NULL DEFAULT 'game', - steam_app_type TEXT, - genres_json TEXT NOT NULL DEFAULT '[]', - steam_categories_json TEXT NOT NULL DEFAULT '[]', - release_date TEXT, - developer TEXT, - publisher TEXT, - cover_image TEXT, - hero_image TEXT, - icon_image TEXT, - metadata_status TEXT NOT NULL DEFAULT 'pending', - last_scanned INTEGER NOT NULL, - user_hidden INTEGER NOT NULL DEFAULT 0, - user_favourite INTEGER NOT NULL DEFAULT 0, - user_title TEXT, - manual_metadata_json TEXT, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL - ); - - CREATE UNIQUE INDEX IF NOT EXISTS idx_games_source_app - ON games (platform_source, source_app_id) - WHERE source_app_id IS NOT NULL; - - CREATE UNIQUE INDEX IF NOT EXISTS idx_games_install_path - ON games (install_path); - - CREATE INDEX IF NOT EXISTS idx_games_hidden ON games (user_hidden); - CREATE INDEX IF NOT EXISTS idx_games_metadata_status ON games (metadata_status); - - CREATE TABLE IF NOT EXISTS scan_runs ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - started_at INTEGER NOT NULL, - completed_at INTEGER, - status TEXT NOT NULL, - discovered_count INTEGER NOT NULL DEFAULT 0, - metadata_matched_count INTEGER NOT NULL DEFAULT 0, - unmatched_count INTEGER NOT NULL DEFAULT 0, - error TEXT - ); - - CREATE TABLE IF NOT EXISTS metadata_cache ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - provider TEXT NOT NULL, - lookup_key TEXT NOT NULL, - metadata_json TEXT NOT NULL, - created_at INTEGER NOT NULL, - updated_at INTEGER NOT NULL, - UNIQUE(provider, lookup_key) - );", - ) - .map_err(|err| format!("Failed to prepare library schema: {err}"))?; - - ensure_column( - conn, - "games", - "app_kind", - "app_kind TEXT NOT NULL DEFAULT 'game'", - )?; - ensure_column(conn, "games", "steam_app_type", "steam_app_type TEXT")?; - ensure_column( - conn, - "games", - "steam_categories_json", - "steam_categories_json TEXT NOT NULL DEFAULT '[]'", - )?; - - Ok(()) -} - -pub fn upsert_candidate( - conn: &Connection, - candidate: &GameCandidate, - metadata: Option<&GameMetadata>, -) -> Result<(), String> { - let now = now_unix_ms(); - let metadata_status = metadata - .map(|value| value.status.as_str()) - .unwrap_or(MetadataStatus::NeedsReview.as_str()); - let title = metadata - .and_then(|value| value.title.as_deref()) - .unwrap_or(&candidate.title); - let source = candidate.source.as_str(); - let app_kind = metadata - .map(|value| value.app_kind.clone()) - .unwrap_or_else(|| infer_library_item_kind(None, Some(&candidate.title), &[], &[])); - let genres_json = metadata - .map(|value| serde_json::to_string(&value.genres)) - .transpose() - .map_err(|err| format!("Failed to encode genres: {err}"))? - .unwrap_or_else(|| "[]".to_string()); - let steam_categories_json = metadata - .map(|value| serde_json::to_string(&value.steam_categories)) - .transpose() - .map_err(|err| format!("Failed to encode Steam categories: {err}"))? - .unwrap_or_else(|| "[]".to_string()); - - conn.execute( - "INSERT INTO games ( - title, - platform_source, - source_store, - source_app_id, - install_path, - executable_path, - launch_command, - description, - app_kind, - steam_app_type, - genres_json, - steam_categories_json, - release_date, - developer, - publisher, - cover_image, - hero_image, - icon_image, - metadata_status, - last_scanned, - created_at, - updated_at - ) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22) - ON CONFLICT(platform_source, source_app_id) WHERE source_app_id IS NOT NULL DO UPDATE SET - title = excluded.title, - source_store = excluded.source_store, - install_path = excluded.install_path, - executable_path = excluded.executable_path, - launch_command = excluded.launch_command, - description = COALESCE(excluded.description, games.description), - app_kind = excluded.app_kind, - steam_app_type = COALESCE(excluded.steam_app_type, games.steam_app_type), - genres_json = excluded.genres_json, - steam_categories_json = excluded.steam_categories_json, - release_date = COALESCE(excluded.release_date, games.release_date), - developer = COALESCE(excluded.developer, games.developer), - publisher = COALESCE(excluded.publisher, games.publisher), - cover_image = excluded.cover_image, - hero_image = excluded.hero_image, - icon_image = excluded.icon_image, - metadata_status = excluded.metadata_status, - last_scanned = excluded.last_scanned, - updated_at = excluded.updated_at - ON CONFLICT(install_path) DO UPDATE SET - title = excluded.title, - platform_source = excluded.platform_source, - source_store = excluded.source_store, - source_app_id = COALESCE(excluded.source_app_id, games.source_app_id), - executable_path = excluded.executable_path, - launch_command = excluded.launch_command, - description = COALESCE(excluded.description, games.description), - app_kind = excluded.app_kind, - steam_app_type = COALESCE(excluded.steam_app_type, games.steam_app_type), - genres_json = excluded.genres_json, - steam_categories_json = excluded.steam_categories_json, - release_date = COALESCE(excluded.release_date, games.release_date), - developer = COALESCE(excluded.developer, games.developer), - publisher = COALESCE(excluded.publisher, games.publisher), - cover_image = excluded.cover_image, - hero_image = excluded.hero_image, - icon_image = excluded.icon_image, - metadata_status = excluded.metadata_status, - last_scanned = excluded.last_scanned, - updated_at = excluded.updated_at", - params![ - title, - source, - source, - candidate.app_id, - candidate.install_path.to_string_lossy(), - candidate - .executable_path - .as_ref() - .map(|path| path.to_string_lossy().to_string()), - candidate.launch_command, - metadata.and_then(|value| value.description.as_deref()), - app_kind.as_str(), - metadata.and_then(|value| value.steam_app_type.as_deref()), - genres_json, - steam_categories_json, - metadata.and_then(|value| value.release_date.as_deref()), - metadata.and_then(|value| value.developer.as_deref()), - metadata.and_then(|value| value.publisher.as_deref()), - metadata - .and_then(|value| value.cover_image.as_ref()) - .map(|path| path.to_string_lossy().to_string()), - metadata - .and_then(|value| value.hero_image.as_ref()) - .map(|path| path.to_string_lossy().to_string()), - metadata - .and_then(|value| value.icon_image.as_ref()) - .map(|path| path.to_string_lossy().to_string()), - metadata_status, - now, - now, - now, - ], - ) - .map_err(|err| format!("Failed to upsert library game: {err}"))?; - - Ok(()) -} - -pub fn list_games(conn: &Connection, include_hidden: bool) -> Result, String> { - let where_clause = if include_hidden { - "" - } else { - "WHERE user_hidden = 0" - }; - let sql = format!( - "SELECT - id, - title, - platform_source, - source_store, - source_app_id, - install_path, - executable_path, - launch_command, - description, - app_kind, - steam_app_type, - genres_json, - steam_categories_json, - release_date, - developer, - publisher, - cover_image, - hero_image, - icon_image, - metadata_status, - last_scanned, - user_hidden, - user_favourite, - user_title - FROM games - {where_clause} - ORDER BY COALESCE(user_title, title) COLLATE NOCASE ASC" - ); - - let mut stmt = conn - .prepare(&sql) - .map_err(|err| format!("Failed to load library games: {err}"))?; - - let rows = stmt - .query_map([], |row| { - let genres_json: String = row.get(11)?; - let genres = parse_string_list(&genres_json); - let steam_categories_json: String = row.get(12)?; - let steam_categories = parse_string_list(&steam_categories_json); - let platform_source: String = row.get(2)?; - let stored_app_kind: String = row.get(9)?; - let metadata_status: String = row.get(19)?; - let steam_app_type: Option = row.get(10)?; - let title: String = row.get(1)?; - let inferred_app_kind = infer_library_item_kind( - steam_app_type.as_deref(), - Some(&title), - &genres, - &steam_categories, - ); - let app_kind = if inferred_app_kind == LibraryItemKind::Unknown { - LibraryItemKind::from_str(&stored_app_kind) - } else { - inferred_app_kind - }; - Ok(LibraryGame { - id: row.get(0)?, - title, - platform_source: GameSource::from_str(&platform_source), - source_store: row.get(3)?, - source_app_id: row.get(4)?, - install_path: row.get(5)?, - executable_path: row.get(6)?, - launch_command: row.get(7)?, - description: row.get(8)?, - app_kind, - steam_app_type, - genres, - steam_categories, - release_date: row.get(13)?, - developer: row.get(14)?, - publisher: row.get(15)?, - cover_image: row.get(16)?, - hero_image: row.get(17)?, - icon_image: row.get(18)?, - metadata_status: MetadataStatus::from_str(&metadata_status), - last_scanned: row.get(20)?, - user_hidden: row.get::<_, i64>(21)? != 0, - user_favourite: row.get::<_, i64>(22)? != 0, - user_title: row.get(23)?, - }) - }) - .map_err(|err| format!("Failed to read library games: {err}"))?; - - rows.collect::, _>>() - .map_err(|err| format!("Failed to parse library games: {err}")) -} - -pub fn update_customization( - conn: &Connection, - request: GameCustomizationRequest, -) -> Result, String> { - let now = now_unix_ms(); - let current = conn - .query_row( - "SELECT id FROM games WHERE id = ?1", - params![request.game_id], - |row| row.get::<_, i64>(0), - ) - .optional() - .map_err(|err| format!("Failed to find library game: {err}"))?; - - if current.is_none() { - return Ok(None); - } - - conn.execute( - "UPDATE games SET - user_title = COALESCE(?1, user_title), - executable_path = COALESCE(?2, executable_path), - user_hidden = COALESCE(?3, user_hidden), - user_favourite = COALESCE(?4, user_favourite), - metadata_status = CASE WHEN ?1 IS NULL THEN metadata_status ELSE 'manual' END, - updated_at = ?5 - WHERE id = ?6", - params![ - request.title.and_then(|value| { - let trimmed = value.trim().to_string(); - if trimmed.is_empty() { - None - } else { - Some(trimmed) - } - }), - request.executable_path, - request.hidden.map(|value| if value { 1 } else { 0 }), - request.favourite.map(|value| if value { 1 } else { 0 }), - now, - request.game_id, - ], - ) - .map_err(|err| format!("Failed to update library game: {err}"))?; - - conn.query_row( - "SELECT - id, - title, - platform_source, - source_store, - source_app_id, - install_path, - executable_path, - launch_command, - description, - app_kind, - steam_app_type, - genres_json, - steam_categories_json, - release_date, - developer, - publisher, - cover_image, - hero_image, - icon_image, - metadata_status, - last_scanned, - user_hidden, - user_favourite, - user_title - FROM games - WHERE id = ?1", - params![request.game_id], - |row| { - let genres_json: String = row.get(11)?; - let steam_categories_json: String = row.get(12)?; - let platform_source: String = row.get(2)?; - let stored_app_kind: String = row.get(9)?; - let metadata_status: String = row.get(19)?; - let steam_app_type: Option = row.get(10)?; - let title: String = row.get(1)?; - let genres = parse_string_list(&genres_json); - let steam_categories = parse_string_list(&steam_categories_json); - let inferred_app_kind = infer_library_item_kind( - steam_app_type.as_deref(), - Some(&title), - &genres, - &steam_categories, - ); - let app_kind = if inferred_app_kind == LibraryItemKind::Unknown { - LibraryItemKind::from_str(&stored_app_kind) - } else { - inferred_app_kind - }; - Ok(LibraryGame { - id: row.get(0)?, - title, - platform_source: GameSource::from_str(&platform_source), - source_store: row.get(3)?, - source_app_id: row.get(4)?, - install_path: row.get(5)?, - executable_path: row.get(6)?, - launch_command: row.get(7)?, - description: row.get(8)?, - app_kind, - steam_app_type, - genres, - steam_categories, - release_date: row.get(13)?, - developer: row.get(14)?, - publisher: row.get(15)?, - cover_image: row.get(16)?, - hero_image: row.get(17)?, - icon_image: row.get(18)?, - metadata_status: MetadataStatus::from_str(&metadata_status), - last_scanned: row.get(20)?, - user_hidden: row.get::<_, i64>(21)? != 0, - user_favourite: row.get::<_, i64>(22)? != 0, - user_title: row.get(23)?, - }) - }, - ) - .optional() - .map_err(|err| format!("Failed to reload library game: {err}")) -} - -fn ensure_column( - conn: &Connection, - table: &str, - column: &str, - definition: &str, -) -> Result<(), String> { - let mut stmt = conn - .prepare(&format!("PRAGMA table_info({table})")) - .map_err(|err| format!("Failed to inspect {table} schema: {err}"))?; - let columns = stmt - .query_map([], |row| row.get::<_, String>(1)) - .map_err(|err| format!("Failed to read {table} schema: {err}"))? - .collect::, _>>() - .map_err(|err| format!("Failed to parse {table} schema: {err}"))?; - - if !columns.iter().any(|name| name == column) { - conn.execute(&format!("ALTER TABLE {table} ADD COLUMN {definition}"), []) - .map_err(|err| format!("Failed to add {table}.{column}: {err}"))?; - } - - Ok(()) -} - -fn parse_string_list(value: &str) -> Vec { - serde_json::from_str(value).unwrap_or_default() -} diff --git a/Bigscreen/src-tauri/src/library/images.rs b/Bigscreen/src-tauri/src/library/images.rs deleted file mode 100644 index 262fb57..0000000 --- a/Bigscreen/src-tauri/src/library/images.rs +++ /dev/null @@ -1,114 +0,0 @@ -use std::fs; -use std::path::PathBuf; - -#[derive(Clone)] -pub struct ImageCache { - root: PathBuf, -} - -impl ImageCache { - pub fn new(root: PathBuf) -> Self { - Self { root } - } - - #[allow(dead_code)] - pub fn game_dir(&self, source: &str, app_id_or_slug: &str) -> Result { - let safe_slug = app_id_or_slug - .chars() - .map(|ch| if ch.is_ascii_alphanumeric() { ch } else { '-' }) - .collect::(); - let dir = self.root.join(source).join(safe_slug); - fs::create_dir_all(&dir).map_err(|err| format!("Failed to prepare image cache: {err}"))?; - Ok(dir) - } - - #[allow(dead_code)] - pub fn local_image_path( - &self, - source: &str, - app_id_or_slug: &str, - image_kind: &str, - extension: &str, - ) -> Result { - Ok(self - .game_dir(source, app_id_or_slug)? - .join(format!("{image_kind}.{extension}"))) - } - - pub fn cache_remote_image( - &self, - source: &str, - app_id_or_slug: &str, - image_kind: &str, - extension: &str, - url: &str, - ) -> Result, String> { - let target = self.local_image_path(source, app_id_or_slug, image_kind, extension)?; - if target.is_file() { - if is_valid_image_file(&target) { - return Ok(Some(target)); - } - let _ = fs::remove_file(&target); - } - - let response = match ureq::get(url) - .header("User-Agent", "NebulaOS/0.1 library-scanner") - .call() - { - Ok(response) => response, - Err(_) => return Ok(None), - }; - - if !response.status().is_success() { - return Ok(None); - } - - let mut response = response; - let bytes = response - .body_mut() - .read_to_vec() - .map_err(|err| format!("Failed to download image {url}: {err}"))?; - if !looks_like_image(&bytes) { - return Ok(None); - } - fs::write(&target, bytes) - .map_err(|err| format!("Failed to write image cache {}: {err}", target.display()))?; - - Ok(Some(target)) - } - - pub fn cache_first_remote_image( - &self, - source: &str, - app_id_or_slug: &str, - image_kind: &str, - extension: &str, - urls: &[String], - ) -> Result, String> { - for url in urls { - if let Some(path) = - self.cache_remote_image(source, app_id_or_slug, image_kind, extension, url)? - { - return Ok(Some(path)); - } - } - - Ok(None) - } -} - -fn is_valid_image_file(path: &PathBuf) -> bool { - fs::read(path) - .map(|bytes| looks_like_image(&bytes)) - .unwrap_or(false) -} - -fn looks_like_image(bytes: &[u8]) -> bool { - if bytes.len() < 1024 { - return false; - } - - bytes.starts_with(&[0xff, 0xd8, 0xff]) - || bytes.starts_with(&[0x89, b'P', b'N', b'G']) - || bytes.starts_with(b"RIFF") && bytes.get(8..12) == Some(b"WEBP") -} diff --git a/Bigscreen/src-tauri/src/library/metadata/mod.rs b/Bigscreen/src-tauri/src/library/metadata/mod.rs deleted file mode 100644 index b38629e..0000000 --- a/Bigscreen/src-tauri/src/library/metadata/mod.rs +++ /dev/null @@ -1,250 +0,0 @@ -use super::images::ImageCache; -use super::models::{ - infer_library_item_kind, GameCandidate, GameMetadata, GameSource, MetadataStatus, -}; -use serde_json::Value; - -pub mod providers { - pub mod igdb { - use crate::library::metadata::MetadataProvider; - - #[allow(dead_code)] - pub trait IgdbMetadataProvider: MetadataProvider {} - } - - pub mod rawg { - use crate::library::metadata::MetadataProvider; - - #[allow(dead_code)] - pub trait RawgMetadataProvider: MetadataProvider {} - } - - pub mod steam { - use crate::library::metadata::MetadataProvider; - - #[allow(dead_code)] - pub trait SteamMetadataProvider: MetadataProvider {} - } -} - -pub trait MetadataProvider: Send + Sync { - #[allow(dead_code)] - fn provider_id(&self) -> &'static str; - fn resolve(&self, candidate: &GameCandidate) -> Result, String>; -} - -pub struct LocalCacheMetadataProvider { - image_cache: ImageCache, -} - -impl LocalCacheMetadataProvider { - pub fn new(image_cache: ImageCache) -> Self { - Self { image_cache } - } -} - -impl MetadataProvider for LocalCacheMetadataProvider { - fn provider_id(&self) -> &'static str { - "local_cache" - } - - fn resolve(&self, candidate: &GameCandidate) -> Result, String> { - if candidate.source == GameSource::Steam { - return self.resolve_steam(candidate); - } - - Ok(None) - } -} - -impl LocalCacheMetadataProvider { - fn resolve_steam(&self, candidate: &GameCandidate) -> Result, String> { - let Some(app_id) = candidate.app_id.as_deref() else { - return Ok(None); - }; - - let store_data = fetch_steam_store_data(app_id)?; - let title = store_data - .as_ref() - .and_then(|value| value.get("name")) - .and_then(Value::as_str) - .map(ToString::to_string) - .or_else(|| Some(candidate.title.clone())); - let description = store_data - .as_ref() - .and_then(|value| value.get("short_description")) - .and_then(Value::as_str) - .map(ToString::to_string); - let genres = store_data - .as_ref() - .and_then(|value| value.get("genres")) - .and_then(Value::as_array) - .map(|items| { - items - .iter() - .filter_map(|item| item.get("description").and_then(Value::as_str)) - .map(ToString::to_string) - .collect::>() - }) - .unwrap_or_default(); - let steam_categories = store_data - .as_ref() - .and_then(|value| value.get("categories")) - .and_then(Value::as_array) - .map(|items| { - items - .iter() - .filter_map(|item| item.get("description").and_then(Value::as_str)) - .map(ToString::to_string) - .collect::>() - }) - .unwrap_or_default(); - let steam_app_type = store_data - .as_ref() - .and_then(|value| value.get("type")) - .and_then(Value::as_str) - .map(ToString::to_string); - let app_kind = infer_library_item_kind( - steam_app_type.as_deref(), - title.as_deref(), - &genres, - &steam_categories, - ); - let release_date = store_data - .as_ref() - .and_then(|value| value.get("release_date")) - .and_then(|value| value.get("date")) - .and_then(Value::as_str) - .map(ToString::to_string); - let developer = joined_string_array(store_data.as_ref(), "developers"); - let publisher = joined_string_array(store_data.as_ref(), "publishers"); - let header_image = store_data - .as_ref() - .and_then(|value| value.get("header_image")) - .and_then(Value::as_str) - .map(ToString::to_string); - - let source = candidate.source.as_str(); - let cover_image = self.image_cache.cache_first_remote_image( - source, - app_id, - "cover", - "jpg", - &[ - format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{app_id}/library_600x900.jpg"), - format!("https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/{app_id}/library_600x900.jpg"), - format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{app_id}/capsule_616x353.jpg"), - ], - )?; - let hero_urls = [ - Some(format!( - "https://cdn.cloudflare.steamstatic.com/steam/apps/{app_id}/library_hero.jpg" - )), - header_image.clone(), - Some(format!( - "https://cdn.cloudflare.steamstatic.com/steam/apps/{app_id}/header.jpg" - )), - ] - .into_iter() - .flatten() - .collect::>(); - let hero_image = self - .image_cache - .cache_first_remote_image(source, app_id, "hero", "jpg", &hero_urls)?; - let icon_image = self.image_cache.cache_first_remote_image( - source, - app_id, - "icon", - "jpg", - &[ - format!( - "https://cdn.cloudflare.steamstatic.com/steam/apps/{app_id}/capsule_184x69.jpg" - ), - format!("https://cdn.cloudflare.steamstatic.com/steam/apps/{app_id}/header.jpg"), - ], - )?; - - Ok(Some(GameMetadata { - title, - description, - app_kind, - steam_app_type, - genres, - steam_categories, - release_date, - developer, - publisher, - cover_image, - hero_image, - icon_image, - status: MetadataStatus::Matched, - })) - } -} - -fn fetch_steam_store_data(app_id: &str) -> Result, String> { - let url = format!("https://store.steampowered.com/api/appdetails?appids={app_id}"); - let response = match ureq::get(&url) - .header("User-Agent", "NebulaOS/0.1 library-scanner") - .call() - { - Ok(response) => response, - Err(_) => return Ok(None), - }; - - if !response.status().is_success() { - return Ok(None); - } - - let mut response = response; - let body = response - .body_mut() - .read_to_string() - .map_err(|err| format!("Failed to read Steam metadata response: {err}"))?; - let value: Value = serde_json::from_str(&body) - .map_err(|err| format!("Failed to parse Steam metadata: {err}"))?; - - Ok(value - .get(app_id) - .filter(|entry| { - entry - .get("success") - .and_then(Value::as_bool) - .unwrap_or(false) - }) - .and_then(|entry| entry.get("data")) - .cloned()) -} - -fn joined_string_array(value: Option<&Value>, key: &str) -> Option { - value - .and_then(|value| value.get(key)) - .and_then(Value::as_array) - .map(|items| { - items - .iter() - .filter_map(Value::as_str) - .collect::>() - .join(", ") - }) - .filter(|value| !value.is_empty()) -} - -#[allow(dead_code)] -pub struct MetadataMatchPlan { - pub exact_app_id: bool, - pub store_specific_lookup: bool, - pub fuzzy_title_matching: bool, - pub manual_user_correction: bool, -} - -impl Default for MetadataMatchPlan { - fn default() -> Self { - Self { - exact_app_id: true, - store_specific_lookup: true, - fuzzy_title_matching: true, - manual_user_correction: true, - } - } -} diff --git a/Bigscreen/src-tauri/src/library/mod.rs b/Bigscreen/src-tauri/src/library/mod.rs deleted file mode 100644 index f0d5e6e..0000000 --- a/Bigscreen/src-tauri/src/library/mod.rs +++ /dev/null @@ -1,142 +0,0 @@ -pub mod commands; -pub mod db; -pub mod images; -pub mod metadata; -pub mod models; -pub mod scanners; - -use crate::storage::AppStorage; -use db::{ensure_library_schema, list_games, upsert_candidate}; -use images::ImageCache; -use metadata::{LocalCacheMetadataProvider, MetadataProvider}; -use models::{GameCandidate, GameSource, LibraryGame, ScanProviderReport, ScanSummary}; -use scanners::{ - epic::EpicScanner, gog::GogScanner, local::LocalAppScanner, steam::SteamScanner, ScanContext, - ScannerProvider, -}; -use std::collections::HashMap; -use std::path::PathBuf; - -pub fn initialize(storage: &AppStorage) -> Result<(), String> { - let conn = storage.connect_library()?; - ensure_library_schema(&conn) -} - -pub fn list_visible_games(storage: &AppStorage) -> Result, String> { - let conn = storage.connect_library()?; - ensure_library_schema(&conn)?; - list_games(&conn, false) -} - -pub fn scan_library( - storage: AppStorage, - local_folders: Vec, -) -> Result { - let conn = storage.connect_library()?; - ensure_library_schema(&conn)?; - - let context = ScanContext { local_folders }; - - let scanners: Vec> = vec![ - Box::new(SteamScanner::new()), - Box::new(EpicScanner::new()), - Box::new(GogScanner::new()), - Box::new(LocalAppScanner::new()), - ]; - - let metadata_provider = - LocalCacheMetadataProvider::new(ImageCache::new(storage.image_cache_dir)); - let mut provider_reports = Vec::new(); - let mut all_candidates = Vec::new(); - - for scanner in scanners { - let source = scanner.source(); - match scanner.scan(&context) { - Ok(mut candidates) => { - let discovered = candidates.len(); - all_candidates.append(&mut candidates); - provider_reports.push(ScanProviderReport { - source, - discovered, - error: None, - }); - } - Err(error) => { - provider_reports.push(ScanProviderReport { - source, - discovered: 0, - error: Some(error), - }); - } - } - } - - let candidates = dedupe_candidates(all_candidates); - let mut inserted_or_updated = 0; - let mut metadata_matched = 0; - let mut unmatched = 0; - - for candidate in &candidates { - let metadata = metadata_provider.resolve(candidate)?; - if metadata.is_some() { - metadata_matched += 1; - } else { - unmatched += 1; - } - upsert_candidate(&conn, candidate, metadata.as_ref())?; - inserted_or_updated += 1; - } - - let games = list_games(&conn, false)?; - - Ok(ScanSummary { - discovered: candidates.len(), - inserted_or_updated, - metadata_matched, - unmatched, - providers: provider_reports, - games, - }) -} - -fn dedupe_candidates(candidates: Vec) -> Vec { - let mut by_identity: HashMap = HashMap::new(); - - for candidate in candidates { - let key = match (&candidate.source, &candidate.app_id) { - (source, Some(app_id)) => format!("source:{}:{app_id}", source.as_str()), - (_, None) => format!( - "path:{}", - candidate.install_path.to_string_lossy().to_lowercase() - ), - }; - - by_identity.entry(key).or_insert(candidate); - } - - let mut unique: Vec = by_identity.into_values().collect(); - unique.sort_by(|left, right| left.title.to_lowercase().cmp(&right.title.to_lowercase())); - unique -} - -#[allow(dead_code)] -pub struct LibraryExpansionHooks; - -#[allow(dead_code)] -impl LibraryExpansionHooks { - pub const ACHIEVEMENTS_DB: &'static str = "databases/achievements.db"; - pub const SAVES_DB: &'static str = "databases/saves.db"; - pub const MODS_DB: &'static str = "databases/mods.db"; - pub const ACCOUNTS_DB: &'static str = "databases/accounts.db"; -} - -#[allow(dead_code)] -pub trait AccountLinkedLibraryProvider: ScannerProvider { - fn account_provider_id(&self) -> &'static str; -} - -#[allow(dead_code)] -pub trait LaunchProvider { - fn source(&self) -> GameSource; - fn launch(&self, game: &LibraryGame) -> Result<(), String>; -} diff --git a/Bigscreen/src-tauri/src/library/models.rs b/Bigscreen/src-tauri/src/library/models.rs deleted file mode 100644 index 262f911..0000000 --- a/Bigscreen/src-tauri/src/library/models.rs +++ /dev/null @@ -1,249 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum GameSource { - Steam, - Epic, - Gog, - Local, - Unknown, -} - -impl GameSource { - pub fn as_str(&self) -> &'static str { - match self { - Self::Steam => "steam", - Self::Epic => "epic", - Self::Gog => "gog", - Self::Local => "local", - Self::Unknown => "unknown", - } - } - - pub fn from_str(value: &str) -> Self { - match value { - "steam" => Self::Steam, - "epic" => Self::Epic, - "gog" => Self::Gog, - "local" => Self::Local, - _ => Self::Unknown, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum MetadataStatus { - Pending, - Matched, - NeedsReview, - Manual, -} - -impl MetadataStatus { - pub fn as_str(&self) -> &'static str { - match self { - Self::Pending => "pending", - Self::Matched => "matched", - Self::NeedsReview => "needs_review", - Self::Manual => "manual", - } - } - - pub fn from_str(value: &str) -> Self { - match value { - "matched" => Self::Matched, - "needs_review" => Self::NeedsReview, - "manual" => Self::Manual, - _ => Self::Pending, - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum LibraryItemKind { - Game, - Tool, - Software, - Unknown, -} - -impl LibraryItemKind { - pub fn as_str(&self) -> &'static str { - match self { - Self::Game => "game", - Self::Tool => "tool", - Self::Software => "software", - Self::Unknown => "unknown", - } - } - - pub fn from_str(value: &str) -> Self { - match value { - "tool" => Self::Tool, - "software" => Self::Software, - "unknown" => Self::Unknown, - _ => Self::Game, - } - } -} - -pub fn infer_library_item_kind( - steam_app_type: Option<&str>, - title: Option<&str>, - genres: &[String], - categories: &[String], -) -> LibraryItemKind { - if steam_app_type - .map(|value| value.eq_ignore_ascii_case("software")) - .unwrap_or(false) - { - return LibraryItemKind::Software; - } - - if genres.iter().any(|genre| is_tool_label(genre)) - || categories.iter().any(|category| is_tool_label(category)) - || title.map(is_tool_title).unwrap_or(false) - { - return LibraryItemKind::Tool; - } - - if steam_app_type - .map(|value| value.eq_ignore_ascii_case("game")) - .unwrap_or(false) - { - LibraryItemKind::Game - } else { - LibraryItemKind::Unknown - } -} - -fn is_tool_label(value: &str) -> bool { - matches!( - normalize_label(value).as_str(), - "animation & modeling" - | "audio production" - | "design & illustration" - | "education" - | "game development" - | "photo editing" - | "software training" - | "utilities" - | "video production" - | "web publishing" - | "includes level editor" - ) -} - -fn is_tool_title(value: &str) -> bool { - let value = normalize_label(value); - value == "blender" - || value.contains("creation kit") - || value.contains("mod tools") - || value.contains("modding tools") - || value.contains("developer tools") - || value.contains("dedicated server") - || value.ends_with(" sdk") - || value.contains(" editor") -} - -fn normalize_label(value: &str) -> String { - value.trim().to_ascii_lowercase() -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct GameCandidate { - pub title: String, - pub source: GameSource, - pub app_id: Option, - pub install_path: PathBuf, - pub executable_path: Option, - pub launch_command: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct GameMetadata { - pub title: Option, - pub description: Option, - pub app_kind: LibraryItemKind, - pub steam_app_type: Option, - pub genres: Vec, - pub steam_categories: Vec, - pub release_date: Option, - pub developer: Option, - pub publisher: Option, - pub cover_image: Option, - pub hero_image: Option, - pub icon_image: Option, - pub status: MetadataStatus, -} - -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct LibraryGame { - pub id: i64, - pub title: String, - pub platform_source: GameSource, - pub source_store: String, - pub source_app_id: Option, - pub install_path: String, - pub executable_path: Option, - pub launch_command: Option, - pub description: Option, - pub app_kind: LibraryItemKind, - pub steam_app_type: Option, - pub genres: Vec, - pub steam_categories: Vec, - pub release_date: Option, - pub developer: Option, - pub publisher: Option, - pub cover_image: Option, - pub hero_image: Option, - pub icon_image: Option, - pub metadata_status: MetadataStatus, - pub last_scanned: i64, - pub user_hidden: bool, - pub user_favourite: bool, - pub user_title: Option, -} - -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ScanProviderReport { - pub source: GameSource, - pub discovered: usize, - pub error: Option, -} - -#[derive(Debug, Clone, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ScanSummary { - pub discovered: usize, - pub inserted_or_updated: usize, - pub metadata_matched: usize, - pub unmatched: usize, - pub providers: Vec, - pub games: Vec, -} - -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct LibraryScanRequest { - #[serde(default)] - pub local_folders: Vec, -} - -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct GameCustomizationRequest { - pub game_id: i64, - pub title: Option, - pub executable_path: Option, - pub hidden: Option, - pub favourite: Option, -} diff --git a/Bigscreen/src-tauri/src/library/scanners/epic.rs b/Bigscreen/src-tauri/src/library/scanners/epic.rs deleted file mode 100644 index 6af014a..0000000 --- a/Bigscreen/src-tauri/src/library/scanners/epic.rs +++ /dev/null @@ -1,118 +0,0 @@ -use super::ScannerProvider; -use crate::library::models::{GameCandidate, GameSource}; -use crate::library::scanners::ScanContext; -use serde_json::Value; -use std::fs; -use std::path::{Path, PathBuf}; - -pub struct EpicScanner; - -impl EpicScanner { - pub fn new() -> Self { - Self - } -} - -impl ScannerProvider for EpicScanner { - fn source(&self) -> GameSource { - GameSource::Epic - } - - fn scan(&self, _context: &ScanContext) -> Result, String> { - let mut candidates = Vec::new(); - - for manifest_dir in discover_manifest_dirs() { - let entries = match fs::read_dir(manifest_dir) { - Ok(entries) => entries, - Err(_) => continue, - }; - - for entry in entries.flatten() { - let path = entry.path(); - if path.extension().and_then(|value| value.to_str()) != Some("item") { - continue; - } - if let Some(candidate) = parse_manifest(&path)? { - candidates.push(candidate); - } - } - } - - Ok(candidates) - } -} - -fn discover_manifest_dirs() -> Vec { - let mut dirs = Vec::new(); - - #[cfg(target_os = "windows")] - if let Some(program_data) = std::env::var_os("PROGRAMDATA") { - dirs.push( - PathBuf::from(program_data) - .join("Epic") - .join("EpicGamesLauncher") - .join("Data") - .join("Manifests"), - ); - } - - #[cfg(target_os = "macos")] - if let Some(home) = super::home_dir() { - dirs.push( - home.join("Library") - .join("Application Support") - .join("Epic") - .join("EpicGamesLauncher") - .join("Data") - .join("Manifests"), - ); - } - - #[cfg(target_os = "linux")] - if let Some(home) = super::home_dir() { - dirs.push( - home.join(".config") - .join("Epic") - .join("EpicGamesLauncher") - .join("Data") - .join("Manifests"), - ); - } - - dirs.into_iter().filter(|path| path.is_dir()).collect() -} - -fn parse_manifest(path: &Path) -> Result, String> { - let contents = fs::read_to_string(path) - .map_err(|err| format!("Failed to read Epic manifest {}: {err}", path.display()))?; - let value: Value = serde_json::from_str(&contents) - .map_err(|err| format!("Failed to parse Epic manifest {}: {err}", path.display()))?; - - let Some(display_name) = value.get("DisplayName").and_then(Value::as_str) else { - return Ok(None); - }; - let Some(install_location) = value.get("InstallLocation").and_then(Value::as_str) else { - return Ok(None); - }; - - let launch_executable = value - .get("LaunchExecutable") - .and_then(Value::as_str) - .filter(|value| !value.is_empty()); - let catalog_item_id = value - .get("CatalogItemId") - .or_else(|| value.get("MainGameCatalogItemId")) - .and_then(Value::as_str) - .map(ToString::to_string); - let install_path = PathBuf::from(install_location); - let executable_path = launch_executable.map(|executable| install_path.join(executable)); - - Ok(Some(GameCandidate { - title: display_name.to_string(), - source: GameSource::Epic, - app_id: catalog_item_id, - install_path, - executable_path, - launch_command: None, - })) -} diff --git a/Bigscreen/src-tauri/src/library/scanners/gog.rs b/Bigscreen/src-tauri/src/library/scanners/gog.rs deleted file mode 100644 index e95876d..0000000 --- a/Bigscreen/src-tauri/src/library/scanners/gog.rs +++ /dev/null @@ -1,153 +0,0 @@ -use super::{is_probably_game_executable, title_from_path, ScannerProvider}; -use crate::library::models::{GameCandidate, GameSource}; -use crate::library::scanners::ScanContext; -use serde_json::Value; -use std::fs; -use std::path::{Path, PathBuf}; - -pub struct GogScanner; - -impl GogScanner { - pub fn new() -> Self { - Self - } -} - -impl ScannerProvider for GogScanner { - fn source(&self) -> GameSource { - GameSource::Gog - } - - fn scan(&self, _context: &ScanContext) -> Result, String> { - let mut candidates = Vec::new(); - - for root in discover_gog_roots() { - let entries = match fs::read_dir(root) { - Ok(entries) => entries, - Err(_) => continue, - }; - - for entry in entries.flatten() { - let install_path = entry.path(); - if !install_path.is_dir() { - continue; - } - if let Some(candidate) = inspect_install_dir(&install_path)? { - candidates.push(candidate); - } - } - } - - Ok(candidates) - } -} - -fn discover_gog_roots() -> Vec { - let mut roots = Vec::new(); - - #[cfg(target_os = "windows")] - { - if let Some(program_files_x86) = std::env::var_os("PROGRAMFILES(X86)") { - let program_files_x86 = PathBuf::from(program_files_x86); - roots.push(program_files_x86.join("GOG Galaxy").join("Games")); - roots.push(program_files_x86.join("GOG.com").join("Games")); - } - if let Some(program_files) = std::env::var_os("PROGRAMFILES") { - let program_files = PathBuf::from(program_files); - roots.push(program_files.join("GOG Galaxy").join("Games")); - roots.push(program_files.join("GOG.com").join("Games")); - } - } - - #[cfg(any(target_os = "linux", target_os = "macos"))] - if let Some(home) = super::home_dir() { - roots.push(home.join("GOG Games")); - roots.push(home.join("Games").join("GOG")); - } - - roots.into_iter().filter(|path| path.is_dir()).collect() -} - -fn inspect_install_dir(install_path: &Path) -> Result, String> { - let metadata = read_gog_metadata(install_path)?; - let title = metadata - .as_ref() - .and_then(|value| value.get("name")) - .and_then(Value::as_str) - .map(ToString::to_string) - .unwrap_or_else(|| title_from_path(install_path)); - let app_id = metadata - .as_ref() - .and_then(|value| value.get("gameId").or_else(|| value.get("game_id"))) - .and_then(|value| { - value - .as_i64() - .map(|id| id.to_string()) - .or_else(|| value.as_str().map(ToString::to_string)) - }); - let executable_path = find_executable(install_path); - - Ok(Some(GameCandidate { - title, - source: GameSource::Gog, - app_id, - install_path: install_path.to_path_buf(), - executable_path, - launch_command: None, - })) -} - -fn read_gog_metadata(install_path: &Path) -> Result, String> { - let entries = match fs::read_dir(install_path) { - Ok(entries) => entries, - Err(_) => return Ok(None), - }; - - for entry in entries.flatten() { - let path = entry.path(); - let Some(file_name) = path.file_name().and_then(|value| value.to_str()) else { - continue; - }; - if !file_name.starts_with("goggame-") || !file_name.ends_with(".info") { - continue; - } - let contents = fs::read_to_string(&path) - .map_err(|err| format!("Failed to read GOG metadata {}: {err}", path.display()))?; - let value = serde_json::from_str(&contents) - .map_err(|err| format!("Failed to parse GOG metadata {}: {err}", path.display()))?; - return Ok(Some(value)); - } - - Ok(None) -} - -fn find_executable(install_path: &Path) -> Option { - let entries = fs::read_dir(install_path).ok()?; - entries - .flatten() - .map(|entry| entry.path()) - .find(|path| path.is_file() && is_launchable(path) && is_probably_game_executable(path)) -} - -fn is_launchable(path: &Path) -> bool { - #[cfg(target_os = "windows")] - { - return path.extension().and_then(|value| value.to_str()) == Some("exe"); - } - - #[cfg(target_os = "macos")] - { - return path.extension().and_then(|value| value.to_str()) == Some("app"); - } - - #[cfg(target_os = "linux")] - { - if path.extension().and_then(|value| value.to_str()) == Some("desktop") { - return true; - } - return true; - } - - #[allow(unreachable_code)] - false -} diff --git a/Bigscreen/src-tauri/src/library/scanners/local.rs b/Bigscreen/src-tauri/src/library/scanners/local.rs deleted file mode 100644 index b7133ef..0000000 --- a/Bigscreen/src-tauri/src/library/scanners/local.rs +++ /dev/null @@ -1,146 +0,0 @@ -use super::{is_probably_game_executable, title_from_path, ScannerProvider}; -use crate::library::models::{GameCandidate, GameSource}; -use crate::library::scanners::ScanContext; -use std::fs; -use std::path::{Path, PathBuf}; - -pub struct LocalAppScanner; - -impl LocalAppScanner { - pub fn new() -> Self { - Self - } -} - -impl ScannerProvider for LocalAppScanner { - fn source(&self) -> GameSource { - GameSource::Local - } - - fn scan(&self, context: &ScanContext) -> Result, String> { - let mut candidates = Vec::new(); - - for folder in &context.local_folders { - if !folder.is_dir() { - continue; - } - scan_folder(folder, 0, &mut candidates)?; - } - - Ok(candidates) - } -} - -fn scan_folder( - folder: &Path, - depth: usize, - candidates: &mut Vec, -) -> Result<(), String> { - if depth > 4 { - return Ok(()); - } - - let entries = match fs::read_dir(folder) { - Ok(entries) => entries, - Err(_) => return Ok(()), - }; - - let mut executables = Vec::new(); - let mut child_dirs = Vec::new(); - - for entry in entries.flatten() { - let path = entry.path(); - if path.is_dir() { - if should_descend(&path) { - child_dirs.push(path); - } - continue; - } - - if is_launchable_file(&path) && is_probably_game_executable(&path) { - executables.push(path); - } - } - - if let Some(executable_path) = pick_best_executable(&executables) { - let title_path = executable_path.parent().unwrap_or(folder); - candidates.push(GameCandidate { - title: title_from_path(title_path), - source: GameSource::Local, - app_id: None, - install_path: title_path.to_path_buf(), - executable_path: Some(executable_path), - launch_command: None, - }); - return Ok(()); - } - - for child in child_dirs { - scan_folder(&child, depth + 1, candidates)?; - } - - Ok(()) -} - -fn should_descend(path: &Path) -> bool { - let name = path - .file_name() - .and_then(|value| value.to_str()) - .unwrap_or_default() - .to_lowercase(); - !matches!( - name.as_str(), - "_commonredist" | "redist" | "redistributables" | "support" | "directx" | "vc_redist" - ) -} - -fn is_launchable_file(path: &Path) -> bool { - #[cfg(target_os = "windows")] - { - return path.extension().and_then(|value| value.to_str()) == Some("exe"); - } - - #[cfg(target_os = "macos")] - { - if path.extension().and_then(|value| value.to_str()) == Some("app") { - return true; - } - } - - #[cfg(target_os = "linux")] - { - if path.extension().and_then(|value| value.to_str()) == Some("desktop") { - return true; - } - return has_executable_bit(path); - } - - #[allow(unreachable_code)] - false -} - -fn pick_best_executable(executables: &[PathBuf]) -> Option { - executables - .iter() - .min_by_key(|path| { - let file_name = path - .file_name() - .and_then(|value| value.to_str()) - .unwrap_or_default() - .to_lowercase(); - if file_name.contains("launcher") { - 1 - } else { - 0 - } - }) - .cloned() -} - -#[cfg(target_os = "linux")] -fn has_executable_bit(path: &Path) -> bool { - use std::os::unix::fs::PermissionsExt; - fs::metadata(path) - .map(|metadata| metadata.permissions().mode() & 0o111 != 0) - .unwrap_or(false) -} diff --git a/Bigscreen/src-tauri/src/library/scanners/mod.rs b/Bigscreen/src-tauri/src/library/scanners/mod.rs deleted file mode 100644 index c72fb8f..0000000 --- a/Bigscreen/src-tauri/src/library/scanners/mod.rs +++ /dev/null @@ -1,69 +0,0 @@ -use super::models::{GameCandidate, GameSource}; -use std::path::PathBuf; - -pub mod epic; -pub mod gog; -pub mod local; -pub mod steam; - -#[derive(Debug, Clone)] -pub struct ScanContext { - pub local_folders: Vec, -} - -pub trait ScannerProvider: Send + Sync { - fn source(&self) -> GameSource; - fn scan(&self, context: &ScanContext) -> Result, String>; -} - -#[cfg(any(target_os = "linux", target_os = "macos"))] -pub fn home_dir() -> Option { - std::env::var_os("HOME") - .map(PathBuf::from) - .or_else(|| std::env::var_os("USERPROFILE").map(PathBuf::from)) -} - -pub fn is_probably_game_executable(path: &std::path::Path) -> bool { - let file_name = path - .file_name() - .and_then(|name| name.to_str()) - .unwrap_or_default() - .to_lowercase(); - - if file_name.is_empty() { - return false; - } - - let ignored_fragments = [ - "unins", - "uninstall", - "setup", - "install", - "redist", - "vcredist", - "crash", - "reporter", - "benchmark", - "launcherhelper", - "unitycrashhandler", - ]; - - !ignored_fragments - .iter() - .any(|fragment| file_name.contains(fragment)) -} - -pub fn title_from_path(path: &std::path::Path) -> String { - path.file_stem() - .or_else(|| path.file_name()) - .and_then(|name| name.to_str()) - .map(|value| { - value - .replace(['_', '-', '.'], " ") - .split_whitespace() - .collect::>() - .join(" ") - }) - .filter(|value| !value.is_empty()) - .unwrap_or_else(|| "Unknown Game".to_string()) -} diff --git a/Bigscreen/src-tauri/src/library/scanners/steam.rs b/Bigscreen/src-tauri/src/library/scanners/steam.rs deleted file mode 100644 index ea69143..0000000 --- a/Bigscreen/src-tauri/src/library/scanners/steam.rs +++ /dev/null @@ -1,227 +0,0 @@ -use super::ScannerProvider; -use crate::library::models::{GameCandidate, GameSource}; -use crate::library::scanners::ScanContext; -use std::collections::HashSet; -use std::fs; -use std::path::{Path, PathBuf}; - -pub struct SteamScanner; - -impl SteamScanner { - pub fn new() -> Self { - Self - } -} - -impl ScannerProvider for SteamScanner { - fn source(&self) -> GameSource { - GameSource::Steam - } - - fn scan(&self, _context: &ScanContext) -> Result, String> { - let mut candidates = Vec::new(); - let mut library_paths = Vec::new(); - - for steam_root in discover_steam_roots() { - library_paths.extend(discover_library_paths(&steam_root)); - } - - library_paths.extend(discover_drive_steam_libraries()); - - for library_path in unique_existing_dirs(library_paths) { - candidates.extend(scan_library_path(&library_path)?); - } - - Ok(candidates) - } -} - -fn discover_steam_roots() -> Vec { - let mut roots = Vec::new(); - - #[cfg(target_os = "windows")] - { - if let Some(program_files_x86) = std::env::var_os("PROGRAMFILES(X86)") { - roots.push(PathBuf::from(program_files_x86).join("Steam")); - } - if let Some(program_files) = std::env::var_os("PROGRAMFILES") { - roots.push(PathBuf::from(program_files).join("Steam")); - } - } - - #[cfg(target_os = "linux")] - if let Some(home) = super::home_dir() { - roots.push(home.join(".steam").join("steam")); - roots.push(home.join(".local").join("share").join("Steam")); - } - - #[cfg(target_os = "macos")] - if let Some(home) = super::home_dir() { - roots.push( - home.join("Library") - .join("Application Support") - .join("Steam"), - ); - } - - unique_existing_dirs(roots) -} - -fn discover_library_paths(steam_root: &Path) -> Vec { - let mut libraries = vec![steam_root.to_path_buf()]; - let library_file = steam_root.join("steamapps").join("libraryfolders.vdf"); - - if let Ok(contents) = fs::read_to_string(library_file) { - for path in extract_vdf_values(&contents, "path") { - libraries.push(PathBuf::from(path.replace("\\\\", "\\"))); - } - - // Older Steam clients used numeric keys directly for library paths. - for (key, value) in extract_vdf_pairs(&contents) { - if key.parse::().is_ok() && value.contains(['\\', '/']) { - libraries.push(PathBuf::from(value.replace("\\\\", "\\"))); - } - } - } - - unique_existing_dirs(libraries) -} - -fn scan_library_path(library_path: &Path) -> Result, String> { - let steamapps = library_path.join("steamapps"); - let manifests = match fs::read_dir(&steamapps) { - Ok(entries) => entries, - Err(_) => return Ok(Vec::new()), - }; - - let mut candidates = Vec::new(); - for entry in manifests.flatten() { - let path = entry.path(); - let file_name = path - .file_name() - .and_then(|name| name.to_str()) - .unwrap_or_default(); - if !file_name.starts_with("appmanifest_") || !file_name.ends_with(".acf") { - continue; - } - - if let Some(candidate) = parse_app_manifest(&steamapps, &path)? { - candidates.push(candidate); - } - } - - Ok(candidates) -} - -fn discover_drive_steam_libraries() -> Vec { - #[cfg(target_os = "windows")] - { - ('A'..='Z') - .map(|letter| PathBuf::from(format!("{letter}:\\SteamLibrary"))) - .filter(|path| path.join("steamapps").is_dir()) - .collect() - } - - #[cfg(not(target_os = "windows"))] - { - Vec::new() - } -} - -fn parse_app_manifest( - steamapps: &Path, - manifest_path: &Path, -) -> Result, String> { - let contents = fs::read_to_string(manifest_path).map_err(|err| { - format!( - "Failed to read Steam manifest {}: {err}", - manifest_path.display() - ) - })?; - let app_id = extract_vdf_values(&contents, "appid").into_iter().next(); - let title = extract_vdf_values(&contents, "name").into_iter().next(); - let install_dir = extract_vdf_values(&contents, "installdir") - .into_iter() - .next(); - - let Some(app_id) = app_id else { - return Ok(None); - }; - let Some(title) = title else { - return Ok(None); - }; - let Some(install_dir) = install_dir else { - return Ok(None); - }; - - Ok(Some(GameCandidate { - title, - source: GameSource::Steam, - app_id: Some(app_id.clone()), - install_path: steamapps.join("common").join(install_dir), - executable_path: None, - launch_command: Some(format!("steam://rungameid/{app_id}")), - })) -} - -fn extract_vdf_values(contents: &str, wanted_key: &str) -> Vec { - extract_vdf_pairs(contents) - .into_iter() - .filter_map(|(key, value)| if key == wanted_key { Some(value) } else { None }) - .collect() -} - -fn extract_vdf_pairs(contents: &str) -> Vec<(String, String)> { - contents - .lines() - .filter_map(|line| { - let parts = quoted_tokens(line); - if parts.len() >= 2 { - Some((parts[0].clone(), parts[1].clone())) - } else { - None - } - }) - .collect() -} - -fn quoted_tokens(line: &str) -> Vec { - let mut tokens = Vec::new(); - let mut current = String::new(); - let mut in_quote = false; - let mut escaped = false; - - for ch in line.chars() { - if escaped { - current.push(ch); - escaped = false; - continue; - } - if ch == '\\' && in_quote { - escaped = true; - continue; - } - if ch == '"' { - if in_quote { - tokens.push(current.clone()); - current.clear(); - } - in_quote = !in_quote; - continue; - } - if in_quote { - current.push(ch); - } - } - - tokens -} - -fn unique_existing_dirs(paths: Vec) -> Vec { - let mut seen = HashSet::new(); - paths - .into_iter() - .filter(|path| path.is_dir()) - .filter(|path| seen.insert(path.to_string_lossy().to_lowercase())) - .collect() -} diff --git a/Bigscreen/src-tauri/src/main.rs b/Bigscreen/src-tauri/src/main.rs deleted file mode 100644 index 795c4ea..0000000 --- a/Bigscreen/src-tauri/src/main.rs +++ /dev/null @@ -1,9 +0,0 @@ -// Prevents additional console window on Windows in release, DO NOT REMOVE!! -#![cfg_attr( - all(target_os = "windows", not(debug_assertions)), - windows_subsystem = "windows" -)] - -fn main() { - nebula_os_lib::run() -} diff --git a/Bigscreen/src-tauri/src/storage.rs b/Bigscreen/src-tauri/src/storage.rs deleted file mode 100644 index 406b06f..0000000 --- a/Bigscreen/src-tauri/src/storage.rs +++ /dev/null @@ -1,48 +0,0 @@ -use rusqlite::Connection; -use std::fs; -use std::path::PathBuf; - -#[derive(Clone)] -pub struct AppStorage { - #[allow(dead_code)] - pub app_data_dir: PathBuf, - #[allow(dead_code)] - pub databases_dir: PathBuf, - #[allow(dead_code)] - pub cache_dir: PathBuf, - pub image_cache_dir: PathBuf, - pub users_db_path: PathBuf, - pub library_db_path: PathBuf, -} - -impl AppStorage { - pub fn new(app_data_dir: PathBuf) -> Result { - let databases_dir = app_data_dir.join("databases"); - let cache_dir = app_data_dir.join("cache"); - let image_cache_dir = cache_dir.join("images"); - - fs::create_dir_all(&databases_dir) - .map_err(|err| format!("Failed to create database directory: {err}"))?; - fs::create_dir_all(&image_cache_dir) - .map_err(|err| format!("Failed to create image cache directory: {err}"))?; - - Ok(Self { - users_db_path: databases_dir.join("users.db"), - library_db_path: databases_dir.join("library.db"), - app_data_dir, - databases_dir, - cache_dir, - image_cache_dir, - }) - } - - pub fn connect_users(&self) -> Result { - Connection::open(&self.users_db_path) - .map_err(|err| format!("Failed to open users database: {err}")) - } - - pub fn connect_library(&self) -> Result { - Connection::open(&self.library_db_path) - .map_err(|err| format!("Failed to open library database: {err}")) - } -} diff --git a/Bigscreen/src-tauri/tauri.conf.json b/Bigscreen/src-tauri/tauri.conf.json deleted file mode 100644 index f91f7f2..0000000 --- a/Bigscreen/src-tauri/tauri.conf.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.tauri.app/config/2", - "productName": "nebula-os", - "version": "0.1.0", - "identifier": "com.user.nebula-os", - "build": { - "frontendDist": "../src" - }, - "app": { - "withGlobalTauri": true, - "windows": [ - { - "title": "nebula-os", - "width": 800, - "height": 600, - "fullscreen": true, - "decorations": false - } - ], - "security": { - "csp": null, - "assetProtocol": { - "enable": true, - "scope": ["$APPDATA/**"] - } - } - }, - "bundle": { - "active": true, - "targets": "all", - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ] - } -} diff --git a/Bigscreen/src/InputRouter.cpp b/Bigscreen/src/InputRouter.cpp new file mode 100644 index 0000000..5446120 --- /dev/null +++ b/Bigscreen/src/InputRouter.cpp @@ -0,0 +1,49 @@ +#include "InputRouter.h" + +#include + +InputRouter::InputRouter(QObject *parent) + : QObject(parent) +{ +} + +bool InputRouter::handleKeyPress(QKeyEvent *event) +{ + if (!event || event->isAutoRepeat()) { + return false; + } + + const Action action = mapKey(event->key()); + if (action == Action::None) { + return false; + } + + emit actionTriggered(action); + event->accept(); + return true; +} + +InputRouter::Action InputRouter::mapKey(int key) +{ + switch (key) { + case Qt::Key_Up: + return Action::Up; + case Qt::Key_Down: + return Action::Down; + case Qt::Key_Left: + return Action::Left; + case Qt::Key_Right: + return Action::Right; + case Qt::Key_Return: + case Qt::Key_Enter: + case Qt::Key_Space: + return Action::Accept; + case Qt::Key_Escape: + case Qt::Key_Backspace: + return Action::Back; + case Qt::Key_Menu: + return Action::Menu; + default: + return Action::None; + } +} diff --git a/Bigscreen/src/InputRouter.h b/Bigscreen/src/InputRouter.h new file mode 100644 index 0000000..64e203a --- /dev/null +++ b/Bigscreen/src/InputRouter.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +class QKeyEvent; + +class InputRouter : public QObject +{ + Q_OBJECT + +public: + enum class Action { + None = 0, + Up, + Down, + Left, + Right, + Accept, + Back, + Menu, + }; + Q_ENUM(Action) + + explicit InputRouter(QObject *parent = nullptr); + + bool handleKeyPress(QKeyEvent *event); + +signals: + void actionTriggered(InputRouter::Action action); + +private: + static Action mapKey(int key); +}; diff --git a/Bigscreen/src/assets/javascript.svg b/Bigscreen/src/assets/javascript.svg deleted file mode 100644 index f9abb2b..0000000 --- a/Bigscreen/src/assets/javascript.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/Bigscreen/src/assets/tauri.svg b/Bigscreen/src/assets/tauri.svg deleted file mode 100644 index 31b62c9..0000000 --- a/Bigscreen/src/assets/tauri.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/Bigscreen/src/core/gamepadProfile.js b/Bigscreen/src/core/gamepadProfile.js deleted file mode 100644 index 839c097..0000000 --- a/Bigscreen/src/core/gamepadProfile.js +++ /dev/null @@ -1,183 +0,0 @@ -const PROFILE_GLYPH_MAP = { - xbox: { - accept: "A", - back: "B", - menu: "☰", - up: "↑", - down: "↓", - left: "←", - right: "→", - l1: "LB", - r1: "RB", - l2: "LT", - r2: "RT", - clear: "X", - y: "Y", - }, - playstation: { - accept: "✕", - back: "○", - menu: "☰", - up: "↑", - down: "↓", - left: "←", - right: "→", - l1: "L1", - r1: "R1", - l2: "L2", - r2: "R2", - clear: "□", - y: "△", - }, - switch: { - accept: "B", - back: "A", - menu: "+", - up: "↑", - down: "↓", - left: "←", - right: "→", - l1: "L", - r1: "R", - l2: "ZL", - r2: "ZR", - clear: "Y", - y: "X", - }, - generic: { - accept: "A", - back: "B", - menu: "☰", - up: "↑", - down: "↓", - left: "←", - right: "→", - l1: "L1", - r1: "R1", - l2: "L2", - r2: "R2", - clear: "X", - y: "Y", - }, -}; - -const GLYPH_ACTIONS = [ - "accept", - "back", - "menu", - "up", - "down", - "left", - "right", - "l1", - "r1", - "l2", - "r2", - "clear", - "y", -]; - -const CORE_GLYPH_IDS = { - accept: "confirm", - back: "back", - menu: "menu", - up: "dpad-up", - down: "dpad-down", - left: "dpad-left", - right: "dpad-right", - l1: "lb", - r1: "rb", - l2: "lt", - r2: "rt", - clear: "x", - y: "y", -}; - -export const CONTROLLER_PROFILES = ["xbox", "playstation", "switch", "generic"]; - -export const PROFILE_LABELS = { - xbox: "Xbox", - playstation: "PlayStation", - switch: "Nintendo Switch", - generic: "controller", -}; - -export const detectGamepadProfile = (gamepad) => { - const id = `${gamepad?.id ?? ""} ${gamepad?.mapping ?? ""}`.toLowerCase(); - - if (/xbox|x-input|microsoft|045e|8bitdo.*xbox|8bitdo.*x/i.test(id)) { - return "xbox"; - } - - if (/playstation|dualsense|dualshock|ps5|ps4|054c|sony|wireless controller/i.test(id)) { - return "playstation"; - } - - if (/nintendo|switch|pro controller|057e|joy-con|joycon/i.test(id)) { - return "switch"; - } - - return "generic"; -}; - -export const getActiveGamepad = () => { - const pads = navigator.getGamepads?.() ?? []; - return pads.find((pad) => pad?.connected) ?? null; -}; - -export const detectActiveProfile = () => { - const pad = getActiveGamepad(); - return pad ? detectGamepadProfile(pad) : "generic"; -}; - -export const getFallbackGlyphs = (profile = "generic") => ({ - ...(PROFILE_GLYPH_MAP[profile] ?? PROFILE_GLYPH_MAP.generic), -}); - -export const resolveGlyphsForProfile = (profile, glyphsModule) => { - const safeProfile = CONTROLLER_PROFILES.includes(profile) ? profile : "generic"; - const platform = safeProfile === "generic" ? "xbox" : safeProfile; - const fallback = getFallbackGlyphs(safeProfile); - - if (typeof glyphsModule?.getGlyph !== "function") { - return fallback; - } - - const resolved = {}; - GLYPH_ACTIONS.forEach((action) => { - const glyphId = CORE_GLYPH_IDS[action]; - resolved[action] = glyphsModule.getGlyph(platform, glyphId) ?? fallback[action]; - }); - return resolved; -}; - -export const watchGamepadProfile = (onChange) => { - let current = detectActiveProfile(); - - const emitIfChanged = () => { - const next = detectActiveProfile(); - if (next === current) { - return; - } - current = next; - onChange(next); - }; - - window.addEventListener("gamepadconnected", emitIfChanged); - window.addEventListener("gamepaddisconnected", emitIfChanged); - - let rafId = 0; - const poll = () => { - emitIfChanged(); - rafId = requestAnimationFrame(poll); - }; - rafId = requestAnimationFrame(poll); - - return () => { - window.removeEventListener("gamepadconnected", emitIfChanged); - window.removeEventListener("gamepaddisconnected", emitIfChanged); - if (rafId) { - cancelAnimationFrame(rafId); - } - }; -}; diff --git a/Bigscreen/src/core/guideSidebar.js b/Bigscreen/src/core/guideSidebar.js deleted file mode 100644 index d8a3768..0000000 --- a/Bigscreen/src/core/guideSidebar.js +++ /dev/null @@ -1,478 +0,0 @@ -import { - NAVIGABLE_VIEWS, - PRIMARY_NAV, - QUICK_ACTIONS, - RECENT_ITEMS, - SOURCE_LABELS, -} from "./sidebarData.js"; - -const escapeHtml = (value) => - String(value) - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll('"', """); - -const initialsFor = (name) => - name - .split(/\s+/) - .filter(Boolean) - .slice(0, 2) - .map((part) => part[0]?.toUpperCase()) - .join("") || "NU"; - -const renderRailNav = () => - PRIMARY_NAV.map( - (item, index) => ` -
  • - -
  • - `, - ).join(""); - -const renderPanelNav = () => - PRIMARY_NAV.map( - (item, index) => ` -
  • - - ${escapeHtml(item.label)} -
  • - `, - ).join(""); - -const renderQuickActions = () => - QUICK_ACTIONS.map( - (item, index) => ` - - `, - ).join(""); - -const renderRecentItems = () => { - if (!RECENT_ITEMS.length) { - return ` -
    - -

    No recent activity

    -

    Launch a game from your library to see it here.

    -
    - `; - } - - return RECENT_ITEMS.map( - (item, index) => ` - - `, - ).join(""); -}; - -const GUIDE_MARKUP = ` - -`; - -export const createGuideSidebar = ({ - state, - renderView, - openPowerMenu, - openGuidePanel, - onGuideChange, -}) => { - let shellEl = null; - let backdropEl = null; - let panelEl = null; - let expanded = false; - let lastFocusedKey = null; - - const syncProfile = () => { - if (!shellEl) return; - const name = state.profileName || "Nebula User"; - const initials = initialsFor(name); - shellEl.querySelectorAll("[data-profile-name]").forEach((el) => { - el.textContent = name; - }); - shellEl.querySelectorAll("[data-profile-avatar], .guide-rail-avatar, .guide-footer-avatar").forEach((el) => { - el.textContent = initials; - el.setAttribute("aria-label", name); - }); - }; - - const setExpanded = (next) => { - expanded = next; - document.body.classList.toggle("guide-expanded", expanded); - - if (panelEl) { - panelEl.hidden = !expanded; - panelEl.setAttribute("aria-hidden", expanded ? "false" : "true"); - } - - if (backdropEl) { - backdropEl.hidden = !expanded; - } - - shellEl?.querySelectorAll("[data-guide-action='toggle'], .guide-rail-brand").forEach((btn) => { - btn.setAttribute("aria-expanded", expanded ? "true" : "false"); - }); - - onGuideChange?.({ expanded }); - }; - - const open = () => { - if (expanded) return; - const focused = document.querySelector(".is-focused"); - lastFocusedKey = focused?.dataset?.focusKey ?? null; - setExpanded(true); - window.dispatchEvent(new CustomEvent("nebula-guide-open")); - }; - - const close = () => { - if (!expanded) return; - setExpanded(false); - window.dispatchEvent( - new CustomEvent("nebula-guide-close", { - detail: { focusKey: lastFocusedKey }, - }), - ); - }; - - const toggle = () => { - if (expanded) { - close(); - } else { - open(); - } - }; - - const navigateTo = (target) => { - if (!target || !NAVIGABLE_VIEWS.has(target)) { - return false; - } - close(); - renderView(target); - return true; - }; - - const handleGuideAction = (element) => { - const action = element?.dataset?.guideAction; - if (action === "toggle") { - toggle(); - return true; - } - if (action === "power") { - close(); - openPowerMenu?.(); - return true; - } - if (action === "profile") { - console.log("[Guide] Profile panel (placeholder)"); - return true; - } - - const panel = element?.dataset?.panel; - if (panel) { - close(); - openGuidePanel?.(panel); - return true; - } - - return false; - }; - - const handleAccept = (focused) => { - if (!focused) return false; - - const target = focused.dataset.target; - if (target && focused.dataset.navRegion) { - return navigateTo(target); - } - - if (focused.dataset.recentId) { - console.log(`[Guide] Recent item: ${focused.dataset.recentId}`); - return true; - } - - return handleGuideAction(focused); - }; - - const updateActiveView = (viewId) => { - if (!shellEl) return; - shellEl.querySelectorAll("[data-sidebar-nav]").forEach((item) => { - const matches = item.dataset.sidebarNav === viewId; - item.classList.toggle("is-active", matches); - if (matches) { - item.setAttribute("aria-current", "page"); - } else { - item.removeAttribute("aria-current"); - } - }); - }; - - const bindPointer = () => { - shellEl?.addEventListener("click", (event) => { - const item = event.target.closest("[data-focusable='true']"); - if (!item || item.dataset.disabled === "true") return; - - if (item.dataset.guideAction === "toggle" && !expanded) { - open(); - return; - } - - if (item.dataset.target || item.dataset.guideAction || item.dataset.recentId || item.dataset.panel) { - handleAccept(item); - window.dispatchEvent( - new CustomEvent("nebula-navigation-refresh", { - detail: { focusKey: item.dataset.focusKey }, - }), - ); - } - }); - - backdropEl?.addEventListener("click", () => close()); - }; - - const mount = ({ shellRoot, backdropRoot }) => { - shellRoot.innerHTML = GUIDE_MARKUP; - shellEl = shellRoot.querySelector("#guide-sidebar") ?? shellRoot; - panelEl = shellRoot.querySelector("#guide-panel"); - backdropEl = backdropRoot; - syncProfile(); - bindPointer(); - setExpanded(false); - }; - - const getFocusRoots = () => { - if (!shellEl || document.body.classList.contains("body-no-sidebar")) { - return []; - } - if (expanded) { - const root = shellEl.querySelector("[data-guide-focus-root]"); - return root ? [root] : []; - } - const rail = shellEl.querySelector("[data-guide-rail]"); - return rail ? [rail] : []; - }; - - return { - mount, - open, - close, - toggle, - isExpanded: () => expanded, - updateActiveView, - getFocusRoots, - handleAccept, - syncProfile, - }; -}; diff --git a/Bigscreen/src/core/input.js b/Bigscreen/src/core/input.js deleted file mode 100644 index 45c7e70..0000000 --- a/Bigscreen/src/core/input.js +++ /dev/null @@ -1,324 +0,0 @@ -const KEYBOARD_MAP = { - ArrowUp: "up", - ArrowDown: "down", - ArrowLeft: "left", - ArrowRight: "right", - KeyY: "y", - KeyX: "clear", - KeyQ: "l1", - KeyE: "r1", - KeyZ: "l2", - KeyC: "r2", - KeyG: "guide", - Enter: "accept", - Escape: "back", - Backspace: "back", -}; - -const BUTTON_MAP = { - 0: "accept", - 1: "back", - 2: "clear", - 3: "y", - 8: "menu", - 9: "menu", - 4: "l1", - 5: "r1", - 6: "l2", - 7: "r2", - 12: "up", - 13: "down", - 14: "left", - 15: "right", -}; - -const createFallbackEmitter = () => { - const listeners = new Set(); - return { - emit: (payload) => listeners.forEach((listener) => listener(payload)), - on: (listener) => { - listeners.add(listener); - return () => listeners.delete(listener); - }, - }; -}; - -export const createInputManager = ({ onAction, actions }) => { - const heldButtons = new Set(); - const heldVirtual = new Set(); - let rafId = 0; - let mapper = null; - let unsubscribeMapper = null; - let axisLatched = false; - let lastAxisEmitAt = 0; - let active = false; - - const emitter = createFallbackEmitter(); - - const emitAction = (action) => { - if (!actions.includes(action)) { - return; - } - onAction(action); - emitter.emit(action); - }; - - const mapKeyboard = async () => { - try { - const coreInput = await import("@nebulaproject/core/input"); - if (typeof coreInput.createActionMapper !== "function") { - return; - } - - mapper = coreInput.createActionMapper({ - bindings: { - up: [ - { source: "keyboard", control: "ArrowUp" }, - { source: "gamepad", control: "dpad-up" }, - { source: "gamepad", control: "axis-up" }, - ], - down: [ - { source: "keyboard", control: "ArrowDown" }, - { source: "gamepad", control: "dpad-down" }, - { source: "gamepad", control: "axis-down" }, - ], - left: [ - { source: "keyboard", control: "ArrowLeft" }, - { source: "gamepad", control: "dpad-left" }, - { source: "gamepad", control: "axis-left" }, - ], - right: [ - { source: "keyboard", control: "ArrowRight" }, - { source: "gamepad", control: "dpad-right" }, - { source: "gamepad", control: "axis-right" }, - ], - accept: [ - { source: "keyboard", control: "Enter" }, - { source: "gamepad", control: "a" }, - ], - back: [ - { source: "keyboard", control: "Escape" }, - { source: "keyboard", control: "Backspace" }, - { source: "gamepad", control: "b" }, - ], - menu: [ - { source: "keyboard", control: "KeyM" }, - { source: "gamepad", control: "start" }, - ], - guide: [ - { source: "keyboard", control: "KeyG" }, - ], - clear: [ - { source: "keyboard", control: "KeyX" }, - { source: "gamepad", control: "x" }, - ], - y: [ - { source: "keyboard", control: "KeyY" }, - { source: "gamepad", control: "y" }, - ], - l1: [{ source: "gamepad", control: "lb" }], - r1: [{ source: "gamepad", control: "rb" }], - l2: [ - { source: "keyboard", control: "KeyZ" }, - { source: "gamepad", control: "lt" }, - ], - r2: [ - { source: "keyboard", control: "KeyC" }, - { source: "gamepad", control: "rt" }, - ], - }, - }); - - unsubscribeMapper = mapper.onAction((update) => { - if (update?.active && update.action) { - emitAction(update.action); - } - }); - } catch (_error) { - mapper = null; - } - }; - - const mapEventToMapper = (event) => { - mapper?.mapEvent?.(event); - }; - - const onKeyDown = (event) => { - const action = - KEYBOARD_MAP[event.code] ?? - (event.code === "KeyM" ? "menu" : event.code === "KeyG" ? "guide" : null); - if (!action) { - return; - } - event.preventDefault(); - - if (mapper) { - mapEventToMapper({ - source: "keyboard", - control: event.code, - type: "pressed", - value: 1, - }); - } else if (!heldVirtual.has(event.code)) { - heldVirtual.add(event.code); - emitAction(action); - } - }; - - const onKeyUp = (event) => { - if (mapper) { - mapEventToMapper({ - source: "keyboard", - control: event.code, - type: "released", - value: 0, - }); - } - heldVirtual.delete(event.code); - }; - - const processAxis = (axisX, axisY) => { - const threshold = 0.6; - const now = performance.now(); - let axisAction = null; - - if (Math.abs(axisX) > Math.abs(axisY)) { - if (axisX <= -threshold) axisAction = "left"; - if (axisX >= threshold) axisAction = "right"; - } else { - if (axisY <= -threshold) axisAction = "up"; - if (axisY >= threshold) axisAction = "down"; - } - - if (!axisAction) { - axisLatched = false; - return; - } - - if (axisLatched || now - lastAxisEmitAt < 120) { - return; - } - - if (mapper) { - mapEventToMapper({ - source: "gamepad", - control: `axis-${axisAction}`, - type: "axis", - value: 1, - }); - } else { - emitAction(axisAction); - } - - axisLatched = true; - lastAxisEmitAt = now; - }; - - const pollGamepad = () => { - if (!active) { - return; - } - - const [pad] = navigator.getGamepads?.() ?? []; - if (pad) { - pad.buttons.forEach((button, index) => { - const action = BUTTON_MAP[index]; - if (!action) { - return; - } - - if (button.pressed && !heldButtons.has(index)) { - heldButtons.add(index); - - if (mapper) { - const controlMap = { - accept: "a", - back: "b", - clear: "x", - y: "y", - menu: "start", - l1: "lb", - r1: "rb", - l2: "lt", - r2: "rt", - up: "dpad-up", - down: "dpad-down", - left: "dpad-left", - right: "dpad-right", - }; - - mapEventToMapper({ - source: "gamepad", - control: controlMap[action] ?? action, - type: "pressed", - value: 1, - }); - } else { - emitAction(action); - } - } - - if (!button.pressed && heldButtons.has(index)) { - heldButtons.delete(index); - if (mapper) { - const releaseControlMap = { - accept: "a", - back: "b", - clear: "x", - y: "y", - menu: "start", - l1: "lb", - r1: "rb", - l2: "lt", - r2: "rt", - up: "dpad-up", - down: "dpad-down", - left: "dpad-left", - right: "dpad-right", - }; - - mapEventToMapper({ - source: "gamepad", - control: releaseControlMap[action] ?? action, - type: "released", - value: 0, - }); - } - } - }); - - processAxis(pad.axes?.[0] ?? 0, pad.axes?.[1] ?? 0); - } - - rafId = requestAnimationFrame(pollGamepad); - }; - - const start = async () => { - if (active) { - return; - } - active = true; - await mapKeyboard(); - window.addEventListener("keydown", onKeyDown); - window.addEventListener("keyup", onKeyUp); - rafId = requestAnimationFrame(pollGamepad); - }; - - const stop = () => { - active = false; - window.removeEventListener("keydown", onKeyDown); - window.removeEventListener("keyup", onKeyUp); - if (rafId) { - cancelAnimationFrame(rafId); - rafId = 0; - } - unsubscribeMapper?.(); - unsubscribeMapper = null; - }; - - return { - start, - stop, - onAction: emitter.on, - }; -}; diff --git a/Bigscreen/src/core/nav.js b/Bigscreen/src/core/nav.js deleted file mode 100644 index e827000..0000000 --- a/Bigscreen/src/core/nav.js +++ /dev/null @@ -1,451 +0,0 @@ -// Spatial controller navigation. -// -// Given a focused element ("source") and a direction (up/down/left/right) we pick -// the next focusable by physical screen position rather than by an authoring-time -// grid (row/col data attributes). The screen geometry is the source of truth that -// the player sees, so it is also what the controller should follow. -// -// The algorithm: -// 1. Filter candidates to those whose *center* is strictly past the source's -// edge in the requested direction. Center-vs-edge avoids picking elements -// that merely brush the source from the side. -// 2. Prefer candidates that overlap the source on the perpendicular axis. A -// candidate sharing your row when you press right (or your column when you -// press up/down) is almost always the right answer. -// 3. If no overlapping candidate exists, fall back to candidates inside a 60° -// cone from the source center pointing in the requested direction. Bias -// heavily against off-axis drift. -// 4. Remember the "preferred" perpendicular center while the player presses the -// same axis repeatedly so vertical traversal doesn't slide sideways across -// rows of unequal width. -// 5. Sidebar is treated as a separate region. Left-from-content escapes to the -// sidebar only when there is no content target to the left; right-from- -// sidebar always re-enters content; up/down inside the sidebar walks the -// sidebar items. - -const EPSILON = 1; -const MAX_FALLBACK_CONE_RADIANS = Math.PI / 3; // 60° - -const getRect = (element) => { - const rect = element.getBoundingClientRect(); - return { - left: rect.left, - top: rect.top, - right: rect.right, - bottom: rect.bottom, - width: rect.width, - height: rect.height, - centerX: rect.left + rect.width / 2, - centerY: rect.top + rect.height / 2, - }; -}; - -const isInDirection = (sourceRect, targetRect, direction) => { - if (direction === "right") return targetRect.centerX > sourceRect.right - EPSILON; - if (direction === "left") return targetRect.centerX < sourceRect.left + EPSILON; - if (direction === "down") return targetRect.centerY > sourceRect.bottom - EPSILON; - if (direction === "up") return targetRect.centerY < sourceRect.top + EPSILON; - return false; -}; - -const perpendicularOverlap = (sourceRect, targetRect, direction) => { - if (direction === "left" || direction === "right") { - const start = Math.max(sourceRect.top, targetRect.top); - const end = Math.min(sourceRect.bottom, targetRect.bottom); - return Math.max(0, end - start); - } - const start = Math.max(sourceRect.left, targetRect.left); - const end = Math.min(sourceRect.right, targetRect.right); - return Math.max(0, end - start); -}; - -const primaryDistance = (sourceRect, targetRect, direction) => { - if (direction === "right") return Math.max(0, targetRect.left - sourceRect.right); - if (direction === "left") return Math.max(0, sourceRect.left - targetRect.right); - if (direction === "down") return Math.max(0, targetRect.top - sourceRect.bottom); - if (direction === "up") return Math.max(0, sourceRect.top - targetRect.bottom); - return Number.POSITIVE_INFINITY; -}; - -const perpendicularDisplacement = (sourceRect, targetRect, direction) => { - if (direction === "left" || direction === "right") { - return Math.abs(targetRect.centerY - sourceRect.centerY); - } - return Math.abs(targetRect.centerX - sourceRect.centerX); -}; - -const offAxisAngle = (sourceRect, targetRect, direction) => { - const dx = targetRect.centerX - sourceRect.centerX; - const dy = targetRect.centerY - sourceRect.centerY; - if (direction === "right") return Math.atan2(Math.abs(dy), Math.max(1, dx)); - if (direction === "left") return Math.atan2(Math.abs(dy), Math.max(1, -dx)); - if (direction === "down") return Math.atan2(Math.abs(dx), Math.max(1, dy)); - if (direction === "up") return Math.atan2(Math.abs(dx), Math.max(1, -dy)); - return Math.PI; -}; - -const scoreCandidate = (sourceRect, targetRect, direction, anchorPerpCenter) => { - if (!isInDirection(sourceRect, targetRect, direction)) { - return null; - } - - const primary = primaryDistance(sourceRect, targetRect, direction); - const overlap = perpendicularOverlap(sourceRect, targetRect, direction); - const perpDisp = perpendicularDisplacement(sourceRect, targetRect, direction); - - let score; - if (overlap > 0) { - // Strong winner: candidate shares the perpendicular line with the source. - // Primary distance dominates; tiny perpendicular drift breaks ties so the - // most centered candidate wins. - score = primary + perpDisp * 0.15; - } else { - // No overlap — only allow within a 60° cone in the requested direction. - if (offAxisAngle(sourceRect, targetRect, direction) > MAX_FALLBACK_CONE_RADIANS) { - return null; - } - // Heavy off-axis penalty plus a flat bias so an overlapping candidate that - // is further away in the primary axis still beats this one. - score = primary + perpDisp * 2.5 + 400; - } - - if (anchorPerpCenter !== null && Number.isFinite(anchorPerpCenter)) { - const anchorDrift = direction === "left" || direction === "right" - ? Math.abs(targetRect.centerY - anchorPerpCenter) - : Math.abs(targetRect.centerX - anchorPerpCenter); - score += anchorDrift * 0.6; - } - - return score; -}; - -const isElementInteractable = (element) => { - if (element.dataset.disabled === "true") return false; - if (element.getAttribute("aria-disabled") === "true") return false; - if (element.hasAttribute("hidden")) return false; - const rect = element.getBoundingClientRect(); - if (rect.width <= 0 || rect.height <= 0) return false; - return true; -}; - -export const createNavigationManager = () => { - let contract = null; - let focusables = []; - let focusedIndex = -1; - - // Tracks the perpendicular center the player is "anchored" to while they - // repeat moves on the same axis. axis="x" while pressing up/down, axis="y" - // while pressing left/right. Reset whenever the axis flips or focus crosses - // regions (sidebar <-> content). - let anchor = { axis: null, value: null }; - - const decorateFocusable = (element) => { - element.classList.remove("is-focused"); - element.tabIndex = -1; - }; - - const dispatchFocusChange = (element) => { - const col = Number(element.dataset.col ?? 0); - const row = Number(element.dataset.row ?? 0); - document.documentElement.style.setProperty("--nebula-accent-line-x", `${20 + col * 110}px`); - document.documentElement.style.setProperty("--nebula-focus-strength", "1"); - - window.dispatchEvent( - new CustomEvent("nebula-focus-change", { - detail: { - key: element.dataset.focusKey ?? null, - row, - col, - }, - }), - ); - }; - - const applyFocus = (index) => { - if (!focusables.length) { - focusedIndex = -1; - return; - } - - const clamped = Math.max(0, Math.min(index, focusables.length - 1)); - - focusables.forEach((focusable) => { - focusable.element.classList.remove("is-focused"); - focusable.element.setAttribute("aria-selected", "false"); - }); - - focusedIndex = clamped; - const focused = focusables[focusedIndex]?.element; - if (focused) { - focused.classList.add("is-focused"); - focused.setAttribute("aria-selected", "true"); - focused.focus({ preventScroll: true }); - dispatchFocusChange(focused); - } - }; - - const buildFocusables = () => { - if (!contract?.focusRoot) { - focusables = []; - return; - } - - const roots = [contract.focusRoot, ...(contract.extraFocusRoots ?? [])].filter(Boolean); - const nodes = Array.from( - new Set(roots.flatMap((root) => Array.from(root.querySelectorAll("[data-focusable='true']")))), - ); - - focusables = nodes - .filter(isElementInteractable) - .map((element) => { - decorateFocusable(element); - return { - element, - row: Number(element.dataset.row ?? 0), - col: Number(element.dataset.col ?? 0), - key: element.dataset.focusKey ?? "", - region: element.dataset.navRegion ?? "content", - }; - }); - - focusables.forEach((focusable, index) => { - focusable.index = index; - }); - }; - - const resolveDefaultFocus = () => { - if (!focusables.length) return -1; - - if (contract?.defaultFocus) { - const idx = focusables.findIndex((f) => f.element === contract.defaultFocus); - if (idx >= 0) return idx; - } - - const contentIdx = focusables.findIndex((f) => f.region !== "sidebar" && f.region !== "guide"); - return contentIdx >= 0 ? contentIdx : 0; - }; - - const findContentDefaultIndex = () => { - if (contract?.defaultFocus) { - const idx = focusables.findIndex((f) => f.element === contract.defaultFocus); - if (idx >= 0 && focusables[idx].region !== "sidebar" && focusables[idx].region !== "guide") { - return idx; - } - } - return focusables.findIndex((f) => f.region !== "sidebar" && f.region !== "guide"); - }; - - const findSidebarTargetIndex = () => { - const activeIdx = focusables.findIndex( - (f) => f.region === "sidebar" && f.element.classList.contains("is-active"), - ); - if (activeIdx >= 0) return activeIdx; - return focusables.findIndex((f) => f.region === "sidebar"); - }; - - const findNextRegionIndex = (direction, region) => { - const regionItems = focusables - .map((focusable, index) => ({ focusable, index })) - .filter(({ focusable }) => focusable.region === region); - - if (!regionItems.length) return null; - - regionItems.sort((a, b) => { - const ra = a.focusable.element.getBoundingClientRect(); - const rb = b.focusable.element.getBoundingClientRect(); - return ra.top - rb.top; - }); - - const currentSlot = regionItems.findIndex(({ index }) => index === focusedIndex); - if (currentSlot < 0) return null; - - const nextSlot = direction === "up" ? currentSlot - 1 : currentSlot + 1; - return regionItems[nextSlot]?.index ?? null; - }; - - const findNextSidebarIndex = (direction) => findNextRegionIndex(direction, "sidebar"); - - const findNextGuideIndex = (direction) => findNextRegionIndex(direction, "guide"); - - const findBestContentSpatialIndex = (direction) => { - const source = focusables[focusedIndex]; - if (!source) return -1; - - const sourceRect = getRect(source.element); - - const axisOfMove = direction === "up" || direction === "down" ? "x" : "y"; - const anchorPerpCenter = anchor.axis === axisOfMove ? anchor.value : null; - - let bestIndex = -1; - let bestScore = Number.POSITIVE_INFINITY; - - focusables.forEach((candidate, idx) => { - if (idx === focusedIndex) return; - if (candidate.region === "sidebar" || candidate.region === "guide") return; - - const targetRect = getRect(candidate.element); - const score = scoreCandidate(sourceRect, targetRect, direction, anchorPerpCenter); - if (score === null) return; - if (score < bestScore) { - bestScore = score; - bestIndex = idx; - } - }); - - return bestIndex; - }; - - const updateAnchorForMove = (direction, sourceRect) => { - const axisOfMove = direction === "up" || direction === "down" ? "x" : "y"; - if (anchor.axis !== axisOfMove) { - anchor = { - axis: axisOfMove, - value: axisOfMove === "x" ? sourceRect.centerX : sourceRect.centerY, - }; - } - }; - - const moveWithNebula = (direction) => { - // The bundled @nebulaproject/core picker is opt-in. Our local spatial - // navigation is the default everywhere so every view gets the same - // predictable controller behavior unless a view explicitly asks for the - // remote algorithm. - if (contract?.useNebulaNavigation !== true) return null; - - const picker = contract?.nebulaNavigation?.pickBestCandidate; - if (typeof picker !== "function") return null; - - const source = focusables[focusedIndex]; - if (!source) return null; - - const sourceRect = getRect(source.element); - const candidates = focusables - .filter((item) => item.index !== source.index) - .map((item) => { - const rect = getRect(item.element); - return { - id: item.key, - x: rect.left, - y: rect.top, - width: rect.width, - height: rect.height, - }; - }); - - const picked = picker( - { - id: source.key, - x: sourceRect.left, - y: sourceRect.top, - width: sourceRect.width, - height: sourceRect.height, - }, - candidates, - direction, - ); - - if (!picked?.id) return null; - - const nextIndex = focusables.findIndex((item) => item.key === picked.id); - return nextIndex >= 0 ? nextIndex : null; - }; - - const move = (direction) => { - if (!focusables.length || focusedIndex < 0) return; - - const source = focusables[focusedIndex]; - const sourceRect = getRect(source.element); - - // -- Guide panel (expanded shell navigation) --------------------------- - if (source.region === "guide") { - if (direction === "up" || direction === "down") { - const next = findNextGuideIndex(direction); - if (next !== null) applyFocus(next); - return; - } - if (direction === "left" || direction === "right") { - const lateral = focusables - .map((focusable, index) => ({ focusable, index })) - .filter(({ focusable }) => focusable.region === "guide") - .filter(({ focusable }) => { - const row = Number(focusable.element.dataset.row ?? 0); - return row === Number(source.element.dataset.row ?? 0); - }) - .sort((a, b) => { - const ra = a.focusable.element.getBoundingClientRect(); - const rb = b.focusable.element.getBoundingClientRect(); - return ra.left - rb.left; - }); - - const slot = lateral.findIndex(({ index }) => index === focusedIndex); - if (slot < 0) return; - const nextSlot = direction === "left" ? slot - 1 : slot + 1; - const next = lateral[nextSlot]?.index; - if (next !== undefined) applyFocus(next); - } - return; - } - - // -- Collapsed sidebar rail -------------------------------------------- - if (source.region === "sidebar") { - if (direction === "up" || direction === "down") { - const next = findNextSidebarIndex(direction); - if (next !== null) applyFocus(next); - return; - } - if (direction === "right") { - const contentIdx = findContentDefaultIndex(); - if (contentIdx >= 0) { - anchor = { axis: null, value: null }; - applyFocus(contentIdx); - } - return; - } - return; - } - - // -- Optional external navigation hook (Nebula core) ------------------- - const nebulaIndex = moveWithNebula(direction); - if (nebulaIndex !== null) { - updateAnchorForMove(direction, sourceRect); - applyFocus(nebulaIndex); - return; - } - - // -- Spatial search within content ------------------------------------- - const bestContentIdx = findBestContentSpatialIndex(direction); - if (bestContentIdx >= 0) { - updateAnchorForMove(direction, sourceRect); - applyFocus(bestContentIdx); - return; - } - - // -- Edge of content: escape to sidebar when going left ---------------- - if (direction === "left") { - const sidebarIdx = findSidebarTargetIndex(); - if (sidebarIdx >= 0) { - anchor = { axis: null, value: null }; - applyFocus(sidebarIdx); - } - return; - } - - // No candidate in this direction. Stay put — pressing again won't bounce - // the player around to unrelated regions. - }; - - const mount = (nextContract) => { - contract = nextContract; - anchor = { axis: null, value: null }; - buildFocusables(); - applyFocus(resolveDefaultFocus()); - }; - - const getFocusedElement = () => focusables[focusedIndex]?.element ?? null; - - return { - mount, - move, - getFocusedElement, - }; -}; diff --git a/Bigscreen/src/core/passkey.js b/Bigscreen/src/core/passkey.js deleted file mode 100644 index 5ea3c34..0000000 --- a/Bigscreen/src/core/passkey.js +++ /dev/null @@ -1,184 +0,0 @@ -const PASSKEY_STORAGE_KEY = "nebula.passkey.v1"; -const PASSKEY_VERSION = 1; -const PASSKEY_LENGTH = 6; - -const clamp = (value, min, max) => Math.max(min, Math.min(max, value)); - -const FALLBACK_CONFIG = { - enabled: true, - length: PASSKEY_LENGTH, - requireConfirm: false, - keyboardSupport: true, - maxAttempts: 5, - cooldownSeconds: 30, - animationSpeed: "normal", - highContrast: false, - hash: "", - salt: "", -}; - -const arrayToHex = (bytes) => - Array.from(bytes) - .map((value) => value.toString(16).padStart(2, "0")) - .join(""); - -const createSalt = () => { - const bytes = new Uint8Array(16); - crypto.getRandomValues(bytes); - return arrayToHex(bytes); -}; - -const normalizeSequence = (sequence) => sequence.join("|"); - -export const hashPasskeySequence = async (sequence, salt) => { - const source = `${salt}::${normalizeSequence(sequence)}`; - const encoded = new TextEncoder().encode(source); - const digest = await crypto.subtle.digest("SHA-256", encoded); - return arrayToHex(new Uint8Array(digest)); -}; - -export const loadPasskeyConfig = () => { - try { - const raw = window.localStorage.getItem(PASSKEY_STORAGE_KEY); - if (!raw) { - return { - ...FALLBACK_CONFIG, - }; - } - - const parsed = JSON.parse(raw); - return { - ...FALLBACK_CONFIG, - ...parsed, - length: PASSKEY_LENGTH, - maxAttempts: clamp(Number(parsed.maxAttempts ?? FALLBACK_CONFIG.maxAttempts), 1, 10), - cooldownSeconds: clamp(Number(parsed.cooldownSeconds ?? FALLBACK_CONFIG.cooldownSeconds), 5, 120), - requireConfirm: Boolean(parsed.requireConfirm ?? FALLBACK_CONFIG.requireConfirm), - keyboardSupport: Boolean(parsed.keyboardSupport ?? FALLBACK_CONFIG.keyboardSupport), - enabled: Boolean(parsed.enabled ?? FALLBACK_CONFIG.enabled), - highContrast: Boolean(parsed.highContrast ?? FALLBACK_CONFIG.highContrast), - animationSpeed: ["slow", "normal", "fast"].includes(parsed.animationSpeed) - ? parsed.animationSpeed - : FALLBACK_CONFIG.animationSpeed, - hash: typeof parsed.hash === "string" ? parsed.hash : "", - salt: typeof parsed.salt === "string" ? parsed.salt : "", - version: PASSKEY_VERSION, - }; - } catch (_error) { - return { - ...FALLBACK_CONFIG, - version: PASSKEY_VERSION, - }; - } -}; - -export const savePasskeyConfig = (config) => { - const safeConfig = { - ...FALLBACK_CONFIG, - ...config, - version: PASSKEY_VERSION, - length: PASSKEY_LENGTH, - maxAttempts: clamp(Number(config.maxAttempts ?? FALLBACK_CONFIG.maxAttempts), 1, 10), - cooldownSeconds: clamp(Number(config.cooldownSeconds ?? FALLBACK_CONFIG.cooldownSeconds), 5, 120), - }; - - window.localStorage.setItem(PASSKEY_STORAGE_KEY, JSON.stringify(safeConfig)); - return safeConfig; -}; - -export const createPasskeyController = () => { - const config = loadPasskeyConfig(); - let failedAttempts = 0; - let lockoutUntil = 0; - - const persist = () => savePasskeyConfig(config); - - const inLockout = () => Date.now() < lockoutUntil; - - const getLockoutRemainingMs = () => Math.max(0, lockoutUntil - Date.now()); - - const verifySequence = async (sequence) => { - if (!config.enabled) { - return { ok: true, reason: "disabled" }; - } - - if (inLockout()) { - return { - ok: false, - reason: "lockout", - lockoutRemainingMs: getLockoutRemainingMs(), - }; - } - - if (!config.hash || !config.salt) { - return { ok: false, reason: "setup-required" }; - } - - const hash = await hashPasskeySequence(sequence, config.salt); - if (hash === config.hash) { - failedAttempts = 0; - return { ok: true, reason: "match" }; - } - - failedAttempts += 1; - const attemptsLeft = Math.max(0, config.maxAttempts - failedAttempts); - if (attemptsLeft <= 0) { - lockoutUntil = Date.now() + config.cooldownSeconds * 1000; - failedAttempts = 0; - return { - ok: false, - reason: "lockout", - lockoutRemainingMs: getLockoutRemainingMs(), - }; - } - - return { - ok: false, - reason: "mismatch", - attemptsLeft, - }; - }; - - const setSequence = async (sequence) => { - const nextSalt = createSalt(); - const nextHash = await hashPasskeySequence(sequence, nextSalt); - config.salt = nextSalt; - config.hash = nextHash; - failedAttempts = 0; - lockoutUntil = 0; - persist(); - }; - - const updateConfig = (partial) => { - Object.assign(config, partial); - config.length = PASSKEY_LENGTH; - config.maxAttempts = clamp(Number(config.maxAttempts), 1, 10); - config.cooldownSeconds = clamp(Number(config.cooldownSeconds), 5, 120); - persist(); - return { ...config }; - }; - - const resetSequence = () => { - config.hash = ""; - config.salt = ""; - config.length = PASSKEY_LENGTH; - failedAttempts = 0; - lockoutUntil = 0; - persist(); - }; - - const getConfig = () => ({ ...config }); - - return { - getConfig, - updateConfig, - verifySequence, - setSequence, - resetSequence, - inLockout, - getLockoutRemainingMs, - hasPasskey: () => Boolean(config.hash && config.salt), - }; -}; - -export const PASSKEY_ACTIONS = ["up", "down", "left", "right", "l1", "r1", "l2", "r2"]; diff --git a/Bigscreen/src/core/router.js b/Bigscreen/src/core/router.js deleted file mode 100644 index e0cc765..0000000 --- a/Bigscreen/src/core/router.js +++ /dev/null @@ -1,34 +0,0 @@ -export const createRouter = (outlet) => { - const views = new Map(); - let current = null; - - const register = (view) => { - views.set(view.id, view); - }; - - const navigate = (id) => { - const view = views.get(id); - if (!view) { - throw new Error(`Unknown view: ${id}`); - } - - current = id; - outlet.innerHTML = view.render(); - const nextView = outlet.querySelector(".view"); - if (nextView) { - requestAnimationFrame(() => { - nextView.classList.add("view-entered"); - }); - } - view.mount?.(outlet); - return view.getNavigationContract(); - }; - - const getCurrent = () => current; - - return { - register, - navigate, - getCurrent, - }; -}; diff --git a/Bigscreen/src/core/sidebarData.js b/Bigscreen/src/core/sidebarData.js deleted file mode 100644 index 41fc692..0000000 --- a/Bigscreen/src/core/sidebarData.js +++ /dev/null @@ -1,58 +0,0 @@ -/** Primary shell navigation (data-driven). */ -export const PRIMARY_NAV = [ - { id: "home", label: "Home", icon: "⌂", target: "home" }, - { id: "library", label: "Library", icon: "⊟", target: "library" }, - { id: "store", label: "Store", icon: "⊞", target: "store" }, - { id: "mods", label: "Mods", icon: "◇", target: "mods" }, - { id: "settings", label: "Settings", icon: "⚙", target: "settings" }, -]; - -/** Quick actions in the expanded guide. */ -export const QUICK_ACTIONS = [ - { id: "search", label: "Search", icon: "⌕", panel: "search" }, - { id: "notifications", label: "Notifications", icon: "🔔", panel: "notifications" }, - { id: "downloads", label: "Downloads", icon: "↓", panel: "downloads" }, - { id: "controller", label: "Controller Settings", icon: "🎮", panel: "controller" }, - { id: "power", label: "Power Menu", icon: "⏻", action: "power" }, -]; - -/** Mock recently played titles (replace with library bridge later). */ -export const RECENT_ITEMS = [ - { - id: "recent-halo", - title: "Halo Infinite", - source: "steam", - lastPlayed: "2 hours ago", - accent: "#1e4a6e", - }, - { - id: "recent-cyber", - title: "Cyberpunk 2077", - source: "gog", - lastPlayed: "Yesterday", - accent: "#5c1a3a", - }, - { - id: "recent-fortnite", - title: "Fortnite", - source: "epic", - lastPlayed: "3 days ago", - accent: "#2a3a5c", - }, - { - id: "recent-emulator", - title: "Nebula Arcade", - source: "local", - lastPlayed: "Last week", - accent: "#1a3c2e", - }, -]; - -export const SOURCE_LABELS = { - steam: "Steam", - gog: "GOG", - epic: "Epic", - local: "Local", -}; - -export const NAVIGABLE_VIEWS = new Set(PRIMARY_NAV.map((item) => item.target)); diff --git a/Bigscreen/src/core/state.js b/Bigscreen/src/core/state.js deleted file mode 100644 index 48b8e53..0000000 --- a/Bigscreen/src/core/state.js +++ /dev/null @@ -1,149 +0,0 @@ -import { - detectActiveProfile, - getFallbackGlyphs, - resolveGlyphsForProfile, - watchGamepadProfile, -} from "./gamepadProfile.js"; -import { createPasskeyController } from "./passkey.js"; -import { loadExistingUser } from "./users.js"; - -const FALLBACK_THEME = { - colors: { - bg: "#0b1020", - panel: "#141c33", - panelAlt: "#1b2747", - text: "#eef4ff", - muted: "#9eb1d3", - accent: "#50d6ff", - danger: "#ff6b88", - success: "#7dff9e", - focus: "#50d6ff", - overlay: "rgba(5, 8, 18, 0.78)", - }, - spacing: { - xs: 6, - sm: 10, - md: 16, - lg: 24, - xl: 36, - }, - radius: { - sm: 10, - md: 16, - lg: 22, - }, - typography: { - body: 18, - title: 24, - display: 34, - }, -}; - -export const createAppState = () => { - const passkey = createPasskeyController(); - const state = { - passkey, - locked: true, - activeView: "lock", - user: null, - profileName: "Nebula User", - userSetupRequired: true, - nebula: { - coreReady: false, - source: "local-fallback", - navigation: null, - input: null, - glyphs: null, - theme: null, - ui: null, - }, - theme: FALLBACK_THEME, - controllerProfile: "generic", - glyphs: getFallbackGlyphs("generic"), - settingsCategory: "system", - settingsValues: { - network: true, - audio: true, - display: false, - storage: true, - system: false, - }, - passkeySetupRequired: !passkey.hasPasskey(), - }; - - state.refreshControllerGlyphs = (profile = detectActiveProfile()) => { - state.controllerProfile = profile; - state.glyphs = resolveGlyphsForProfile(profile, state.nebula.glyphs); - window.dispatchEvent( - new CustomEvent("nebula-controller-profile", { - detail: { profile }, - }), - ); - }; - - state.startGamepadProfileWatcher = () => { - if (state.stopGamepadProfileWatcher) { - state.stopGamepadProfileWatcher(); - } - state.stopGamepadProfileWatcher = watchGamepadProfile((profile) => { - state.refreshControllerGlyphs(profile); - }); - }; - - const applyThemeToDocument = () => { - const root = document.documentElement; - const { colors, spacing, radius, typography } = state.theme; - - Object.entries(colors).forEach(([key, value]) => root.style.setProperty(`--nebula-color-${key}`, value)); - Object.entries(spacing).forEach(([key, value]) => root.style.setProperty(`--nebula-spacing-${key}`, `${value}px`)); - Object.entries(radius).forEach(([key, value]) => root.style.setProperty(`--nebula-radius-${key}`, `${value}px`)); - Object.entries(typography).forEach(([key, value]) => root.style.setProperty(`--nebula-type-${key}`, `${value}px`)); - }; - - const initializeNebulaCore = async () => { - try { - const [input, navigation, glyphs, theme, ui] = await Promise.all([ - import("@nebulaproject/core/input"), - import("@nebulaproject/core/navigation"), - import("@nebulaproject/core/glyphs"), - import("@nebulaproject/core/theme"), - import("@nebulaproject/core/ui"), - ]); - - state.nebula = { - coreReady: true, - source: "@nebulaproject/core", - input, - navigation, - glyphs, - theme, - ui, - }; - - state.theme = typeof theme.createTheme === "function" ? theme.createTheme({}) : FALLBACK_THEME; - - state.refreshControllerGlyphs(detectActiveProfile()); - } catch (_error) { - state.nebula = { - ...state.nebula, - coreReady: false, - source: "local-fallback", - }; - state.theme = FALLBACK_THEME; - state.refreshControllerGlyphs("generic"); - } - - applyThemeToDocument(); - }; - - const initializeUser = async () => { - const user = await loadExistingUser(); - state.user = user; - state.userSetupRequired = !user; - state.profileName = user?.name ?? "New User"; - }; - - state.initializeNebulaCore = initializeNebulaCore; - state.initializeUser = initializeUser; - return state; -}; diff --git a/Bigscreen/src/core/users.js b/Bigscreen/src/core/users.js deleted file mode 100644 index a5b8149..0000000 --- a/Bigscreen/src/core/users.js +++ /dev/null @@ -1,105 +0,0 @@ -const LEGACY_USER_STORAGE_KEY = "nebula.user.v1"; - -const getInvoke = async () => { - const globalInvoke = window.__TAURI__?.core?.invoke; - if (typeof globalInvoke === "function") { - return globalInvoke; - } - - try { - const tauriCore = await import("@tauri-apps/api/core"); - return typeof tauriCore.invoke === "function" ? tauriCore.invoke : null; - } catch (_error) { - return null; - } -}; - -const normalizeUser = (record) => { - if (!record || typeof record !== "object") { - return null; - } - const id = Number(record.id); - const name = typeof record.name === "string" ? record.name.trim() : ""; - const firstName = typeof record.firstName === "string" ? record.firstName.trim() : ""; - const lastName = typeof record.lastName === "string" ? record.lastName.trim() : ""; - if (!Number.isFinite(id) || !name) { - return null; - } - - return { - id, - name, - firstName: firstName || name, - lastName: lastName || "", - createdAtUnixMs: Number(record.createdAtUnixMs ?? Date.now()), - }; -}; - -export const loadExistingUser = async () => { - const invoke = await getInvoke(); - if (!invoke) { - const legacyRaw = window.localStorage.getItem(LEGACY_USER_STORAGE_KEY); - if (!legacyRaw) { - return null; - } - try { - return normalizeUser(JSON.parse(legacyRaw)); - } catch (_parseError) { - return null; - } - } - - try { - const record = await invoke("get_first_user"); - return normalizeUser(record); - } catch (_error) { - const legacyRaw = window.localStorage.getItem(LEGACY_USER_STORAGE_KEY); - if (!legacyRaw) { - return null; - } - try { - return normalizeUser(JSON.parse(legacyRaw)); - } catch (_parseError) { - return null; - } - } -}; - -export const createUser = async ({ firstName, lastName = "" }) => { - const requestedFirstName = String(firstName ?? "").trim(); - const requestedLastName = String(lastName ?? "").trim(); - if (!requestedFirstName) { - throw new Error("First name is required."); - } - - const invoke = await getInvoke(); - if (!invoke) { - const fallback = { - id: 1, - name: requestedLastName ? `${requestedFirstName} ${requestedLastName}` : requestedFirstName, - firstName: requestedFirstName, - lastName: requestedLastName, - createdAtUnixMs: Date.now(), - }; - window.localStorage.setItem(LEGACY_USER_STORAGE_KEY, JSON.stringify(fallback)); - return fallback; - } - - try { - const record = await invoke("create_user", { - firstName: requestedFirstName, - lastName: requestedLastName || null, - }); - return normalizeUser(record); - } catch (_error) { - const fallback = { - id: 1, - name: requestedLastName ? `${requestedFirstName} ${requestedLastName}` : requestedFirstName, - firstName: requestedFirstName, - lastName: requestedLastName, - createdAtUnixMs: Date.now(), - }; - window.localStorage.setItem(LEGACY_USER_STORAGE_KEY, JSON.stringify(fallback)); - return fallback; - } -}; diff --git a/Bigscreen/src/index.html b/Bigscreen/src/index.html deleted file mode 100644 index 8c9c358..0000000 --- a/Bigscreen/src/index.html +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - - - - Nebula Shell - - - - - - -
    -
    - -
    - -
    -
    -
    -
    - -
    -
    - - - - - - - - - - - - - - - - diff --git a/Bigscreen/src/main.cpp b/Bigscreen/src/main.cpp new file mode 100644 index 0000000..9459325 --- /dev/null +++ b/Bigscreen/src/main.cpp @@ -0,0 +1,99 @@ +#include "InputRouter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +void logMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message) +{ + QFile logFile(QStringLiteral("nebula-bigscreen.log")); + if (!logFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) { + return; + } + + QTextStream stream(&logFile); + stream << QDateTime::currentDateTime().toString(Qt::ISODate) << " "; + switch (type) { + case QtDebugMsg: + stream << "DEBUG"; + break; + case QtInfoMsg: + stream << "INFO"; + break; + case QtWarningMsg: + stream << "WARN"; + break; + case QtCriticalMsg: + stream << "CRITICAL"; + break; + case QtFatalMsg: + stream << "FATAL"; + break; + } + + stream << ": " << message; + if (context.file) { + stream << " (" << context.file << ":" << context.line << ")"; + } + stream << "\n"; +} +} + +class InputFilter : public QObject +{ +public: + explicit InputFilter(InputRouter *router, QObject *parent = nullptr) + : QObject(parent) + , m_router(router) + { + } + +protected: + bool eventFilter(QObject *watched, QEvent *event) override + { + Q_UNUSED(watched); + if (event->type() == QEvent::KeyPress) { + return m_router->handleKeyPress(static_cast(event)); + } + return false; + } + +private: + InputRouter *m_router = nullptr; +}; + +int main(int argc, char *argv[]) +{ + qInstallMessageHandler(logMessageHandler); + + QGuiApplication app(argc, argv); + QGuiApplication::setApplicationName(QStringLiteral("Nebula Bigscreen")); + QGuiApplication::setOrganizationName(QStringLiteral("NebulaOS")); + QGuiApplication::setOrganizationDomain(QStringLiteral("nebulaos.local")); + + InputRouter inputRouter; + InputFilter inputFilter(&inputRouter); + app.installEventFilter(&inputFilter); + + QQmlApplicationEngine engine; + engine.rootContext()->setContextProperty(QStringLiteral("InputRouter"), &inputRouter); + + QObject::connect( + &engine, + &QQmlApplicationEngine::objectCreationFailed, + &app, + []() { QCoreApplication::exit(-1); }, + Qt::QueuedConnection); + + engine.load(QUrl(QStringLiteral("qrc:/qt/qml/Nebula/Bigscreen/qml/Main.qml"))); + + return app.exec(); +} diff --git a/Bigscreen/src/main.js b/Bigscreen/src/main.js deleted file mode 100644 index f04aca1..0000000 --- a/Bigscreen/src/main.js +++ /dev/null @@ -1,388 +0,0 @@ -import { createGuideSidebar } from "./core/guideSidebar.js"; -import { NAVIGABLE_VIEWS } from "./core/sidebarData.js"; -import { createInputManager } from "./core/input.js"; -import { createNavigationManager } from "./core/nav.js"; -import { createRouter } from "./core/router.js"; -import { createAppState } from "./core/state.js"; -import { createHomeView } from "./views/home/home.js"; -import { createLibraryView } from "./views/library/library.js"; -import { createLockView } from "./views/lock/lock.js"; -import { createUserSetupView } from "./views/onboarding/userSetup.js"; -import { createGuidePanelOverlay } from "./views/overlays/guidePanel.js"; -import { createKeyboardOverlay } from "./views/overlays/keyboard.js"; -import { createPowerMenuOverlay } from "./views/overlays/powerMenu.js"; -import { createPlaceholderView } from "./views/placeholder/placeholder.js"; -import { createSettingsView } from "./views/settings/settings.js"; - -const appRoot = document.querySelector("#app"); -const overlayRoot = document.querySelector("#overlay-root"); -const keyboardRoot = document.querySelector("#keyboard-root"); -const footer = document.querySelector("#app-footer"); -const guideShellRoot = document.querySelector("#guide-shell-root"); -const guideBackdrop = document.querySelector("#guide-backdrop"); - -const SIDEBAR_HIDDEN_VIEWS = new Set(["lock", "user-setup"]); - -const state = createAppState(); -const nav = createNavigationManager(); -const router = createRouter(appRoot); -const guidePanels = createGuidePanelOverlay({ mountRoot: overlayRoot }); -const powerMenu = createPowerMenuOverlay({ mountRoot: overlayRoot }); -const keyboard = createKeyboardOverlay({ mountRoot: keyboardRoot }); - -let currentViewContract = null; - -const remountNavigation = (options = {}) => { - if (!currentViewContract) return; - - currentViewContract = { - ...currentViewContract, - extraFocusRoots: SIDEBAR_HIDDEN_VIEWS.has(state.activeView) - ? [] - : guideSidebar.getFocusRoots(), - defaultFocus: options.defaultFocus ?? currentViewContract.defaultFocus, - }; - - nav.mount(currentViewContract); - - if (options.focusKey) { - const target = document.querySelector(`[data-focus-key="${CSS.escape(options.focusKey)}"]`); - if (target) { - currentViewContract.defaultFocus = target; - nav.mount(currentViewContract); - } - } -}; - -const guideSidebar = createGuideSidebar({ - state, - renderView, - openPowerMenu, - openGuidePanel: (panelId) => { - guidePanels.open(panelId, { - onClose: () => remountNavigation(), - }); - setFooterHints("#guide-panel-hints-template", state.glyphs); - }, - onGuideChange: ({ expanded }) => { - if (expanded) { - const active = document.querySelector( - "#guide-panel [data-sidebar-nav].is-active, #guide-panel .guide-nav-item", - ); - remountNavigation({ defaultFocus: active ?? undefined }); - setFooterHints("#guide-hints-template", state.glyphs); - return; - } - remountNavigation(); - setFooterHints(currentViewContract?.hintsTemplate ?? "#global-hints-template", state.glyphs); - }, -}); - -const emitUiHook = (type, payload = {}) => { - window.dispatchEvent( - new CustomEvent("nebula-ui-hook", { - detail: { type, ...payload }, - }), - ); -}; - -const setFooterHints = (templateId, glyphs) => { - const template = document.querySelector(templateId); - if (!template) { - footer.innerHTML = ""; - return; - } - footer.innerHTML = template.innerHTML; - footer.querySelectorAll("[data-glyph]").forEach((element) => { - const action = element.dataset.glyph; - element.textContent = glyphs[action] ?? action; - }); -}; - -const updateClockLabels = () => { - const now = new Date(); - const formattedTime = now.toLocaleTimeString([], { - hour: "2-digit", - minute: "2-digit", - }); - const formattedDate = now.toLocaleDateString([], { - weekday: "short", - month: "short", - day: "numeric", - year: "numeric", - }); - document.querySelectorAll("[data-clock]").forEach((clock) => { - clock.textContent = formattedTime; - }); - document.querySelectorAll("[data-date]").forEach((date) => { - date.textContent = formattedDate; - }); -}; - -const updateSidebar = (viewId) => { - const body = document.body; - - if (SIDEBAR_HIDDEN_VIEWS.has(viewId)) { - body.classList.add("body-no-sidebar"); - guideSidebar.close(); - return; - } - - body.classList.remove("body-no-sidebar"); - guideSidebar.updateActiveView(viewId); - guideSidebar.syncProfile(); -}; - -function renderView(viewId) { - const contract = router.navigate(viewId); - if (!contract) { - return; - } - state.activeView = viewId; - updateSidebar(viewId); - - if (guideSidebar.isExpanded()) { - guideSidebar.close(); - } - - currentViewContract = { - ...contract, - extraFocusRoots: SIDEBAR_HIDDEN_VIEWS.has(viewId) ? [] : guideSidebar.getFocusRoots(), - }; - - nav.mount(currentViewContract); - setFooterHints(currentViewContract.hintsTemplate ?? "#global-hints-template", state.glyphs); - updateClockLabels(); -} - -const refreshNavigation = (event) => { - if (!currentViewContract?.focusRoot) { - return; - } - - const focusKey = event?.detail?.focusKey; - const requestedFocus = focusKey - ? document.querySelector(`[data-focus-key="${CSS.escape(focusKey)}"]`) - : null; - - remountNavigation({ - defaultFocus: requestedFocus ?? currentViewContract.defaultFocus, - }); -}; - -const registerViews = () => { - const context = { state, renderView, powerMenu, keyboard, openPowerMenu }; - router.register(createUserSetupView(context)); - router.register(createLockView(context)); - router.register(createHomeView(context)); - router.register(createSettingsView(context)); - router.register(createLibraryView(context)); - router.register( - createPlaceholderView(context, { - id: "store", - title: "Store", - subtitle: "Discover", - }), - ); - router.register( - createPlaceholderView(context, { - id: "mods", - title: "Mods", - subtitle: "Community", - }), - ); -}; - -function openPowerMenu() { - if (guideSidebar.isExpanded()) { - guideSidebar.close(); - } - powerMenu.open({ - onClose: () => { - remountNavigation(); - setFooterHints(currentViewContract?.hintsTemplate ?? "#global-hints-template", state.glyphs); - }, - }); - setFooterHints("#minimal-hints-template", state.glyphs); -} - -const toggleGuide = () => { - if (SIDEBAR_HIDDEN_VIEWS.has(state.activeView)) { - return; - } - guideSidebar.toggle(); -}; - -const handleGuideAccept = (focused) => { - const handled = guideSidebar.handleAccept(focused); - if (handled) { - remountNavigation({ focusKey: focused?.dataset?.focusKey }); - } -}; - -const handleAction = (action) => { - if (powerMenu.isOpen()) { - powerMenu.handleAction(action); - return; - } - - if (guidePanels.isOpen()) { - if (guidePanels.handleAction(action)) { - return; - } - } - - if (keyboard.isOpen()) { - const consumed = keyboard.handleAction(action); - if (consumed) { - return; - } - } - - if (!currentViewContract) { - return; - } - - if (action === "guide") { - toggleGuide(); - return; - } - - if (action === "menu") { - if (SIDEBAR_HIDDEN_VIEWS.has(state.activeView)) { - currentViewContract.onMenu?.(); - return; - } - - if (guideSidebar.isExpanded()) { - guideSidebar.close(); - return; - } - toggleGuide(); - return; - } - - if (guideSidebar.isExpanded()) { - if (action === "back") { - guideSidebar.close(); - return; - } - - if (action === "up" || action === "down" || action === "left" || action === "right") { - nav.move(action); - emitUiHook("move", { action }); - return; - } - - if (action === "accept") { - const focused = nav.getFocusedElement(); - focused?.classList.add("is-pressed"); - window.setTimeout(() => focused?.classList.remove("is-pressed"), 180); - handleGuideAccept(focused); - return; - } - - return; - } - - if (action === "up" || action === "down" || action === "left" || action === "right") { - if (currentViewContract.captureDirectionalInput) { - const handled = currentViewContract.onAction?.(action, nav.getFocusedElement()); - if (handled) { - return; - } - } - - nav.move(action); - emitUiHook("move", { action }); - return; - } - - if (action === "accept") { - const focused = nav.getFocusedElement(); - focused?.classList.add("is-pressed"); - window.setTimeout(() => focused?.classList.remove("is-pressed"), 180); - emitUiHook("accept", { focusKey: focused?.dataset.focusKey ?? null }); - - if (focused?.dataset.navRegion === "sidebar") { - if (focused.dataset.guideAction === "toggle") { - guideSidebar.open(); - return; - } - if (focused.dataset.guideAction === "power") { - openPowerMenu(); - return; - } - const target = focused.dataset.target; - if (target && NAVIGABLE_VIEWS.has(target)) { - renderView(target); - } - return; - } - - currentViewContract.onAccept?.(focused); - return; - } - - if (action === "back") { - emitUiHook("back"); - currentViewContract.onBack?.(); - return; - } - - currentViewContract.onAction?.(action, nav.getFocusedElement()); -}; - -const initialize = async () => { - guideSidebar.mount({ - shellRoot: guideShellRoot, - backdropRoot: guideBackdrop, - }); - - await state.initializeUser(); - await state.initializeNebulaCore(); - state.startGamepadProfileWatcher?.(); - registerViews(); - renderView(state.userSetupRequired ? "user-setup" : "lock"); - updateClockLabels(); - window.setInterval(updateClockLabels, 1000); - - window.addEventListener("nebula-navigation-refresh", refreshNavigation); - window.addEventListener("nebula-controller-profile", () => { - setFooterHints(currentViewContract?.hintsTemplate ?? "#global-hints-template", state.glyphs); - }); - window.addEventListener("nebula-guide-close", (event) => { - remountNavigation({ focusKey: event.detail?.focusKey }); - }); - - window.addEventListener("keydown", (event) => { - if (event.key === "Escape" && guideSidebar.isExpanded()) { - event.preventDefault(); - guideSidebar.close(); - } - }); - - const input = createInputManager({ - onAction: handleAction, - actions: [ - "up", - "down", - "left", - "right", - "accept", - "back", - "menu", - "guide", - "clear", - "y", - "l1", - "r1", - "l2", - "r2", - ], - }); - - input.start(); -}; - -initialize(); diff --git a/Bigscreen/src/styles.css b/Bigscreen/src/styles.css deleted file mode 100644 index e57b8ac..0000000 --- a/Bigscreen/src/styles.css +++ /dev/null @@ -1,112 +0,0 @@ -.logo.vanilla:hover { - filter: drop-shadow(0 0 2em #ffe21c); -} -:root { - font-family: Inter, Avenir, Helvetica, Arial, sans-serif; - font-size: 16px; - line-height: 24px; - font-weight: 400; - - color: #0f0f0f; - background-color: #f6f6f6; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; -} - -.container { - margin: 0; - padding-top: 10vh; - display: flex; - flex-direction: column; - justify-content: center; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: 0.75s; -} - -.logo.tauri:hover { - filter: drop-shadow(0 0 2em #24c8db); -} - -.row { - display: flex; - justify-content: center; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} - -a:hover { - color: #535bf2; -} - -h1 { - text-align: center; -} - -input, -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - color: #0f0f0f; - background-color: #ffffff; - transition: border-color 0.25s; - box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); -} - -button { - cursor: pointer; -} - -button:hover { - border-color: #396cd8; -} -button:active { - border-color: #396cd8; - background-color: #e8e8e8; -} - -input, -button { - outline: none; -} - -#greet-input { - margin-right: 5px; -} - -@media (prefers-color-scheme: dark) { - :root { - color: #f6f6f6; - background-color: #2f2f2f; - } - - a:hover { - color: #24c8db; - } - - input, - button { - color: #ffffff; - background-color: #0f0f0f98; - } - button:active { - background-color: #0f0f0f69; - } -} diff --git a/Bigscreen/src/styles/base.css b/Bigscreen/src/styles/base.css deleted file mode 100644 index 2423049..0000000 --- a/Bigscreen/src/styles/base.css +++ /dev/null @@ -1,230 +0,0 @@ -* { - box-sizing: border-box; -} - -html, -body { - margin: 0; - width: 100%; - height: 100%; - overflow: hidden; - background: var(--nebula-color-bg); - color: var(--nebula-color-text); - font-family: "Segoe UI", system-ui, -apple-system, BlinkMacSystemFont, sans-serif; -} - -/* ─── Flat background: no blur layers ─── */ -#nebula-background { - position: fixed; - inset: 0; - z-index: -2; - overflow: hidden; -} - -.nebula-layer.gradient { - position: absolute; - inset: 0; - background: - radial-gradient(circle at 15% 10%, rgba(79, 216, 255, 0.06), transparent 38%), - radial-gradient(circle at 85% 80%, rgba(157, 79, 224, 0.07), transparent 38%), - linear-gradient(165deg, #050810 0%, #070a14 40%, #0a0d1c 70%, #0d0a1e 100%); -} - -.nebula-layer.starfield { - position: absolute; - inset: 0; - background-image: - radial-gradient(circle, rgba(255, 255, 255, 0.75) 0.6px, transparent 1.2px), - radial-gradient(circle, rgba(79, 216, 255, 0.4) 0.5px, transparent 1px); - background-size: 200px 200px, 300px 300px; - background-position: 0 0, 140px 110px; - opacity: 0.18; -} - -/* fog and vignette layers: hidden for Zero-Blur Policy */ -.nebula-layer.fog, -.nebula-layer.vignette { - display: none; -} - -/* ─── No shell depth blur ─── */ -.shell-chrome, -.shell-depth-blur { - display: none; -} - -/* ─── Root layout: sidebar + main ─── */ -.app-layout { - display: flex; - width: 100vw; - height: 100vh; - overflow: hidden; - position: relative; - z-index: 0; -} - -/* ─── Guide sidebar mount ─── */ -.guide-mount { - flex-shrink: 0; - height: 100%; -} - -/* ─── Main content area ─── */ -.app-main-area { - position: relative; - flex: 1; - display: flex; - flex-direction: column; - height: 100%; - overflow: hidden; - min-width: 0; -} - -.placeholder-view .placeholder-body { - margin: var(--nebula-spacing-lg); - max-width: 720px; -} - -.placeholder-copy { - margin: var(--nebula-spacing-md) 0 var(--nebula-spacing-lg); - color: var(--nebula-color-muted); - line-height: 1.5; -} - -.placeholder-back { - min-height: 52px; - padding: 0 var(--nebula-spacing-lg); - border: none; - border-radius: var(--nebula-radius-pill); - background: var(--nebula-color-panel-alt); - color: var(--nebula-color-text); - font-weight: 600; - cursor: pointer; -} - -.app-shell { - flex: 1; - min-height: 0; - overflow: hidden; - position: relative; - z-index: 2; -} - -/* ─── View transitions ─── */ -.view { - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - opacity: 0; - transform: translateX(24px); -} - -.view.view-entered { - opacity: 1; - transform: translateX(0); - transition: - transform var(--nebula-duration-slow) var(--nebula-ease-console), - opacity var(--nebula-duration-nav) var(--nebula-ease-standard); -} - -/* ─── Shared shell elements ─── */ -.shell-topbar { - display: flex; - flex-direction: column; - padding: 14px var(--nebula-spacing-xl); - border-bottom: 1px solid var(--nebula-color-border); - flex-shrink: 0; -} - -.shell-brand { - margin: 0; - letter-spacing: 0.12em; - text-transform: uppercase; - font-size: 14px; - font-weight: 700; - color: var(--nebula-color-text); -} - -.shell-time { - letter-spacing: 0.05em; - font-size: 16px; - font-weight: 600; - color: var(--nebula-color-muted); -} - -.shell-avatar { - width: 30px; - height: 30px; - border-radius: var(--nebula-radius-pill); - border: 2px solid rgba(79, 216, 255, 0.3); - background: radial-gradient(circle at 34% 30%, rgba(255, 255, 255, 0.8), rgba(75, 81, 155, 0.7)); -} - -.shell-topbar-content { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; -} - -.shell-accent-line { - display: none; -} - -.shell-status { - display: inline-flex; - align-items: center; - gap: var(--nebula-spacing-md); -} - -.view-header { - display: flex; - align-items: center; - justify-content: space-between; -} - -.view-title { - margin: 0; - font-size: var(--nebula-type-display); - line-height: 1.15; - letter-spacing: 0.01em; -} - -.muted { - color: var(--nebula-color-muted); -} - -/* ─── Footer hints bar ─── */ -.app-footer { - flex-shrink: 0; - min-height: 44px; - padding: 0 var(--nebula-spacing-xl) var(--nebula-spacing-sm); - border-top: 1px solid var(--nebula-color-border); - display: flex; - align-items: center; -} - -.hint-row { - display: flex; - align-items: center; - gap: var(--nebula-spacing-lg); - color: var(--nebula-color-muted); -} - -.hint { - display: inline-flex; - gap: 6px; - align-items: center; - font-size: 14px; -} - -/* ─── Background animations ─── */ -@keyframes starfieldShift { - 0% { transform: translate3d(0, 0, 0); } - 100% { transform: translate3d(-140px, -110px, 0); } -} - -.nebula-layer.starfield { - animation: starfieldShift 60s linear infinite; -} diff --git a/Bigscreen/src/styles/components.css b/Bigscreen/src/styles/components.css deleted file mode 100644 index ee81db2..0000000 --- a/Bigscreen/src/styles/components.css +++ /dev/null @@ -1,159 +0,0 @@ -/* ─── Panel (flat, no blur) ─── */ -.panel { - background: var(--nebula-color-panel); - border: 1px solid var(--nebula-color-border); - border-radius: var(--nebula-radius-md); - padding: var(--nebula-spacing-lg); -} - -/* ─── Focusable — flat neon border, no glow/blur ─── */ -.focusable { - border: 2px solid transparent; - border-radius: var(--nebula-radius-md); - outline: none; - position: relative; - overflow: hidden; - cursor: pointer; - will-change: transform, border-color; - transform: translateZ(0); - transition: - transform var(--nebula-duration-nav) var(--nebula-ease-console), - border-color var(--nebula-duration-nav) var(--nebula-ease-console), - background-color var(--nebula-duration-nav) var(--nebula-ease-standard); -} - -.focusable.is-focused { - border-color: var(--nebula-color-accent); - transform: scale(1.03) translateZ(0); -} - -.sidebar-nav-item.focusable.is-focused, -.guide-rail-item.focusable.is-focused, -.guide-nav-item.focusable.is-focused { - border-top-color: transparent; - border-bottom-color: transparent; - border-left-color: var(--nebula-color-accent); - border-right-color: var(--nebula-color-purple); - transform: none; -} - -.focusable.is-pressed { - animation: uiPressPulse var(--nebula-duration-fast) var(--nebula-ease-snap); -} - -/* ─── Tile ─── */ -.tile { - min-height: 160px; - min-width: 280px; - display: flex; - flex-direction: column; - justify-content: flex-end; - gap: var(--nebula-spacing-xs); - background: var(--nebula-color-panel-alt); - color: var(--nebula-color-text); - border-radius: var(--nebula-radius-md); - padding: var(--nebula-spacing-lg); - transform-origin: center; - position: relative; - overflow: hidden; - border: 2px solid var(--nebula-color-border); -} - -.tile.is-focused { - border-color: var(--nebula-color-accent); - transform: scale(1.04) translateZ(0); -} - -.tile-icon { - font-size: 38px; - line-height: 1; - opacity: 0.9; - transition: transform var(--nebula-duration-nav) var(--nebula-ease-console); -} - -.tile.is-focused .tile-icon { - transform: scale(1.06) translateY(-2px); -} - -.tile-label { - margin: 0; - font-size: clamp(20px, 2vw, 26px); - font-weight: 700; - letter-spacing: -0.01em; - z-index: 1; -} - -.tile-meta { - margin: 0; - font-size: 14px; - color: var(--nebula-color-muted); - z-index: 1; -} - -.tile-accent-bar { - position: absolute; - left: 0; - bottom: 0; - right: 0; - height: 3px; - background: var(--nebula-color-accent); - opacity: 0; - transform: scaleX(0); - transform-origin: left center; - transition: - opacity var(--nebula-duration-nav) var(--nebula-ease-console), - transform var(--nebula-duration-nav) var(--nebula-ease-console); -} - -.dashboard-tile.is-focused .tile-accent-bar { - opacity: 1; - transform: scaleX(1); -} - -/* ─── Controller button prompts ─── */ -.btn-prompt { - display: inline-flex; - align-items: center; - justify-content: center; - width: 20px; - height: 20px; - border-radius: 50%; - font-size: 11px; - font-weight: 800; - flex-shrink: 0; - line-height: 1; -} - -.btn-a { background: var(--btn-a); color: #fff; } -.btn-b { background: var(--btn-b); color: #fff; } -.btn-x { background: var(--btn-x); color: #fff; } -.btn-y { background: var(--btn-y); color: #fff; } - -.btn-glyph { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0 6px; - height: 18px; - border-radius: var(--nebula-radius-sm); - font-size: 11px; - font-weight: 700; - background: rgba(255, 255, 255, 0.1); - border: 1px solid rgba(255, 255, 255, 0.18); - color: var(--nebula-color-muted); -} - -/* ─── Button-like ─── */ -.button-like { - background: var(--nebula-color-panel-alt); - color: var(--nebula-color-text); - padding: var(--nebula-spacing-md) var(--nebula-spacing-lg); - border-radius: var(--nebula-radius-sm); -} - -/* ─── Animations ─── */ -@keyframes uiPressPulse { - 0% { transform: scale(1); } - 40% { transform: scale(0.97); } - 100% { transform: scale(1); } -} diff --git a/Bigscreen/src/styles/guide.css b/Bigscreen/src/styles/guide.css deleted file mode 100644 index 2a6fc50..0000000 --- a/Bigscreen/src/styles/guide.css +++ /dev/null @@ -1,618 +0,0 @@ -/* ─── Nebula guide sidebar (Xbox-inspired shell navigation) ─── */ - -:root { - --guide-rail-width: 88px; - --guide-panel-width: min(400px, 92vw); - --guide-glass: rgba(8, 10, 22, 0.92); - --guide-glass-border: rgba(79, 216, 255, 0.14); - --guide-accent-glow: rgba(79, 216, 255, 0.35); -} - -.guide-shell { - position: relative; - flex-shrink: 0; - width: var(--guide-rail-width); - height: 100%; - z-index: 30; -} - -.body-no-sidebar .guide-shell, -.body-no-sidebar .guide-backdrop { - display: none; -} - -/* ─── Collapsed rail ─── */ -.guide-rail { - width: var(--guide-rail-width); - height: 100%; - display: flex; - flex-direction: column; - align-items: center; - padding: 14px 0 12px; - background: linear-gradient(180deg, rgba(10, 12, 24, 0.98) 0%, rgba(7, 9, 18, 0.98) 100%); - border-right: 1px solid var(--guide-glass-border); - transition: opacity var(--nebula-duration-nav) var(--nebula-ease-standard); -} - -.guide-expanded .guide-rail { - opacity: 0; - pointer-events: none; -} - -.guide-rail-brand { - width: 52px; - height: 52px; - border-radius: 50%; - border: 1.5px solid rgba(79, 216, 255, 0.3); - background: radial-gradient(circle at 38% 32%, rgba(79, 216, 255, 0.55), rgba(157, 79, 224, 0.75) 55%, rgba(30, 16, 60, 1)); - color: var(--nebula-color-text); - display: grid; - place-items: center; - margin-bottom: 18px; - cursor: pointer; - padding: 0; -} - -.guide-brand-mark { - font-size: 20px; - line-height: 1; - text-shadow: 0 0 12px var(--guide-accent-glow); -} - -.guide-rail-nav { - list-style: none; - margin: 0; - padding: 0; - width: 100%; - flex: 1; - display: flex; - flex-direction: column; - gap: 4px; -} - -.guide-rail-item { - display: flex; - align-items: center; - justify-content: center; - min-height: 52px; - margin: 0 8px; - border-radius: var(--nebula-radius-md); - border: 2px solid transparent; - border-left: 3px solid transparent; - color: var(--nebula-color-muted); - cursor: pointer; - transition: - color var(--nebula-duration-fast) var(--nebula-ease-standard), - background var(--nebula-duration-fast) var(--nebula-ease-standard), - border-color var(--nebula-duration-fast) var(--nebula-ease-standard), - box-shadow var(--nebula-duration-fast) var(--nebula-ease-standard); -} - -.guide-rail-item.is-active { - color: var(--nebula-color-text); - background: rgba(79, 216, 255, 0.08); - border-left-color: var(--nebula-color-accent); - box-shadow: inset 3px 0 0 var(--nebula-color-accent); -} - -.guide-rail-item.is-focused { - color: var(--nebula-color-text); - background: rgba(79, 216, 255, 0.14); - border-color: rgba(79, 216, 255, 0.45); - border-left-color: var(--nebula-color-accent); - box-shadow: - 0 0 0 1px rgba(79, 216, 255, 0.25), - 0 0 18px rgba(79, 216, 255, 0.12); - transform: none; -} - -.guide-rail-icon { - font-size: 22px; - line-height: 1; -} - -.guide-rail-footer { - display: flex; - flex-direction: column; - align-items: center; - gap: 10px; - padding-top: 12px; - border-top: 1px solid rgba(255, 255, 255, 0.06); - width: 100%; -} - -.guide-rail-expand, -.guide-rail-profile { - width: 48px; - height: 48px; - border-radius: var(--nebula-radius-md); - border: 2px solid transparent; - background: rgba(255, 255, 255, 0.04); - color: var(--nebula-color-muted); - cursor: pointer; - display: grid; - place-items: center; - padding: 0; -} - -.guide-rail-expand.is-focused, -.guide-rail-profile.is-focused { - border-color: var(--nebula-color-accent); - color: var(--nebula-color-text); - transform: none; -} - -.guide-rail-expand-icon { - font-size: 20px; -} - -.guide-rail-avatar { - width: 34px; - height: 34px; - border-radius: 50%; - display: grid; - place-items: center; - font-size: 12px; - font-weight: 700; - background: linear-gradient(145deg, rgba(79, 216, 255, 0.35), rgba(157, 79, 224, 0.5)); - border: 2px solid rgba(79, 216, 255, 0.35); - color: var(--nebula-color-text); -} - -/* ─── Expanded guide panel (overlay) ─── */ -.guide-panel { - position: fixed; - left: 0; - top: 0; - bottom: 0; - width: var(--guide-panel-width); - z-index: 35; - transform: translateX(-104%); - transition: transform var(--nebula-duration-slow) var(--nebula-ease-console); - pointer-events: none; -} - -.guide-expanded .guide-panel { - transform: translateX(0); - pointer-events: auto; -} - -.guide-panel-inner { - height: 100%; - display: flex; - flex-direction: column; - overflow: hidden; - background: var(--guide-glass); - border-right: 1px solid var(--guide-glass-border); - box-shadow: - 8px 0 40px rgba(0, 0, 0, 0.45), - inset -1px 0 0 rgba(157, 79, 224, 0.12); -} - -.guide-header { - padding: 20px 22px 16px; - display: grid; - gap: 12px; -} - -.guide-profile { - display: flex; - align-items: center; - gap: 14px; -} - -.guide-profile-avatar, -.guide-footer-avatar { - width: 48px; - height: 48px; - border-radius: 50%; - display: grid; - place-items: center; - font-size: 15px; - font-weight: 700; - flex-shrink: 0; - background: linear-gradient(145deg, rgba(79, 216, 255, 0.4), rgba(157, 79, 224, 0.55)); - border: 2px solid rgba(79, 216, 255, 0.4); - color: var(--nebula-color-text); - box-shadow: 0 0 20px rgba(79, 216, 255, 0.15); -} - -.guide-profile-name { - margin: 0; - font-size: 20px; - font-weight: 700; - letter-spacing: -0.02em; -} - -.guide-profile-status { - margin: 4px 0 0; - display: flex; - align-items: center; - gap: 8px; - font-size: 13px; - color: var(--nebula-color-muted); -} - -.guide-status-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--nebula-color-success); - box-shadow: 0 0 8px rgba(79, 255, 136, 0.6); -} - -.guide-header-meta { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; -} - -.guide-header-time { - margin: 0; - font-size: 22px; - font-weight: 600; - font-variant-numeric: tabular-nums; -} - -.guide-header-date { - margin: 2px 0 0; - font-size: 12px; - color: var(--nebula-color-muted); -} - -.guide-header-indicators { - display: flex; - gap: 10px; - color: var(--nebula-color-accent); - font-size: 14px; - opacity: 0.85; -} - -.guide-header-brand { - margin: 0; - font-size: 11px; - font-weight: 700; - letter-spacing: 0.14em; - text-transform: uppercase; - color: var(--nebula-color-muted); - display: flex; - align-items: center; - gap: 8px; -} - -.guide-header-brand-mark { - color: var(--nebula-color-accent); - text-shadow: 0 0 10px var(--guide-accent-glow); -} - -.guide-divider { - border: none; - height: 1px; - margin: 0 18px; - background: linear-gradient(90deg, transparent, rgba(79, 216, 255, 0.2), transparent); -} - -.guide-section { - padding: 12px 18px; - min-height: 0; -} - -.guide-section-recent { - flex: 1; - overflow: hidden; - display: flex; - flex-direction: column; -} - -.guide-section-title { - margin: 0 0 10px; - font-size: 11px; - font-weight: 700; - letter-spacing: 0.12em; - text-transform: uppercase; - color: var(--nebula-color-muted); -} - -.guide-nav-list { - list-style: none; - margin: 0; - padding: 0; - display: flex; - flex-direction: column; - gap: 6px; -} - -.guide-nav-item { - display: flex; - align-items: center; - gap: 14px; - min-height: 48px; - padding: 0 14px; - border-radius: var(--nebula-radius-md); - border: 2px solid transparent; - border-left: 3px solid transparent; - color: var(--nebula-color-muted); - cursor: pointer; - transition: - background var(--nebula-duration-fast) var(--nebula-ease-standard), - border-color var(--nebula-duration-fast) var(--nebula-ease-standard), - color var(--nebula-duration-fast) var(--nebula-ease-standard); -} - -.guide-nav-item.is-active { - color: var(--nebula-color-text); - background: rgba(79, 216, 255, 0.08); - border-left-color: var(--nebula-color-accent); -} - -.guide-nav-item.is-active::after { - content: ""; - margin-left: auto; - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--nebula-color-accent); - box-shadow: 0 0 8px var(--guide-accent-glow); -} - -.guide-nav-item.is-focused { - color: var(--nebula-color-text); - background: rgba(79, 216, 255, 0.16); - border-color: rgba(79, 216, 255, 0.45); - border-left-color: var(--nebula-color-accent); - box-shadow: 0 0 22px rgba(79, 216, 255, 0.1); - transform: none; -} - -.guide-nav-icon { - font-size: 20px; - width: 28px; - text-align: center; -} - -.guide-nav-label { - font-size: 16px; - font-weight: 600; -} - -.guide-quick-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 8px; -} - -.guide-quick-item { - display: flex; - align-items: center; - gap: 10px; - min-height: 44px; - padding: 8px 12px; - border-radius: var(--nebula-radius-md); - border: 2px solid transparent; - background: rgba(255, 255, 255, 0.03); - color: var(--nebula-color-muted); - cursor: pointer; - text-align: left; - font: inherit; -} - -.guide-quick-item.is-focused { - border-color: var(--nebula-color-accent); - color: var(--nebula-color-text); - background: rgba(79, 216, 255, 0.12); - transform: none; -} - -.guide-quick-icon { - font-size: 16px; - opacity: 0.9; -} - -.guide-quick-label { - font-size: 13px; - font-weight: 600; -} - -.guide-recent-list { - display: flex; - flex-direction: column; - gap: 6px; - overflow-y: auto; - padding-right: 4px; - max-height: min(220px, 28vh); -} - -.guide-recent-item { - display: flex; - align-items: center; - gap: 12px; - width: 100%; - min-height: 56px; - padding: 8px 10px; - border-radius: var(--nebula-radius-md); - border: 2px solid transparent; - background: rgba(255, 255, 255, 0.02); - color: var(--nebula-color-text); - cursor: pointer; - text-align: left; - font: inherit; -} - -.guide-recent-item.is-focused { - border-color: var(--nebula-color-accent); - background: rgba(79, 216, 255, 0.1); - transform: none; -} - -.guide-recent-cover { - width: 40px; - height: 40px; - border-radius: var(--nebula-radius-sm); - flex-shrink: 0; - background: linear-gradient(135deg, var(--recent-accent, #1a2a44), rgba(0, 0, 0, 0.5)); - border: 1px solid rgba(255, 255, 255, 0.1); -} - -.guide-recent-meta { - display: flex; - flex-direction: column; - gap: 4px; - min-width: 0; -} - -.guide-recent-title { - font-size: 14px; - font-weight: 600; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.guide-recent-sub { - display: flex; - align-items: center; - gap: 8px; - font-size: 11px; - color: var(--nebula-color-muted); -} - -.guide-source-badge { - padding: 2px 7px; - border-radius: var(--nebula-radius-pill); - font-size: 10px; - font-weight: 700; - letter-spacing: 0.04em; - text-transform: uppercase; - background: rgba(79, 216, 255, 0.12); - color: var(--nebula-color-accent); -} - -.guide-source-badge[data-source="steam"] { - background: rgba(27, 40, 56, 0.9); - color: #66c0f4; -} - -.guide-source-badge[data-source="gog"] { - background: rgba(60, 20, 90, 0.5); - color: #c9a0ff; -} - -.guide-source-badge[data-source="epic"] { - background: rgba(30, 30, 30, 0.8); - color: #f0f0f0; -} - -.guide-recent-empty { - padding: 20px 12px; - text-align: center; - border-radius: var(--nebula-radius-md); - border: 1px dashed rgba(255, 255, 255, 0.1); - background: rgba(0, 0, 0, 0.2); -} - -.guide-recent-empty-icon { - font-size: 28px; - opacity: 0.4; - display: block; - margin-bottom: 8px; -} - -.guide-recent-empty-title { - margin: 0 0 6px; - font-weight: 600; -} - -.guide-recent-empty-copy { - margin: 0; - font-size: 13px; - color: var(--nebula-color-muted); -} - -.guide-footer { - margin-top: auto; - padding: 14px 18px 16px; - border-top: 1px solid rgba(255, 255, 255, 0.06); - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 8px; -} - -.guide-footer-btn { - display: inline-flex; - align-items: center; - gap: 8px; - min-height: 40px; - padding: 0 12px; - border-radius: var(--nebula-radius-pill); - border: 2px solid transparent; - background: rgba(255, 255, 255, 0.04); - color: var(--nebula-color-muted); - font-size: 13px; - font-weight: 600; - cursor: pointer; - font: inherit; -} - -.guide-footer-btn.is-focused { - border-color: var(--nebula-color-accent); - color: var(--nebula-color-text); - transform: none; -} - -.guide-footer-avatar { - width: 28px; - height: 28px; - font-size: 10px; -} - -.guide-version { - flex: 1 1 100%; - margin: 6px 0 0; - font-size: 11px; - color: var(--nebula-color-muted); - opacity: 0.75; -} - -/* Dim layer over main content when guide is open (scoped to .app-main-area) */ -.guide-backdrop { - position: absolute; - inset: 0; - z-index: 10; - background: rgba(3, 5, 14, 0.62); - opacity: 0; - pointer-events: none; - transition: opacity var(--nebula-duration-slow) var(--nebula-ease-standard); -} - -body.guide-expanded .guide-backdrop, -.guide-backdrop:not([hidden]) { - opacity: 1; - pointer-events: auto; -} - -.guide-backdrop[hidden] { - display: block !important; - opacity: 0; - pointer-events: none; -} - -/* Focus overrides for guide items */ -.guide-rail-item.focusable.is-focused, -.guide-nav-item.focusable.is-focused, -.guide-quick-item.focusable.is-focused, -.guide-recent-item.focusable.is-focused, -.guide-footer-btn.focusable.is-focused, -.guide-rail-brand.focusable.is-focused, -.guide-rail-expand.focusable.is-focused, -.guide-rail-profile.focusable.is-focused { - transform: none; -} - -@media (max-width: 720px) { - :root { - --guide-rail-width: 72px; - --guide-panel-width: min(100vw, 100%); - } -} diff --git a/Bigscreen/src/styles/shell-guidelines.md b/Bigscreen/src/styles/shell-guidelines.md deleted file mode 100644 index 384ac58..0000000 --- a/Bigscreen/src/styles/shell-guidelines.md +++ /dev/null @@ -1,53 +0,0 @@ -# Nebula Shell UI Guidelines (Dashboard Refresh) - -## Layout Structure -- Home uses a left-aligned horizontal tile rail (`.tile-rail`) with one primary row of large app tiles. -- Top bar is shared across shell views with brand left and time/profile right. -- Settings uses a horizontal category bar at top and card panel content below. -- Lock screen keeps immersive centered time/date and reveals PIN panel on first input. - -## Animation Guidelines -- Use cubic-bezier curves only: - - `--nebula-ease-console`: focus travel and panel transitions. - - `--nebula-ease-standard`: opacity and ambient motion. - - `--nebula-ease-snap`: press pulse feedback. -- Focus transitions target `120ms–180ms` (`--nebula-duration-fast` / `--nebula-duration-nav`). -- Use transform/opacity for movement and avoid layout-triggering transitions. -- Page changes use `.view` to `.view-entered` slide-in transitions. -- Press feedback uses `.is-pressed` and `uiPressPulse` keyframes. - -## Component Breakdown - -### TopBar -- Class roots: `.shell-topbar`, `.shell-brand`, `.shell-status`, `.shell-time`, `.shell-avatar`, `.shell-accent-line`. -- Purpose: persistent identity + status + navigation-reactive accent line. -- Reactive token: `--nebula-accent-line-x` updates from focus manager. - -### TileRow -- Class root: `.tile-rail`. -- Purpose: horizontal app rail with controller-first left/right travel and smooth scroll. -- Behavior: focus auto-centers via `scrollTo` and updates parallax variables. - -### Tile -- Class roots: `.tile`, `.dashboard-tile`, `.tile-icon`, `.tile-label`, `.tile-meta`. -- Focus state: `.is-focused` scales tile and adds cyan glow outline. -- Press state: `.is-pressed` triggers pulse animation and hook events. - -### BackgroundLayer -- DOM root: `#nebula-background` with `.nebula-layer` children (`gradient`, `starfield`, `fog`, `vignette`). -- Purpose: animated nebula depth stack with subtle star motion and fog drift. -- Parallax token: `--bg-parallax-x` supports focus-driven depth shift. - -### FocusManager -- Implementation root: `src/core/nav.js`. -- Responsibilities: - - Maintain focused element and directional navigation. - - Apply `.is-focused` state and `aria-selected`. - - Publish focus telemetry events (`nebula-focus-change`). - - Update CSS vars (`--nebula-accent-line-x`, `--nebula-focus-strength`). - -## UI Hook Contract (No Audio Implementation) -- `window` `CustomEvent("nebula-ui-hook")` details: - - `type: "focus" | "move" | "accept" | "back"` - - Optional metadata (`target`, `action`, `focusKey`) -- Intended use: external UI audio/haptics bridge without coupling shell visuals to playback. diff --git a/Bigscreen/src/styles/theme.css b/Bigscreen/src/styles/theme.css deleted file mode 100644 index e90c6ba..0000000 --- a/Bigscreen/src/styles/theme.css +++ /dev/null @@ -1,58 +0,0 @@ -:root { - /* Core palette — solid, no heavy transparency */ - --nebula-color-bg: #070a14; - --nebula-color-bg-deep: #050810; - --nebula-color-sidebar: #0a0c18; - --nebula-color-panel: #0d1020; - --nebula-color-panel-alt: #111425; - --nebula-color-border: rgba(255, 255, 255, 0.08); - --nebula-color-border-mid: rgba(255, 255, 255, 0.14); - - --nebula-color-text: #f2f7ff; - --nebula-color-muted: #7a8fa8; - - /* Neon accents */ - --nebula-color-accent: #4fd8ff; - --nebula-color-purple: #9d4fe0; - --nebula-color-danger: #ff4f6b; - --nebula-color-success: #4fff88; - --nebula-color-focus: #4fd8ff; - - /* Controller button colours (flat solid) */ - --btn-a: #4caf50; - --btn-b: #f44336; - --btn-x: #2196f3; - --btn-y: #ff9800; - - --nebula-spacing-xs: 6px; - --nebula-spacing-sm: 10px; - --nebula-spacing-md: 16px; - --nebula-spacing-lg: 24px; - --nebula-spacing-xl: 36px; - - --nebula-radius-sm: 6px; - --nebula-radius-md: 10px; - --nebula-radius-lg: 16px; - --nebula-radius-pill: 999px; - - --nebula-type-body: 18px; - --nebula-type-title: 24px; - --nebula-type-display: 34px; - - --nebula-ease-standard: cubic-bezier(0.25, 0.1, 0.25, 1); - --nebula-ease-console: cubic-bezier(0.19, 0.82, 0.18, 1); - --nebula-ease-snap: cubic-bezier(0.32, 0.94, 0.18, 1); - - --nebula-duration-fast: 100ms; - --nebula-duration-nav: 160ms; - --nebula-duration-slow: 300ms; - - /* No glow/shadow — flat */ - --nebula-depth-shadow: 0 4px 16px rgba(0, 0, 0, 0.5); - - /* Overlay backdrop (power menu, etc.) — kept solid/dark */ - --nebula-color-overlay: rgba(5, 7, 18, 0.88); - - /* Backward-compat aliases */ - --nebula-color-panelAlt: #111425; -} diff --git a/Bigscreen/src/views/home/home.css b/Bigscreen/src/views/home/home.css deleted file mode 100644 index 4611b87..0000000 --- a/Bigscreen/src/views/home/home.css +++ /dev/null @@ -1,594 +0,0 @@ -/* ════════════════════════════════════════════════════════ - HOME VIEW — Sci-fi dashboard layout - ════════════════════════════════════════════════════════ */ - -.home-view { - display: flex; - flex-direction: column; - height: 100%; - padding: 0; - gap: 0; - overflow: hidden; -} - -/* ── Top status bar ──────────────────────────────────── */ -.home-topbar { - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px 24px 12px; - flex-shrink: 0; - border-bottom: 1px solid var(--nebula-color-border); -} - -.home-time { - font-size: clamp(36px, 4vw, 52px); - font-weight: 800; - letter-spacing: -0.03em; - color: var(--nebula-color-text); - line-height: 1; -} - -.home-status-icons { - display: flex; - align-items: center; - gap: 14px; - color: var(--nebula-color-muted); -} - -.home-status-icon { - display: flex; - align-items: center; - justify-content: center; -} - -/* ── Body: two columns ────────────────────────────────── */ -.home-body { - flex: 1; - display: grid; - grid-template-columns: 1fr 310px; - gap: 16px; - padding: 14px 20px 0; - min-height: 0; - overflow: hidden; -} - -/* ── Center column ───────────────────────────────────── */ -.home-center { - display: flex; - flex-direction: column; - gap: 12px; - min-height: 0; - overflow: hidden; -} - -/* ── Category tabs ───────────────────────────────────── */ -.home-tabs { - display: flex; - align-items: center; - gap: 20px; - flex-shrink: 0; - border-bottom: 1px solid var(--nebula-color-border); - padding-bottom: 10px; -} - -.home-tab { - background: none; - color: var(--nebula-color-muted); - font-size: 16px; - font-weight: 700; - padding: 0 2px 8px; - border: none; - border-bottom: 2px solid transparent; - border-radius: 0; - cursor: pointer; - transition: - color var(--nebula-duration-fast) var(--nebula-ease-standard), - border-bottom-color var(--nebula-duration-fast) var(--nebula-ease-standard); - transform: none; -} - -.home-tab.is-active { - color: var(--nebula-color-text); - border-bottom-color: var(--nebula-color-accent); -} - -.home-tab.is-focused { - color: var(--nebula-color-accent); - border-bottom-color: var(--nebula-color-accent); - transform: none; - border-radius: 0; -} - -.tab-hint { - margin-left: auto; - display: inline-flex; - align-items: center; - gap: 4px; - font-size: 13px; - color: var(--nebula-color-muted); -} - -/* ── Hero card ───────────────────────────────────────── */ -.hero-card { - flex: 1; - position: relative; - border-radius: var(--nebula-radius-lg); - overflow: hidden; - min-height: 0; - border: 2px solid var(--nebula-color-border); - transition: border-color var(--nebula-duration-nav) var(--nebula-ease-console); - transform: none; -} - -.hero-card.is-focused { - border-color: var(--nebula-color-accent); - transform: none; -} - -/* Game art layers */ -.hero-art { - position: absolute; - inset: 0; - overflow: hidden; -} - -.hero-art-bg { - position: absolute; - inset: 0; - background: - linear-gradient(165deg, - #0d0e00 0%, - #1a1600 20%, - #2a2200 40%, - #3a3000 60%, - rgba(180, 150, 0, 0.25) 80%, - rgba(220, 180, 0, 0.15) 100% - ); -} - -.hero-art.has-image .hero-art-bg { - background: - linear-gradient(165deg, rgba(7, 10, 20, 0.36), rgba(7, 10, 20, 0.86)), - var(--hero-image) center / cover no-repeat; -} - -.hero-art-mid { - position: absolute; - inset: 0; - background: - radial-gradient(ellipse at 65% 50%, rgba(255, 200, 0, 0.18) 0%, transparent 55%), - radial-gradient(ellipse at 30% 80%, rgba(0, 80, 180, 0.2) 0%, transparent 45%); -} - -/* Stylised "character" area */ -.hero-art-character { - position: absolute; - bottom: 0; - right: 10%; - width: 42%; - height: 90%; - background: - linear-gradient(180deg, - transparent 0%, - rgba(60, 50, 0, 0.4) 30%, - rgba(100, 90, 0, 0.6) 60%, - rgba(30, 25, 0, 0.9) 100% - ); - clip-path: polygon(15% 0%, 85% 0%, 100% 100%, 0% 100%); -} - -.hero-title-watermark { - position: absolute; - bottom: 110px; - right: 5%; - font-size: clamp(32px, 5vw, 64px); - font-weight: 900; - color: rgba(255, 220, 0, 0.85); - letter-spacing: -0.03em; - line-height: 0.95; - text-align: right; - text-transform: uppercase; - pointer-events: none; - font-style: italic; -} - -.hero-art.has-image .hero-art-mid, -.hero-art.has-image .hero-art-character { - display: none; -} - -/* Controller overlay */ -.hero-ctrl-overlay { - position: absolute; - top: 20px; - left: 20px; - display: flex; - gap: 14px; - align-items: center; - opacity: 0.55; -} - -.ctrl-glyph { - display: block; -} - -.hero-l-badge { - width: 22px; - height: 22px; - border-radius: var(--nebula-radius-sm); - background: rgba(255, 255, 255, 0.15); - border: 1px solid rgba(255, 255, 255, 0.25); - display: flex; - align-items: center; - justify-content: center; - font-size: 11px; - font-weight: 800; - color: rgba(255, 255, 255, 0.7); -} - -/* Gradient overlay + info */ -.hero-overlay { - position: absolute; - inset: 0; - background: linear-gradient( - to top, - rgba(7, 10, 20, 0.98) 0%, - rgba(7, 10, 20, 0.7) 36%, - transparent 65% - ); - display: flex; - flex-direction: column; - justify-content: flex-end; - padding: 20px 24px; -} - -.hero-info { - display: flex; - flex-direction: column; - gap: 14px; -} - -.hero-game-title { - margin: 0; - font-size: clamp(26px, 3.2vw, 42px); - font-weight: 800; - letter-spacing: -0.02em; - color: #fff; - line-height: 1.1; -} - -.hero-game-meta { - margin: 0; - color: var(--nebula-color-muted); - font-size: 14px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.08em; -} - -.hero-actions { - display: flex; - gap: 10px; - flex-wrap: wrap; -} - -.hero-btn { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 9px 18px; - border-radius: var(--nebula-radius-sm); - font-size: 14px; - font-weight: 700; - cursor: pointer; - background: rgba(255, 255, 255, 0.08); - border: 1.5px solid rgba(255, 255, 255, 0.2); - color: #fff; - transition: - background var(--nebula-duration-fast) var(--nebula-ease-standard), - border-color var(--nebula-duration-fast) var(--nebula-ease-standard), - transform var(--nebula-duration-fast) var(--nebula-ease-console); - transform: none; -} - -.hero-btn-primary { - background: var(--nebula-color-accent); - border-color: transparent; - color: #070a14; -} - -.hero-btn.is-focused { - border-color: var(--nebula-color-accent); - transform: translateY(-1px); -} - -.hero-btn-primary.is-focused { - border-color: #fff; - transform: translateY(-1px); -} - -.hero-dots { - display: flex; - align-items: center; - gap: 6px; - margin-top: 10px; -} - -.hero-dot { - width: 6px; - height: 6px; - border-radius: 50%; - background: rgba(255, 255, 255, 0.28); - transition: all var(--nebula-duration-fast) var(--nebula-ease-standard); -} - -.hero-dot.is-active { - width: 22px; - border-radius: 3px; - background: var(--nebula-color-accent); -} - -/* ── Featured strip ──────────────────────────────────── */ -.featured-strip { - flex-shrink: 0; - padding-bottom: 12px; -} - -.section-label { - font-size: 12px; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.12em; - color: var(--nebula-color-muted); - margin: 0 0 10px; -} - -.featured-row { - display: flex; - gap: 10px; -} - -.featured-thumb { - flex: 1; - height: 68px; - border-radius: var(--nebula-radius-md); - background: - linear-gradient(135deg, rgba(79, 216, 255, 0.18), rgba(125, 89, 255, 0.18)), - linear-gradient(135deg, var(--thumb-a, #1a1a2e), var(--thumb-b, #2a1050)); - border: 2px solid var(--nebula-color-border); - cursor: pointer; - transition: border-color var(--nebula-duration-fast); - overflow: hidden; - padding: 0; -} - -.featured-thumb.has-image { - background: - linear-gradient(180deg, rgba(7, 10, 20, 0), rgba(7, 10, 20, 0.64)), - var(--featured-image) center / cover no-repeat; -} - -.featured-thumb.is-focused { - border-color: var(--nebula-color-accent); - transform: translateY(-2px); -} - -/* ── Right panel ─────────────────────────────────────── */ -.home-right-panel { - display: flex; - flex-direction: column; - gap: 12px; - overflow: hidden; - min-height: 0; - padding-bottom: 12px; -} - -.panel-header-row { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 10px; -} - -.panel-heading { - font-size: 15px; - font-weight: 700; - color: var(--nebula-color-text); - margin: 0; - text-transform: uppercase; - letter-spacing: 0.06em; -} - -.panel-heading-sm { - font-size: 13px; - font-weight: 700; - color: var(--nebula-color-text); - margin: 0 0 8px; - text-transform: uppercase; - letter-spacing: 0.06em; -} - -/* ── Quick launch grid ───────────────────────────────── */ -.quick-launch { - flex-shrink: 0; -} - -.quick-grid { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 8px; -} - -.quick-tile { - position: relative; - border-radius: var(--nebula-radius-md); - overflow: hidden; - background: var(--nebula-color-panel); - border: 2px solid var(--nebula-color-border); - cursor: pointer; - padding: 0; - text-align: left; - color: var(--nebula-color-text); - transition: - border-color var(--nebula-duration-fast) var(--nebula-ease-console), - transform var(--nebula-duration-fast) var(--nebula-ease-console); - transform: translateZ(0); -} - -.quick-tile.is-focused { - border-color: var(--nebula-color-accent); - transform: scale(1.04) translateZ(0); -} - -.quick-tile-art { - height: 70px; - display: grid; - place-items: center; - background: - radial-gradient(circle at top left, rgba(79, 216, 255, 0.22), transparent 46%), - linear-gradient(135deg, var(--ta, #1a1a2e), var(--tb, #2a2050)); - position: relative; - overflow: hidden; -} - -.quick-tile-art.has-image { - background: - linear-gradient(180deg, rgba(7, 10, 20, 0), rgba(7, 10, 20, 0.48)), - var(--quick-image) center / cover no-repeat; -} - -.quick-tile-initials { - display: grid; - place-items: center; - width: 36px; - height: 36px; - border-radius: 12px; - background: rgba(0, 0, 0, 0.24); - color: var(--nebula-color-text); - font-size: 13px; - font-weight: 900; - letter-spacing: 0.04em; -} - -.quick-tile-art.has-image .quick-tile-initials { - display: none; -} - -.tile-badge { - position: absolute; - bottom: 4px; - right: 4px; - font-size: 12px; - line-height: 1; -} - -.quick-tile-footer { - padding: 6px 8px 7px; -} - -.quick-tile-name { - margin: 0; - font-size: 11px; - font-weight: 700; - color: var(--nebula-color-text); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - line-height: 1.3; -} - -.quick-tile-meta { - margin: 3px 0 0; - font-size: 10px; - color: var(--nebula-color-muted); - line-height: 1.3; -} - -/* Solid progress bar */ -.quick-tile-bar { - position: relative; - padding-left: 0; -} - -.quick-tile-bar::before { - content: ""; - display: block; - height: 2px; - background: var(--nebula-color-border); - border-radius: 1px; - margin-bottom: 3px; -} - -.quick-tile-bar::after { - content: ""; - display: block; - position: absolute; - top: 0; - left: 0; - width: var(--pct, 0%); - height: 2px; - background: var(--nebula-color-accent); - border-radius: 1px; -} - -/* ── Bottom side panels ──────────────────────────────── */ -.side-bottom-panels { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 10px; - flex-shrink: 0; -} - -.friends-panel, -.activity-panel { - background: var(--nebula-color-panel); - border: 1px solid var(--nebula-color-border); - border-radius: var(--nebula-radius-md); - padding: 12px; -} - -.friends-avatars { - display: flex; - gap: 6px; - flex-wrap: wrap; -} - -.home-placeholder-copy { - margin: 0; - color: var(--nebula-color-muted); - font-size: 11px; - line-height: 1.4; -} - -.friend-avatar { - width: 28px; - height: 28px; - border-radius: 50%; - background: var(--fc, var(--nebula-color-accent)); - opacity: 0.85; - border: 1.5px solid rgba(255, 255, 255, 0.12); -} - -.activity-row { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 4px; -} - -.activity-label { - font-size: 17px; - font-weight: 800; - color: var(--nebula-color-text); - line-height: 1; -} - -.activity-hint { - margin: 0; - font-size: 11px; - color: var(--nebula-color-muted); - display: inline-flex; - align-items: center; - gap: 4px; -} diff --git a/Bigscreen/src/views/home/home.html b/Bigscreen/src/views/home/home.html deleted file mode 100644 index ad2deec..0000000 --- a/Bigscreen/src/views/home/home.html +++ /dev/null @@ -1,60 +0,0 @@ -
    -
    -
    -

    Nebula OS

    -
    - -

    --:--

    -
    -
    -
    -
    - -
    -

    Dashboard

    -

    Jump back in

    -
    - -
    - - - - -
    -
    diff --git a/Bigscreen/src/views/home/home.js b/Bigscreen/src/views/home/home.js deleted file mode 100644 index 7acdfa4..0000000 --- a/Bigscreen/src/views/home/home.js +++ /dev/null @@ -1,456 +0,0 @@ -const getTauriCore = async () => { - const globalCore = window.__TAURI__?.core; - if (typeof globalCore?.invoke === "function") { - return { - invoke: globalCore.invoke, - convertFileSrc: typeof globalCore.convertFileSrc === "function" ? globalCore.convertFileSrc : null, - }; - } - - try { - const tauriCore = await import("@tauri-apps/api/core"); - return { - invoke: typeof tauriCore.invoke === "function" ? tauriCore.invoke : null, - convertFileSrc: typeof tauriCore.convertFileSrc === "function" ? tauriCore.convertFileSrc : null, - }; - } catch (_error) { - return { invoke: null, convertFileSrc: null }; - } -}; - -const sourceLabel = (source) => { - const labels = { - steam: "Steam", - epic: "Epic Games", - gog: "GOG", - local: "Local", - unknown: "Unknown", - }; - return labels[source] ?? "Library"; -}; - -const gameTitle = (game) => game?.userTitle || game?.title || "No games scanned yet"; - -const gameArtUrl = (game, convertFileSrc, key = "heroImage") => { - const path = game?.[key] || game?.coverImage || game?.iconImage; - if (!path || !convertFileSrc) return ""; - return convertFileSrc(path); -}; - -const initialsForTitle = (title) => - title - .split(/\s+/) - .filter(Boolean) - .slice(0, 2) - .map((part) => part[0]?.toUpperCase()) - .join("") || "OS"; - -const HOME_TEMPLATE = ` -
    - - -
    - --:-- -
    - - - - - - - - - -
    -
    - - -
    - - -
    - - - - - -
    - - - -
    -
    -

    Scan your device to populate Home

    -

    No games scanned yet

    -
    - - - -
    -
    - -
    -
    - - - -
    - - - -
    -
    -`; - -export const createHomeView = ({ state, renderView, openPowerMenu }) => ({ - id: "home", - render: () => HOME_TEMPLATE, - mount: async () => { - const view = document.querySelector("[data-view='home']"); - if (!view) return; - - view.querySelectorAll(".home-tab").forEach((tab) => { - tab.addEventListener("click", () => { - view.querySelectorAll(".home-tab").forEach((t) => { - t.classList.remove("is-active"); - t.setAttribute("aria-selected", "false"); - }); - tab.classList.add("is-active"); - tab.setAttribute("aria-selected", "true"); - }); - }); - - await hydrateHomeLibrary(view); - }, - getNavigationContract: () => { - const root = document.querySelector("[data-view='home']"); - return { - focusRoot: root, - defaultFocus: root?.querySelector("[data-focus-key='btn-continue']") ?? null, - layout: { type: "grid", cols: 6, rows: 4 }, - hintsTemplate: "#global-hints-template", - nebulaNavigation: state.nebula.navigation, - useNebulaNavigation: false, - onAccept: (element) => { - if (!element) return; - const target = element.dataset.target; - const focusKey = element.dataset.focusKey; - - if (target === "power") { - openPowerMenu(); - return; - } - if (target) { - state.activeView = target; - renderView(target); - return; - } - - if (element.dataset.gameId || element.dataset.quickSlot || element.dataset.featuredSlot) { - state.activeView = "library"; - renderView("library"); - return; - } - - if (focusKey === "tab-now-playing" || focusKey === "tab-featured") { - element.click(); - } - }, - onBack: () => { - state.locked = true; - state.activeView = "lock"; - renderView("lock"); - }, - onMenu: () => {}, - }; - }, -}); - -const hydrateHomeLibrary = async (view) => { - const { invoke, convertFileSrc } = await getTauriCore(); - if (!invoke) { - setEmptyHomeState(view, "Run NebulaOS with Tauri to load your library."); - return; - } - - try { - const games = await invoke("list_library_games"); - if (!Array.isArray(games) || games.length === 0) { - setEmptyHomeState(view, "Open Library and scan your device to populate Home."); - return; - } - - const sortedGames = [...games].sort((left, right) => { - if (left.userFavourite !== right.userFavourite) { - return left.userFavourite ? -1 : 1; - } - return gameTitle(left).localeCompare(gameTitle(right)); - }); - - renderHeroGame(view, sortedGames[0], convertFileSrc); - renderQuickLaunch(view, sortedGames.slice(0, 6), convertFileSrc); - renderFeatured(view, sortedGames.slice(0, 4), convertFileSrc); - renderActivity(view, sortedGames); - } catch (error) { - setEmptyHomeState(view, String(error)); - } -}; - -const setEmptyHomeState = (view, message) => { - view.querySelector("[data-hero-title]").textContent = "No games scanned yet"; - view.querySelector("[data-hero-meta]").textContent = message; - view.querySelector("[data-home-library-count]").textContent = "0 Games"; - view.querySelector("[data-home-activity-hint]").textContent = "Scan from Library to populate Home."; -}; - -const renderHeroGame = (view, game, convertFileSrc) => { - const title = gameTitle(game); - const hero = view.querySelector("[data-home-hero]"); - const heroArt = view.querySelector("[data-hero-art]"); - const titleNode = view.querySelector("[data-hero-title]"); - const metaNode = view.querySelector("[data-hero-meta]"); - const watermark = view.querySelector("[data-hero-watermark]"); - const imageUrl = gameArtUrl(game, convertFileSrc, "heroImage"); - - hero?.setAttribute("aria-label", `Featured game: ${title}`); - titleNode.textContent = title; - metaNode.textContent = `${sourceLabel(game.platformSource)} · ${game.metadataStatus === "needs_review" ? "Needs review" : "Ready"}`; - watermark.textContent = title.toUpperCase(); - - if (imageUrl) { - heroArt.style.setProperty("--hero-image", `url("${imageUrl}")`); - heroArt.classList.add("has-image"); - } else { - heroArt.style.removeProperty("--hero-image"); - heroArt.classList.remove("has-image"); - } -}; - -const renderQuickLaunch = (view, games, convertFileSrc) => { - view.querySelectorAll("[data-quick-slot]").forEach((tile, index) => { - const game = games[index]; - const art = tile.querySelector(".quick-tile-art"); - const initials = tile.querySelector(".quick-tile-initials"); - const name = tile.querySelector(".quick-tile-name"); - const meta = tile.querySelector(".quick-tile-meta"); - - if (!game) { - tile.removeAttribute("data-game-id"); - tile.setAttribute("aria-label", "Empty library slot"); - art.style.removeProperty("--quick-image"); - art.classList.remove("has-image"); - initials.textContent = "OS"; - name.textContent = "Empty Slot"; - meta.textContent = "Scan for more games"; - return; - } - - const title = gameTitle(game); - const imageUrl = gameArtUrl(game, convertFileSrc, "coverImage"); - tile.dataset.gameId = String(game.id); - tile.setAttribute("aria-label", title); - name.textContent = title; - meta.textContent = sourceLabel(game.platformSource); - initials.textContent = initialsForTitle(title); - - if (imageUrl) { - art.style.setProperty("--quick-image", `url("${imageUrl}")`); - art.classList.add("has-image"); - } else { - art.style.removeProperty("--quick-image"); - art.classList.remove("has-image"); - } - }); -}; - -const renderFeatured = (view, games, convertFileSrc) => { - view.querySelectorAll("[data-featured-slot]").forEach((tile, index) => { - const game = games[index]; - if (!game) { - tile.removeAttribute("data-game-id"); - tile.setAttribute("aria-label", "Empty featured slot"); - tile.style.removeProperty("--featured-image"); - tile.classList.remove("has-image"); - return; - } - - const title = gameTitle(game); - const imageUrl = gameArtUrl(game, convertFileSrc, "heroImage"); - tile.dataset.gameId = String(game.id); - tile.setAttribute("aria-label", title); - if (imageUrl) { - tile.style.setProperty("--featured-image", `url("${imageUrl}")`); - tile.classList.add("has-image"); - } else { - tile.style.removeProperty("--featured-image"); - tile.classList.remove("has-image"); - } - }); -}; - -const renderActivity = (view, games) => { - const count = games.length; - view.querySelector("[data-home-library-count]").textContent = `${count} ${count === 1 ? "Game" : "Games"}`; - view.querySelector("[data-home-activity-hint]").textContent = "Home is showing your scanned local library."; -}; diff --git a/Bigscreen/src/views/library/library.css b/Bigscreen/src/views/library/library.css deleted file mode 100644 index 277aaf4..0000000 --- a/Bigscreen/src/views/library/library.css +++ /dev/null @@ -1,901 +0,0 @@ -.library-view { - --library-glow: rgba(79, 216, 255, 0.42); - height: 100%; - overflow: hidden; - background: - radial-gradient(circle at 18% 12%, rgba(79, 216, 255, 0.12), transparent 34%), - radial-gradient(circle at 78% 0%, rgba(157, 79, 224, 0.14), transparent 36%); -} - -.library-topbar { - display: flex; - align-items: center; - justify-content: space-between; - flex-shrink: 0; - padding: 18px 26px 12px; - border-bottom: 1px solid rgba(79, 216, 255, 0.14); -} - -.library-eyebrow, -.library-section-kicker { - margin: 0; - color: var(--nebula-color-accent); - font-size: 12px; - font-weight: 800; - letter-spacing: 0.16em; - text-transform: uppercase; -} - -.library-title { - margin: 4px 0 0; - font-size: clamp(38px, 4.5vw, 62px); - line-height: 1; - letter-spacing: -0.04em; -} - -.library-topbar-right, -.library-actions, -.library-system-status { - display: flex; - align-items: center; - gap: 12px; -} - -.library-system-status { - padding: 10px 14px; - border: 1px solid rgba(255, 255, 255, 0.11); - border-radius: var(--nebula-radius-pill); - background: rgba(10, 16, 34, 0.74); - color: var(--nebula-color-muted); - font-size: 13px; -} - -.library-system-status strong { - color: var(--nebula-color-text); - font-size: 16px; -} - -.library-status-dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--nebula-color-success); - box-shadow: 0 0 14px rgba(79, 255, 136, 0.55); -} - -.library-action, -.library-category-tab, -.library-genre-tab, -.library-filter-chip, -.library-sort-option, -.library-detail-button { - color: var(--nebula-color-text); - border: 2px solid rgba(255, 255, 255, 0.09); - background: rgba(15, 23, 46, 0.82); - box-shadow: none; -} - -.library-action { - min-width: 132px; - padding: 12px 18px; - border-radius: var(--nebula-radius-pill); - font-weight: 800; -} - -.library-console { - flex: 1; - min-height: 0; - padding: 16px 20px 0; -} - -.library-main { - display: flex; - flex-direction: column; - gap: 14px; - height: 100%; - min-height: 0; -} - -.library-category-tabs, -.library-genre-tabs { - display: flex; - gap: 12px; - flex-shrink: 0; -} - -.library-category-tab { - min-width: 210px; - padding: 18px 24px; - border-radius: var(--nebula-radius-lg); - font-size: clamp(20px, 2vw, 28px); - font-weight: 900; - text-align: left; -} - -.library-category-tab.is-active, -.library-genre-tab.is-active, -.library-filter-chip.is-active, -.library-sort-option.is-active { - color: #04101d; - background: linear-gradient(135deg, var(--nebula-color-accent), #78f0ff); - border-color: rgba(255, 255, 255, 0.7); -} - -.library-genre-tab { - padding: 12px 18px; - border-radius: var(--nebula-radius-pill); - font-size: 15px; - font-weight: 800; -} - -.library-content-row { - flex: 1; - min-height: 0; - display: grid; - grid-template-columns: minmax(250px, 300px) 1fr; - gap: 16px; -} - -.library-sidebar { - display: flex; - flex-direction: column; - gap: 12px; - min-height: 0; -} - -.library-summary-card, -.library-filter-card, -.library-grid-region, -.library-details-card, -.library-sort-card, -.library-empty-card { - border: 1px solid rgba(79, 216, 255, 0.13); - background: - linear-gradient(180deg, rgba(17, 24, 48, 0.88), rgba(8, 12, 27, 0.92)), - rgba(10, 16, 34, 0.86); - border-radius: var(--nebula-radius-lg); - box-shadow: 0 18px 50px rgba(0, 0, 0, 0.36); -} - -.library-summary-card, -.library-filter-card { - padding: 16px; -} - -.library-summary-total { - display: flex; - align-items: end; - gap: 10px; - margin: 12px 0; -} - -.library-summary-total strong { - font-size: 48px; - line-height: 0.9; -} - -.library-summary-total span, -.library-sync-status p, -.library-status-copy { - margin: 0; - color: var(--nebula-color-muted); -} - -.library-stat-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 8px; -} - -.library-stat-grid span, -.library-sync-status { - padding: 10px; - border-radius: var(--nebula-radius-md); - background: rgba(255, 255, 255, 0.045); -} - -.library-stat-grid strong { - color: var(--nebula-color-text); -} - -.library-sync-status { - display: grid; - grid-template-columns: auto 1fr; - gap: 10px; - margin-top: 12px; -} - -.library-filter-group { - display: flex; - flex-direction: column; - gap: 8px; - margin-top: 12px; -} - -.library-filter-label { - color: var(--nebula-color-muted); - font-size: 12px; - font-weight: 800; - text-transform: uppercase; - letter-spacing: 0.12em; -} - -.library-filter-chip { - width: 100%; - padding: 11px 12px; - border-radius: var(--nebula-radius-md); - text-align: left; - font-weight: 800; -} - -.library-grid-region { - min-width: 0; - min-height: 0; - padding: 16px; - display: flex; - flex-direction: column; -} - -.library-grid-header { - display: flex; - align-items: end; - justify-content: space-between; - gap: 16px; - flex-shrink: 0; - margin-bottom: 14px; -} - -.library-grid-header h2 { - margin: 4px 0 0; - font-size: 28px; -} - -.library-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); - gap: 16px; - overflow: auto; - padding: 4px 8px 24px; - scrollbar-width: thin; -} - -.library-card { - min-height: 330px; - padding: 0; - display: flex; - flex-direction: column; - border-radius: 22px; - border: 2px solid rgba(255, 255, 255, 0.08); - background: rgba(8, 12, 27, 0.94); - color: var(--nebula-color-text); - text-align: left; - box-shadow: 0 16px 34px rgba(0, 0, 0, 0.34); - transform-origin: center; -} - -.library-card.is-focused { - border-color: var(--nebula-color-accent); - box-shadow: - 0 0 0 2px rgba(79, 216, 255, 0.16), - 0 0 34px var(--library-glow), - 0 22px 50px rgba(0, 0, 0, 0.54); - transform: scale(1.045) translateZ(0); - z-index: 3; -} - -.library-card-art { - position: relative; - min-height: 178px; - display: grid; - place-items: center; - overflow: hidden; - border-radius: 20px 20px 0 0; - background: - radial-gradient(circle at 18% 16%, color-mix(in srgb, var(--library-accent, #4fd8ff), transparent 24%), transparent 42%), - linear-gradient(135deg, rgba(79, 216, 255, 0.16), rgba(157, 79, 224, 0.18)), - #10182f; -} - -.library-card-art.has-image { - background: - linear-gradient(180deg, rgba(7, 10, 20, 0), rgba(7, 10, 20, 0.58)), - var(--library-art) center / cover no-repeat; -} - -.library-card-art span, -.library-details-art span { - display: grid; - place-items: center; - width: 78px; - height: 78px; - border-radius: 24px; - background: rgba(0, 0, 0, 0.24); - border: 1px solid rgba(255, 255, 255, 0.16); - font-size: 26px; - font-weight: 950; - letter-spacing: 0.08em; -} - -.library-card-art.has-image span, -.library-details-art.has-image span { - display: none; -} - -.library-verified-badge { - position: absolute; - right: 10px; - top: 10px; - padding: 5px 8px; - border-radius: var(--nebula-radius-pill); - background: rgba(79, 255, 136, 0.16); - border: 1px solid rgba(79, 255, 136, 0.42); - color: #9effbc; - font-size: 11px; - font-style: normal; - font-weight: 900; - text-transform: uppercase; -} - -.library-card-body { - display: flex; - flex-direction: column; - gap: 8px; - padding: 14px; -} - -.library-card-title-row { - display: flex; - align-items: start; - justify-content: space-between; - gap: 10px; -} - -.library-card-title-row h3 { - margin: 0; - font-size: 20px; - line-height: 1.1; -} - -.library-card-title-row span { - flex-shrink: 0; - color: var(--nebula-color-accent); - font-size: 11px; - font-weight: 900; - text-transform: uppercase; -} - -.library-card-body p, -.library-card-meta-row, -.library-card-achievements { - margin: 0; - color: var(--nebula-color-muted); - font-size: 13px; -} - -.library-card-meta-row { - display: flex; - justify-content: space-between; - gap: 10px; -} - -.library-card-focus-copy, -.library-card-achievements { - display: none; -} - -.library-card.is-focused .library-card-focus-copy, -.library-card.is-focused .library-card-achievements { - display: block; -} - -.library-empty-card { - grid-column: 1 / -1; - padding: 28px; -} - -.library-empty-card h3 { - margin: 8px 0; - font-size: 28px; -} - -.library-details-panel, -.library-sort-panel { - display: none; -} - -.library-details-panel.is-open, -.library-sort-panel.is-open { - position: fixed; - inset: 0; - z-index: 30; - display: block; -} - -.library-details-backdrop, -.library-sort-backdrop { - position: absolute; - inset: 0; - background: rgba(3, 6, 14, 0.72); -} - -.library-details-card { - position: absolute; - top: 30px; - right: 30px; - bottom: 30px; - width: min(520px, calc(100vw - 140px)); - overflow: hidden; - display: flex; - flex-direction: column; -} - -.library-details-art { - min-height: 210px; - display: grid; - place-items: center; - background: - radial-gradient(circle at 20% 20%, color-mix(in srgb, var(--library-accent, #4fd8ff), transparent 24%), transparent 44%), - linear-gradient(135deg, rgba(79, 216, 255, 0.18), rgba(157, 79, 224, 0.2)); -} - -.library-details-art.has-image { - background: - linear-gradient(180deg, rgba(7, 10, 20, 0), rgba(7, 10, 20, 0.78)), - var(--library-detail-art) center / cover no-repeat; -} - -.library-details-body { - padding: 22px; - overflow: auto; -} - -.library-details-body h2 { - margin: 8px 0; - font-size: 34px; - line-height: 1; -} - -.library-details-body p { - color: var(--nebula-color-muted); - line-height: 1.5; -} - -.library-details-list { - display: grid; - gap: 10px; - margin: 16px 0; -} - -.library-details-list div { - padding: 10px; - border-radius: var(--nebula-radius-md); - background: rgba(255, 255, 255, 0.05); -} - -.library-details-list dt { - color: var(--nebula-color-muted); - font-size: 11px; - font-weight: 900; - text-transform: uppercase; -} - -.library-details-list dd { - margin: 3px 0 0; -} - -.library-details-actions, -.library-sort-options { - display: grid; - gap: 10px; -} - -.library-detail-button, -.library-sort-option { - padding: 14px 16px; - border-radius: var(--nebula-radius-md); - text-align: left; - font-weight: 900; -} - -.library-sort-card { - position: absolute; - right: 34px; - bottom: 58px; - width: min(360px, calc(100vw - 130px)); - padding: 20px; -} - -.library-sort-card h2 { - margin: 8px 0; - font-size: 30px; -} - -.library-sort-card p { - color: var(--nebula-color-muted); -} - -.library-action.is-focused, -.library-category-tab.is-focused, -.library-genre-tab.is-focused, -.library-filter-chip.is-focused, -.library-sort-option.is-focused, -.library-detail-button.is-focused { - border-color: var(--nebula-color-accent); - box-shadow: - 0 0 0 2px rgba(79, 216, 255, 0.14), - 0 0 26px rgba(79, 216, 255, 0.34); - transform: scale(1.035) translateZ(0); -} - -@media (max-width: 1180px) { - .library-content-row { - grid-template-columns: 240px 1fr; - } - - .library-category-tab { - min-width: 170px; - } - - .library-grid { - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - } -} - -/* Game Detail View Styles */ -.game-detail-view { - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - overflow: hidden; - background: linear-gradient(180deg, rgba(17, 24, 48, 0.95), rgba(8, 12, 27, 0.98)); -} - -.game-detail-header-image { - position: absolute; - top: 0; - left: 0; - right: 0; - height: 40%; - background-size: cover; - background-position: center; - background-color: rgba(79, 216, 255, 0.08); - z-index: 1; -} - -.game-detail-header-overlay { - position: absolute; - inset: 0; - background: linear-gradient(180deg, rgba(7, 10, 20, 0.3), rgba(7, 10, 20, 0.95)); - z-index: 1; -} - -.game-detail-content { - position: relative; - z-index: 2; - flex: 1; - overflow-y: auto; - display: flex; - flex-direction: column; -} - -.game-detail-header { - padding: 28px 40px 20px; - display: grid; - grid-template-columns: 1fr auto; - gap: 40px; - align-items: start; -} - -.game-detail-title-section h1 { - margin: 0; - font-size: 48px; - line-height: 1.1; - letter-spacing: -0.02em; - max-width: 600px; -} - -.game-detail-title-section .library-section-kicker { - display: block; - margin-bottom: 12px; -} - -.game-detail-meta { - display: flex; - gap: 24px; - margin-top: 12px; - font-size: 14px; - color: var(--nebula-color-muted); - flex-wrap: wrap; -} - -.game-detail-play-button-container { - display: flex; - gap: 12px; -} - -.game-detail-play-button { - min-width: 160px; - padding: 16px 32px; - font-size: 18px; - font-weight: 900; - text-transform: uppercase; - letter-spacing: 0.08em; - border: none; - border-radius: 12px; - background: linear-gradient(135deg, var(--nebula-color-accent), #78f0ff); - color: #04101d; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - gap: 8px; - box-shadow: 0 12px 32px rgba(79, 216, 255, 0.34); - transition: all 0.2s ease; - white-space: nowrap; -} - -.game-detail-play-button .play-icon { - font-size: 20px; -} - -.game-detail-play-button.is-focused { - transform: scale(1.08); - box-shadow: - 0 0 0 2px rgba(79, 216, 255, 0.3), - 0 16px 40px rgba(79, 216, 255, 0.42); -} - -.game-detail-main { - display: grid; - grid-template-columns: 1fr 320px; - gap: 40px; - padding: 0 40px 40px; -} - -.game-detail-left-column { - display: flex; - flex-direction: column; - gap: 32px; - max-width: 700px; -} - -.game-detail-right-column { - display: flex; - flex-direction: column; - gap: 24px; -} - -.game-detail-section-title { - margin: 0 0 16px 0; - font-size: 18px; - font-weight: 900; - text-transform: uppercase; - letter-spacing: 0.08em; - color: var(--nebula-color-accent); -} - -.game-detail-description { - display: flex; - flex-direction: column; - gap: 12px; -} - -.game-detail-description p { - margin: 0; - color: var(--nebula-color-muted); - line-height: 1.6; -} - -.game-detail-description .long-description { - color: var(--nebula-color-muted); - opacity: 0.85; -} - -.game-detail-info { - display: flex; - flex-direction: column; - gap: 12px; -} - -.game-detail-info-list { - display: grid; - gap: 12px; -} - -.game-detail-info-list div { - display: grid; - grid-template-columns: 120px 1fr; - gap: 12px; - padding: 12px; - border-radius: var(--nebula-radius-md); - background: rgba(79, 216, 255, 0.05); - border: 1px solid rgba(79, 216, 255, 0.08); -} - -.game-detail-info-list dt { - font-size: 12px; - font-weight: 900; - text-transform: uppercase; - letter-spacing: 0.08em; - color: var(--nebula-color-accent); -} - -.game-detail-info-list dd { - margin: 0; - color: var(--nebula-color-muted); - word-break: break-word; -} - -/* Screenshots Section */ -.game-detail-screenshots { - display: flex; - flex-direction: column; - gap: 16px; -} - -.screenshots-carousel { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 12px; -} - -.screenshot-item { - aspect-ratio: 16 / 9; - border-radius: 12px; - overflow: hidden; - border: 1px solid rgba(79, 216, 255, 0.12); - background: rgba(10, 16, 34, 0.6); -} - -.screenshot-item img { - width: 100%; - height: 100%; - object-fit: cover; -} - -/* Achievements Section */ -.game-detail-achievements { - display: flex; - flex-direction: column; - gap: 16px; - padding: 16px; - border-radius: var(--nebula-radius-lg); - background: linear-gradient(135deg, rgba(79, 216, 255, 0.08), rgba(157, 79, 224, 0.08)); - border: 1px solid rgba(79, 216, 255, 0.12); -} - -.achievements-container { - display: flex; - flex-direction: column; - gap: 12px; -} - -.achievements-progress { - display: grid; - grid-template-columns: auto 1fr auto; - gap: 12px; - align-items: center; -} - -.achievement-stat { - display: flex; - flex-direction: column; - align-items: center; - text-align: center; -} - -.achievement-number { - font-size: 20px; - font-weight: 900; - color: var(--nebula-color-accent); -} - -.achievement-label { - font-size: 11px; - font-weight: 800; - text-transform: uppercase; - color: var(--nebula-color-muted); - margin-top: 4px; -} - -.achievement-bar { - height: 8px; - border-radius: 4px; - background: rgba(255, 255, 255, 0.08); - overflow: hidden; -} - -.achievement-bar-fill { - height: 100%; - background: linear-gradient(90deg, var(--nebula-color-accent), #78f0ff); - border-radius: 4px; -} - -/* Features and Badges */ -.game-detail-features { - display: flex; - flex-direction: column; - gap: 12px; - padding: 16px; - border-radius: var(--nebula-radius-lg); - background: rgba(255, 255, 255, 0.04); - border: 1px solid rgba(255, 255, 255, 0.07); -} - -.features-grid { - display: grid; - gap: 10px; -} - -.feature-badge { - display: inline-block; - padding: 8px 12px; - border-radius: 8px; - font-size: 12px; - font-weight: 800; - text-transform: uppercase; - letter-spacing: 0.04em; - background: rgba(79, 216, 255, 0.14); - color: var(--nebula-color-accent); - border: 1px solid rgba(79, 216, 255, 0.22); -} - -.feature-badge.verified { - background: rgba(79, 255, 136, 0.12); - color: #9effbc; - border-color: rgba(79, 255, 136, 0.24); -} - -/* Actions */ -.game-detail-actions { - display: grid; - gap: 10px; - padding: 16px; - border-radius: var(--nebula-radius-lg); - background: rgba(255, 255, 255, 0.02); - border: 1px solid rgba(255, 255, 255, 0.05); -} - -.library-detail-button { - padding: 12px 16px; - border-radius: var(--nebula-radius-md); - font-weight: 900; - font-size: 14px; - text-align: left; -} - -.library-detail-button.is-focused { - border-color: var(--nebula-color-accent); - box-shadow: - 0 0 0 2px rgba(79, 216, 255, 0.14), - 0 0 26px rgba(79, 216, 255, 0.34); - transform: scale(1.03) translateZ(0); -} - -/* Scrollbar styling for content area */ -.game-detail-content::-webkit-scrollbar { - width: 8px; -} - -.game-detail-content::-webkit-scrollbar-track { - background: transparent; -} - -.game-detail-content::-webkit-scrollbar-thumb { - background: rgba(79, 216, 255, 0.24); - border-radius: 4px; -} - -.game-detail-content::-webkit-scrollbar-thumb:hover { - background: rgba(79, 216, 255, 0.36); -} - -/* Update the library-details-panel for full-screen mode */ -.library-details-panel.is-open { - display: flex; -} - -.game-detail-view { - z-index: 31; -} diff --git a/Bigscreen/src/views/library/library.html b/Bigscreen/src/views/library/library.html deleted file mode 100644 index 53d1747..0000000 --- a/Bigscreen/src/views/library/library.html +++ /dev/null @@ -1,22 +0,0 @@ -
    -
    -
    -

    Nebula OS

    -
    - -

    --:--

    -
    -
    -
    -
    -
    -
    -

    Nebula App

    -

    Library

    -
    -
    -
    - -

    Library integration stub for v0 shell navigation.

    -
    -
    diff --git a/Bigscreen/src/views/library/library.js b/Bigscreen/src/views/library/library.js deleted file mode 100644 index 5871f75..0000000 --- a/Bigscreen/src/views/library/library.js +++ /dev/null @@ -1,292 +0,0 @@ -import { filterLibraryItems } from "./libraryFilters.js"; -import { - GENRE_FILTERS, - LIBRARY_CATEGORIES, - createDefaultLibraryQuery, - createMockLibraryItems, -} from "./libraryModel.js"; -import { hideLibraryItem, launchLibraryItem, loadLibraryItems, scanLibraryItems } from "./libraryBridge.js"; -import { - renderCategoryTabs, - renderDetailsPanel, - renderFilters, - renderGenreTabs, - renderGrid, - renderLibraryShell, - renderSortPanel, - renderSummary, -} from "./libraryComponents.js"; -import { cycleOption, requestNavigationRefresh } from "./libraryController.js"; - -const PLAY_STATES = ["all", "played", "unplayed"]; - -const findItem = (runtime, id = runtime.focusedId) => runtime.items.find((item) => item.id === id) ?? null; - -const focusKeyForRuntime = (runtime) => { - if (runtime.detailsOpen) return "library-detail-0"; - if (runtime.sortOpen) return `library-sort-${runtime.query.sortBy}`; - return runtime.focusedId ? `library-card-${runtime.focusedId}` : "library-category-games"; -}; - -export const createLibraryView = ({ state, renderView }) => { - const runtime = { - items: createMockLibraryItems(), - visibleItems: [], - query: createDefaultLibraryQuery(), - status: "Mock data ready", - message: "Mock apps are shown until Start Scan imports real apps into library.db.", - providers: [], - focusedId: null, - detailsOpen: false, - sortOpen: false, - mounted: false, - focusListenerAttached: false, - }; - - const renderLibrary = (focusKey = null) => { - const root = document.querySelector("[data-view='library']"); - if (!root) return; - - runtime.visibleItems = filterLibraryItems(runtime.items, runtime.query); - if (!runtime.visibleItems.some((item) => item.id === runtime.focusedId)) { - runtime.focusedId = runtime.visibleItems[0]?.id ?? null; - } - - root.querySelector("[data-library-categories]").innerHTML = renderCategoryTabs(runtime.query); - root.querySelector("[data-library-genres]").innerHTML = renderGenreTabs(runtime.query); - root.querySelector("[data-library-summary]").innerHTML = renderSummary( - runtime.items, - runtime.status, - runtime.message, - ); - root.querySelector("[data-library-filters]").innerHTML = renderFilters(runtime.query); - root.querySelector("[data-library-grid]").innerHTML = renderGrid(runtime.visibleItems, runtime.focusedId); - - const resultTitle = root.querySelector("[data-library-result-title]"); - const resultKicker = root.querySelector("[data-library-result-kicker]"); - const status = root.querySelector("[data-library-status]"); - if (resultTitle) resultTitle.textContent = `${runtime.visibleItems.length} Visible`; - if (resultKicker) resultKicker.textContent = `${runtime.query.genre} · ${runtime.query.sortBy}`; - if (status) status.textContent = runtime.message; - - const details = root.querySelector("[data-library-details]"); - details.innerHTML = runtime.detailsOpen ? renderDetailsPanel(findItem(runtime)) : ""; - details.setAttribute("aria-hidden", runtime.detailsOpen ? "false" : "true"); - details.classList.toggle("is-open", runtime.detailsOpen); - - const sortPanel = root.querySelector("[data-library-sort-panel]"); - sortPanel.innerHTML = runtime.sortOpen ? renderSortPanel(runtime.query) : ""; - sortPanel.setAttribute("aria-hidden", runtime.sortOpen ? "false" : "true"); - sortPanel.classList.toggle("is-open", runtime.sortOpen); - - requestNavigationRefresh(focusKey ?? focusKeyForRuntime(runtime)); - }; - - const setLoadedState = (result) => { - runtime.items = result.items; - runtime.status = result.status; - runtime.message = result.message; - runtime.providers = result.providers; - renderLibrary(); - }; - - const refreshLibrary = async () => { - runtime.status = "Refreshing"; - runtime.message = "Reloading library cards from library.db or mock fallback data."; - renderLibrary("library-refresh"); - setLoadedState(await loadLibraryItems()); - }; - - const scanLibrary = async () => { - runtime.status = "Scanning device"; - runtime.message = "Checking Steam, GOG, Epic, emulator folders, and local apps for future integrations."; - renderLibrary("library-scan"); - try { - setLoadedState(await scanLibraryItems()); - } catch (error) { - runtime.status = "Scan failed"; - runtime.message = String(error); - renderLibrary("library-scan"); - } - }; - - const launchFocusedItem = async () => { - const item = findItem(runtime); - if (!item) return; - const result = await launchLibraryItem(item); - runtime.status = result?.launched ? "Launch requested" : "Details ready"; - runtime.message = result?.message ?? `${item.title} is ready.`; - if (!item.installed) { - runtime.detailsOpen = true; - } - renderLibrary(); - }; - - const openDetails = () => { - if (!runtime.focusedId) return; - runtime.detailsOpen = true; - runtime.sortOpen = false; - renderLibrary("library-detail-0"); - }; - - const closePanels = () => { - runtime.detailsOpen = false; - runtime.sortOpen = false; - renderLibrary(); - }; - - const toggleSortPanel = () => { - runtime.sortOpen = !runtime.sortOpen; - runtime.detailsOpen = false; - renderLibrary(runtime.sortOpen ? `library-sort-${runtime.query.sortBy}` : focusKeyForRuntime(runtime)); - }; - - const applyElementAction = async (element) => { - if (!element) return; - const { action } = element.dataset; - - if (action === "scan") return scanLibrary(); - if (action === "refresh") return refreshLibrary(); - if (action === "category") runtime.query.category = element.dataset.category; - if (action === "genre") runtime.query.genre = element.dataset.genre; - if (action === "platform") runtime.query.platform = element.dataset.platform; - if (action === "toggle-installed") runtime.query.installedOnly = !runtime.query.installedOnly; - if (action === "cycle-play-state") runtime.query.playState = cycleOption(PLAY_STATES, runtime.query.playState, 1); - if (action === "toggle-coop") runtime.query.coOpOnly = !runtime.query.coOpOnly; - if (action === "toggle-achievements") runtime.query.achievementsOnly = !runtime.query.achievementsOnly; - if (action === "sort") runtime.query.sortBy = element.dataset.sort; - - if (action === "card") { - runtime.focusedId = element.dataset.itemId; - return openDetails(); - } - - if (action === "launch") return launchFocusedItem(); - - if (action === "install") { - runtime.status = "Install queued"; - runtime.message = "Store/provider install flows will connect here when integrations are added."; - } - - if (action === "uninstall") { - runtime.status = "Uninstall placeholder"; - runtime.message = "Uninstall will be routed through source-specific providers later."; - } - - if (action === "hide") { - const item = findItem(runtime); - if (item) { - item.hidden = true; - await hideLibraryItem(item); - runtime.detailsOpen = false; - runtime.status = "Hidden"; - runtime.message = `${item.title} is hidden from the visible library.`; - } - } - - if (action === "open-folder") { - runtime.status = "Open folder placeholder"; - runtime.message = "Folder opening will use the desktop bridge for installed apps."; - } - - if (action === "close-details" || action === "close-sort") { - return closePanels(); - } - - renderLibrary(element.dataset.focusKey); - }; - - const cycleCategory = (direction) => { - runtime.query.category = cycleOption( - LIBRARY_CATEGORIES.map((category) => category.id), - runtime.query.category, - direction, - ); - renderLibrary(`library-category-${runtime.query.category}`); - }; - - const cycleGenre = (direction) => { - runtime.query.genre = cycleOption(GENRE_FILTERS, runtime.query.genre, direction); - const genreIndex = GENRE_FILTERS.findIndex((genre) => genre === runtime.query.genre); - renderLibrary(`library-genre-${genreIndex}`); - }; - - return { - id: "library", - render: () => renderLibraryShell(), - mount: async () => { - const root = document.querySelector("[data-view='library']"); - if (!root) return; - runtime.mounted = true; - root.addEventListener("click", (event) => { - const actionElement = event.target.closest("[data-action]"); - if (actionElement) { - applyElementAction(actionElement); - } - }); - if (!runtime.focusListenerAttached) { - window.addEventListener("nebula-focus-change", (event) => { - const key = event.detail?.key ?? ""; - if (key.startsWith("library-card-")) { - runtime.focusedId = key.replace("library-card-", ""); - } - }); - runtime.focusListenerAttached = true; - } - renderLibrary("library-category-games"); - setLoadedState(await loadLibraryItems()); - }, - getNavigationContract: () => { - const root = document.querySelector("[data-view='library']"); - return { - focusRoot: root, - defaultFocus: root?.querySelector("[data-focus-key='library-category-games']") ?? null, - layout: { type: "grid", cols: 9, rows: 24 }, - hintsTemplate: "#library-hints-template", - nebulaNavigation: state.nebula.navigation, - useNebulaNavigation: false, - onAccept: applyElementAction, - onBack: () => { - if (runtime.detailsOpen || runtime.sortOpen) { - closePanels(); - return; - } - state.activeView = "home"; - renderView("home"); - }, - onMenu: () => {}, - onAction: (action, element) => { - if (action === "y") { - toggleSortPanel(); - return true; - } - if (action === "clear") { - openDetails(); - return true; - } - if (action === "l1") { - cycleCategory(-1); - return true; - } - if (action === "r1") { - cycleCategory(1); - return true; - } - if (action === "l2") { - cycleGenre(-1); - return true; - } - if (action === "r2") { - cycleGenre(1); - return true; - } - if (element?.dataset.itemId) { - runtime.focusedId = element.dataset.itemId; - renderLibrary(element.dataset.focusKey); - } - return false; - }, - }; - }, - }; -}; diff --git a/Bigscreen/src/views/library/libraryBridge.js b/Bigscreen/src/views/library/libraryBridge.js deleted file mode 100644 index fc28b64..0000000 --- a/Bigscreen/src/views/library/libraryBridge.js +++ /dev/null @@ -1,124 +0,0 @@ -import { createMockLibraryItems, normalizeLibraryItem } from "./libraryModel.js"; - -export const getTauriCore = async () => { - const globalCore = window.__TAURI__?.core; - if (typeof globalCore?.invoke === "function") { - return { - invoke: globalCore.invoke, - convertFileSrc: typeof globalCore.convertFileSrc === "function" ? globalCore.convertFileSrc : null, - }; - } - - try { - const tauriCore = await import("@tauri-apps/api/core"); - return { - invoke: typeof tauriCore.invoke === "function" ? tauriCore.invoke : null, - convertFileSrc: typeof tauriCore.convertFileSrc === "function" ? tauriCore.convertFileSrc : null, - }; - } catch (_error) { - return { invoke: null, convertFileSrc: null }; - } -}; - -const defaultLocalFolders = () => ["C:/Games", "D:/Games"]; - -export const loadLibraryItems = async () => { - const { invoke, convertFileSrc } = await getTauriCore(); - if (!invoke) { - return { - items: createMockLibraryItems(), - status: "Mock data", - message: "Run inside Tauri to scan real installed apps into library.db.", - providers: [], - }; - } - - try { - const games = await invoke("list_library_games"); - const scannedItems = Array.isArray(games) - ? games.map((game, index) => normalizeLibraryItem(game, index, convertFileSrc)) - : []; - - return { - items: scannedItems.length ? scannedItems : createMockLibraryItems(), - status: scannedItems.length ? "Synced with library.db" : "Mock data", - message: scannedItems.length - ? "Showing installed apps scanned into the separate NebulaOS library database." - : "No scanned apps yet. Mock cards are shown until Start Scan finds installed apps.", - providers: [], - }; - } catch (error) { - return { - items: createMockLibraryItems(), - status: "Library bridge fallback", - message: String(error), - providers: [], - }; - } -}; - -export const scanLibraryItems = async () => { - const { invoke, convertFileSrc } = await getTauriCore(); - if (!invoke) { - return { - items: createMockLibraryItems(), - status: "Desktop bridge unavailable", - message: "Start Scan will call Tauri when NebulaOS is running as a desktop app.", - providers: [], - }; - } - - const summary = await invoke("scan_library_command", { - request: { localFolders: defaultLocalFolders() }, - }); - - const items = Array.isArray(summary?.games) - ? summary.games.map((game, index) => normalizeLibraryItem(game, index, convertFileSrc)) - : []; - - return { - items: items.length ? items : createMockLibraryItems(), - status: `${summary?.discovered ?? 0} discovered`, - message: `${summary?.insertedOrUpdated ?? 0} saved · ${summary?.metadataMatched ?? 0} metadata matches · ${ - summary?.unmatched ?? 0 - } need review`, - providers: summary?.providers ?? [], - }; -}; - -export const launchLibraryItem = async (item) => { - if (!item?.installed) { - console.info("[NebulaOS] Install/details state requested", item); - return { launched: false, action: "details", message: "This app is not installed yet." }; - } - - const { invoke } = await getTauriCore(); - const numericId = Number(item.backendId ?? item.id); - if (invoke && Number.isFinite(numericId)) { - return invoke("launch_library_game", { gameId: numericId }); - } - - console.info("[NebulaOS] Launch placeholder", { - id: item.id, - title: item.title, - executablePath: item.executablePath, - installPath: item.installPath, - }); - return { launched: true, action: "mock", message: `Launch queued for ${item.title}.` }; -}; - -export const hideLibraryItem = async (item) => { - const { invoke } = await getTauriCore(); - const numericId = Number(item?.backendId ?? item?.id); - if (invoke && Number.isFinite(numericId)) { - await invoke("update_library_game", { - request: { - gameId: numericId, - title: null, - executablePath: null, - hidden: true, - favourite: null, - }, - }); - } -}; diff --git a/Bigscreen/src/views/library/libraryComponents.js b/Bigscreen/src/views/library/libraryComponents.js deleted file mode 100644 index 3f7ccf1..0000000 --- a/Bigscreen/src/views/library/libraryComponents.js +++ /dev/null @@ -1,440 +0,0 @@ -import { - GENRE_FILTERS, - LIBRARY_CATEGORIES, - PLATFORM_FILTERS, - PLAY_STATE_LABELS, - SORT_OPTIONS, - SOURCE_LABELS, - TYPE_LABELS, - formatPlaytime, - formatShortDate, - initialsForTitle, - summarizeLibrary, -} from "./libraryModel.js"; - -const escapeHtml = (value) => - String(value ?? "") - .replaceAll("&", "&") - .replaceAll("<", "<") - .replaceAll(">", ">") - .replaceAll('"', """) - .replaceAll("'", "'"); - -const providerLabel = (source) => SOURCE_LABELS[source] ?? source; -const typeLabel = (type) => TYPE_LABELS[type] ?? type; - -const navButton = ({ className, row, col, key, action, label, active = false, extra = "" }) => ` - -`; - -export const renderLibraryShell = () => ` -
    -
    -
    -

    Unified Library

    -

    Library

    -
    -
    -
    - - --- - --:-- -
    -
    - ${navButton({ className: "library-action", row: 0, col: 7, key: "library-scan", action: "scan", label: "Start Scan" })} - ${navButton({ className: "library-action", row: 0, col: 8, key: "library-refresh", action: "refresh", label: "Refresh" })} -
    -
    -
    - -
    -
    - - -
    - -
    -
    -
    -

    Ready

    -

    Controller Library

    -
    -

    Loading library...

    -
    -
    -
    -
    -
    -
    - - - -
    -`; - -export const renderCategoryTabs = (query) => - LIBRARY_CATEGORIES.map((category, index) => - navButton({ - className: "library-category-tab", - row: 1, - col: index, - key: `library-category-${category.id}`, - action: "category", - label: escapeHtml(category.label), - active: query.category === category.id, - extra: `data-category="${category.id}"`, - }), - ).join(""); - -export const renderGenreTabs = (query) => - GENRE_FILTERS.map((genre, index) => - navButton({ - className: "library-genre-tab", - row: 2, - col: index, - key: `library-genre-${index}`, - action: "genre", - label: escapeHtml(genre), - active: query.genre === genre, - extra: `data-genre="${escapeHtml(genre)}"`, - }), - ).join(""); - -export const renderSummary = (items, status, message) => { - const summary = summarizeLibrary(items); - return ` -

    Library Summary

    -
    - ${summary.total} - Total apps -
    -
    - ${summary.games} Games - ${summary.verified} Verified - ${summary.hidden} Hidden - ${summary.installed} Installed -
    -
    - -
    - ${escapeHtml(status)} -

    ${escapeHtml(message)}

    -
    -
    - `; -}; - -export const renderFilters = (query) => { - const platformButtons = PLATFORM_FILTERS.map((platform, index) => - navButton({ - className: "library-filter-chip", - row: 4 + index, - col: 0, - key: `library-platform-${platform}`, - action: "platform", - label: platform === "all" ? "All Platforms" : providerLabel(platform), - active: query.platform === platform, - extra: `data-platform="${platform}"`, - }), - ).join(""); - - return ` -

    Filter Toggles

    -
    - By Platform - ${platformButtons} -
    -
    - ${navButton({ - className: "library-filter-chip", - row: 11, - col: 0, - key: "library-installed-only", - action: "toggle-installed", - label: "Installed Only", - active: query.installedOnly, - })} - ${navButton({ - className: "library-filter-chip", - row: 12, - col: 0, - key: "library-play-state", - action: "cycle-play-state", - label: `Play State: ${PLAY_STATE_LABELS[query.playState]}`, - active: query.playState !== "all", - })} - ${navButton({ - className: "library-filter-chip", - row: 13, - col: 0, - key: "library-coop", - action: "toggle-coop", - label: "Co-op", - active: query.coOpOnly, - })} - ${navButton({ - className: "library-filter-chip", - row: 14, - col: 0, - key: "library-achievements", - action: "toggle-achievements", - label: "Achievements", - active: query.achievementsOnly, - })} -
    - `; -}; - -export const renderGrid = (items, focusedId) => { - if (!items.length) { - return ` -
    -

    No Matches

    -

    No apps match these filters.

    -

    Press Y to open sorting and filters, or clear toggles from the left panel.

    -
    - `; - } - - return items - .map((item, index) => { - const row = 4 + Math.floor(index / 4); - const col = 2 + (index % 4); - const artStyle = item.coverImage - ? `style="--library-art: url('${escapeHtml(item.coverImage)}'); --library-accent: ${escapeHtml(item.accent)};"` - : `style="--library-accent: ${escapeHtml(item.accent)};"`; - const achievementCopy = item.achievementsSupported - ? `${item.achievementsUnlocked ?? 0}/${item.achievementsTotal ?? "?"} achievements` - : "No achievements"; - - return ` - - `; - }) - .join(""); -}; - -export const renderDetailsPanel = (item) => { - if (!item) return ""; - - const achievements = item.achievementsSupported - ? `${item.achievementsUnlocked ?? 0} / ${item.achievementsTotal ?? "?"}` - : "Not supported"; - - const screenshotsHtml = item.screenshots && item.screenshots.length > 0 - ? ` -
    -

    Screenshots

    - -
    - ` - : ''; - - const achievementsHtml = item.achievementsSupported - ? ` -
    -

    Achievements

    -
    -
    -
    - ${item.achievementsUnlocked ?? 0} - Unlocked -
    -
    -
    -
    -
    - ${item.achievementsTotal ?? "?"} - Total -
    -
    -
    -
    - ` - : ''; - - const featuresHtml = ` -
    -

    Features

    -
    - ${item.supportsController ? 'Controller Support' : ''} - ${item.steamDeckVerified ? 'Steam Deck Verified' : ''} - ${item.multiplayer ? 'Multiplayer' : ''} - ${item.coOp ? 'Co-op' : ''} -
    -
    - `; - - return ` -
    -
    -
    -
    -
    - -
    -
    -
    -

    ${escapeHtml(providerLabel(item.source))} · ${escapeHtml(typeLabel(item.type))}

    -

    ${escapeHtml(item.title)}

    -
    - ${item.installed ? '✓ Installed' : 'Not installed'} - ${item.lastPlayed ? `Last played: ${escapeHtml(formatShortDate(item.lastPlayed))}` : ''} - ${item.playtimeMinutes > 0 ? `${escapeHtml(formatPlaytime(item.playtimeMinutes))}` : ''} -
    -
    - -
    - -
    -
    - -
    -
    -
    -

    About

    -

    ${escapeHtml(item.description)}

    - ${item.longDescription ? `

    ${escapeHtml(item.longDescription)}

    ` : ''} -
    - -
    -

    Details

    -
    -
    -
    Genres
    -
    ${escapeHtml(item.genre.join(', '))}
    -
    -
    -
    Source
    -
    ${escapeHtml(providerLabel(item.source))}
    -
    - ${item.installed ? `
    -
    Install Location
    -
    ${escapeHtml(item.installPath)}
    -
    ` : ''} - ${item.installedAt ? `
    -
    Installed
    -
    ${escapeHtml(formatShortDate(item.installedAt))}
    -
    ` : ''} - ${item.playtimeMinutes > 0 ? `
    -
    Playtime
    -
    ${escapeHtml(formatPlaytime(item.playtimeMinutes))}
    -
    ` : ''} -
    -
    - - ${screenshotsHtml} -
    - -
    - ${featuresHtml} - ${achievementsHtml} - -
    - ${["Hide", "Open Folder"] - .filter(label => { - if (label === "Open Folder" && !item.installed) return false; - return true; - }) - .map( - (label, index) => ` - - `, - ) - .join("")} -
    -
    -
    -
    -
    - `; -}; - -export const renderSortPanel = (query) => ` -
    -
    -

    Sort Library

    -

    Filter / Sort

    -

    Use Y to close. These options immediately reorder the visible grid.

    -
    - ${SORT_OPTIONS.map((option, index) => - navButton({ - className: "library-sort-option", - row: 18 + index, - col: 6, - key: `library-sort-${option.id}`, - action: "sort", - label: escapeHtml(option.label), - active: query.sortBy === option.id, - extra: `data-sort="${option.id}"`, - }), - ).join("")} -
    -
    -`; diff --git a/Bigscreen/src/views/library/libraryController.js b/Bigscreen/src/views/library/libraryController.js deleted file mode 100644 index e624c1a..0000000 --- a/Bigscreen/src/views/library/libraryController.js +++ /dev/null @@ -1,14 +0,0 @@ -export const requestNavigationRefresh = (focusKey = null) => { - window.dispatchEvent( - new CustomEvent("nebula-navigation-refresh", { - detail: { focusKey }, - }), - ); -}; - -export const cycleOption = (options, current, direction) => { - const index = options.findIndex((option) => option === current); - const safeIndex = index >= 0 ? index : 0; - const nextIndex = (safeIndex + direction + options.length) % options.length; - return options[nextIndex]; -}; diff --git a/Bigscreen/src/views/library/libraryFilters.js b/Bigscreen/src/views/library/libraryFilters.js deleted file mode 100644 index 80614da..0000000 --- a/Bigscreen/src/views/library/libraryFilters.js +++ /dev/null @@ -1,83 +0,0 @@ -const lower = (value) => String(value ?? "").toLowerCase(); - -const byDateDesc = (field) => (left, right) => { - const leftTime = left[field] ? new Date(left[field]).getTime() : 0; - const rightTime = right[field] ? new Date(right[field]).getTime() : 0; - return rightTime - leftTime; -}; - -export const filterLibraryItems = (items, query) => { - let next = [...items]; - - if (!query.includeHidden) { - next = next.filter((item) => !item.hidden); - } - - if (query.category === "games") { - next = next.filter((item) => item.type === "game"); - } - - if (query.category === "software") { - next = next.filter((item) => item.type !== "game"); - } - - if (query.genre === "Latest Installed") { - next = next.filter((item) => item.installedAt); - } else if (query.genre === "Recently Played") { - next = next.filter((item) => item.lastPlayed); - } else if (query.genre !== "All Genres") { - next = next.filter((item) => item.genre.some((genre) => lower(genre) === lower(query.genre))); - } - - if (query.platform !== "all") { - next = next.filter((item) => item.source === query.platform); - } - - if (query.installedOnly) { - next = next.filter((item) => item.installed); - } - - if (query.playState === "played") { - next = next.filter((item) => item.playtimeMinutes > 0 || item.lastPlayed); - } - - if (query.playState === "unplayed") { - next = next.filter((item) => item.playtimeMinutes === 0 && !item.lastPlayed); - } - - if (query.coOpOnly) { - next = next.filter((item) => item.coOp); - } - - if (query.achievementsOnly) { - next = next.filter((item) => item.achievementsSupported); - } - - return sortLibraryItems(next, query.sortBy, query.genre); -}; - -export const sortLibraryItems = (items, sortBy, genre) => { - const sorted = [...items]; - const resolvedSort = - genre === "Latest Installed" ? "recentlyInstalled" : genre === "Recently Played" ? "recentlyPlayed" : sortBy; - - if (resolvedSort === "recentlyPlayed") { - return sorted.sort(byDateDesc("lastPlayed")); - } - - if (resolvedSort === "recentlyInstalled") { - return sorted.sort(byDateDesc("installedAt")); - } - - if (resolvedSort === "playtime") { - return sorted.sort((left, right) => right.playtimeMinutes - left.playtimeMinutes); - } - - if (resolvedSort === "platform") { - return sorted.sort( - (left, right) => left.source.localeCompare(right.source) || left.title.localeCompare(right.title), - ); - } - - return sorted.sort((left, right) => left.title.localeCompare(right.title)); -}; diff --git a/Bigscreen/src/views/library/libraryModel.js b/Bigscreen/src/views/library/libraryModel.js deleted file mode 100644 index c0a138c..0000000 --- a/Bigscreen/src/views/library/libraryModel.js +++ /dev/null @@ -1,309 +0,0 @@ -export const LIBRARY_CATEGORIES = [ - { id: "games", label: "Games" }, - { id: "software", label: "Software" }, - { id: "unified", label: "Unified Library" }, -]; - -export const GENRE_FILTERS = [ - "All Genres", - "Action", - "RPG", - "Strategy", - "Indie", - "Latest Installed", - "Recently Played", -]; - -export const PLATFORM_FILTERS = ["all", "steam", "gog", "epic", "native", "emulated", "other"]; - -export const SORT_OPTIONS = [ - { id: "recentlyPlayed", label: "Recently played" }, - { id: "recentlyInstalled", label: "Recently installed" }, - { id: "alphabetical", label: "Alphabetical" }, - { id: "playtime", label: "Playtime" }, - { id: "platform", label: "Platform" }, -]; - -export const SOURCE_LABELS = { - steam: "Steam", - gog: "GOG", - epic: "Epic", - native: "Native", - emulated: "Emulated", - other: "Other", -}; - -export const TYPE_LABELS = { - game: "Game", - software: "Software", - tool: "Tool", - launcher: "Launcher", -}; - -export const PLAY_STATE_LABELS = { - all: "All play states", - played: "Played", - unplayed: "Unplayed", -}; - -export const createDefaultLibraryQuery = () => ({ - category: "games", - genre: "All Genres", - platform: "all", - installedOnly: false, - playState: "all", - coOpOnly: false, - achievementsOnly: false, - sortBy: "recentlyPlayed", - includeHidden: false, -}); - -const sourceFromBackend = (source) => { - const normalized = String(source ?? "").toLowerCase(); - if (normalized === "local") return "native"; - if (["steam", "gog", "epic", "native", "emulated"].includes(normalized)) return normalized; - return "other"; -}; - -const typeFromBackend = (kind) => { - const normalized = String(kind ?? "").toLowerCase(); - if (["game", "software", "tool", "launcher"].includes(normalized)) return normalized; - return "game"; -}; - -export const initialsForTitle = (title) => - String(title ?? "Nebula") - .split(/\s+/) - .filter(Boolean) - .slice(0, 2) - .map((part) => part[0]?.toUpperCase()) - .join("") || "OS"; - -export const formatPlaytime = (minutes = 0) => { - if (!minutes) return "Not played"; - if (minutes < 60) return `${minutes}m`; - const hours = Math.floor(minutes / 60); - const remainder = minutes % 60; - return remainder ? `${hours}h ${remainder}m` : `${hours}h`; -}; - -export const formatShortDate = (value) => { - if (!value) return "Never"; - const date = new Date(value); - if (Number.isNaN(date.getTime())) return "Never"; - return date.toLocaleDateString([], { month: "short", day: "numeric" }); -}; - -export const normalizeLibraryItem = (raw, index = 0, convertFileSrc = null) => { - const title = raw?.userTitle || raw?.title || "Unknown App"; - const source = sourceFromBackend(raw?.platformSource ?? raw?.source); - const type = typeFromBackend(raw?.appKind ?? raw?.type); - const convert = (path) => (path && convertFileSrc ? convertFileSrc(path) : path || null); - const convertArray = (paths) => (Array.isArray(paths) ? paths.map(convert).filter(Boolean) : []); - const installed = raw?.installed ?? Boolean(raw?.installPath || raw?.install_path); - - return { - id: String(raw?.id ?? `backend-${index}`), - backendId: raw?.id ?? null, - title, - type, - source, - genre: Array.isArray(raw?.genre) - ? raw.genre - : Array.isArray(raw?.genres) - ? raw.genres - : type === "game" - ? ["Action"] - : ["Utilities"], - installed, - installPath: raw?.installPath ?? raw?.install_path ?? null, - executablePath: raw?.executablePath ?? raw?.executable_path ?? null, - coverImage: convert(raw?.coverImage ?? raw?.cover_image), - bannerImage: convert(raw?.bannerImage ?? raw?.heroImage ?? raw?.hero_image), - iconImage: convert(raw?.iconImage ?? raw?.icon_image), - screenshots: convertArray(raw?.screenshots ?? []), - description: - raw?.description || - "Scanned from your local library. Metadata can be enriched later by Steam, GOG, Epic, emulator, and local metadata providers.", - longDescription: raw?.longDescription ?? null, - lastPlayed: raw?.lastPlayed ?? null, - installedAt: raw?.installedAt ?? raw?.createdAt ?? null, - playtimeMinutes: Number(raw?.playtimeMinutes ?? 0), - supportsController: Boolean(raw?.supportsController ?? type === "game"), - steamDeckVerified: Boolean(raw?.steamDeckVerified ?? source === "steam"), - achievementsSupported: Boolean(raw?.achievementsSupported ?? false), - achievementsUnlocked: raw?.achievementsUnlocked ?? null, - achievementsTotal: raw?.achievementsTotal ?? null, - hidden: Boolean(raw?.hidden ?? raw?.userHidden ?? false), - multiplayer: Boolean(raw?.multiplayer ?? false), - coOp: Boolean(raw?.coOp ?? false), - accent: raw?.accent ?? ["#4fd8ff", "#9d4fe0", "#1f7aff", "#39ffd2"][index % 4], - }; -}; - -export const createMockLibraryItems = () => - [ - { - id: "mock-starfall", - title: "Starfall Protocol", - type: "game", - source: "steam", - genre: ["Action", "RPG"], - installed: true, - installPath: "C:/Games/Starfall Protocol", - executablePath: "C:/Games/Starfall Protocol/starfall.exe", - description: "Pilot a relic fighter through collapsing gates in a neon campaign built for controller play.", - longDescription: "Experience a fast-paced action-RPG where you pilot experimental fighter craft through a collapsing interdimensional gateway. Built from the ground up with controller support, Starfall Protocol features real-time combat, intricate level design, and a gripping sci-fi narrative. Perfect for couch co-op sessions or solo playthroughs.", - lastPlayed: "2026-05-15T21:15:00Z", - installedAt: "2026-05-01T08:30:00Z", - playtimeMinutes: 1874, - supportsController: true, - steamDeckVerified: true, - achievementsSupported: true, - achievementsUnlocked: 28, - achievementsTotal: 54, - multiplayer: true, - coOp: true, - screenshots: [ - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%234fd8ff' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EStarfall Protocol - Screenshot 1%3C/text%3E%3C/svg%3E", - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%239d4fe0' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EStarfall Protocol - Screenshot 2%3C/text%3E%3C/svg%3E", - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%231f7aff' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EStarfall Protocol - Screenshot 3%3C/text%3E%3C/svg%3E", - ], - accent: "#4fd8ff", - }, - { - id: "mock-iron-vault", - title: "Iron Vault Tactics", - type: "game", - source: "gog", - genre: ["Strategy", "Indie"], - installed: true, - installPath: "D:/Games/Iron Vault Tactics", - executablePath: "D:/Games/Iron Vault Tactics/ivt.exe", - description: "A turn-based tactics sandbox with long-form campaigns, mod support, and couch co-op skirmishes.", - longDescription: "Command your squad through dynamic turn-based tactical battles in Iron Vault Tactics. Features mod support, deep unit customization, engaging campaign narratives, and intense couch co-op multiplayer. Each decision matters in this tactical masterpiece.", - lastPlayed: "2026-05-10T11:00:00Z", - installedAt: "2026-04-18T18:00:00Z", - playtimeMinutes: 942, - supportsController: true, - achievementsSupported: false, - multiplayer: true, - coOp: true, - screenshots: [ - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%2339ffd2' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EIron Vault Tactics - Screenshot 1%3C/text%3E%3C/svg%3E", - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%234fd8ff' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EIron Vault Tactics - Screenshot 2%3C/text%3E%3C/svg%3E", - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%239d4fe0' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EIron Vault Tactics - Screenshot 3%3C/text%3E%3C/svg%3E", - ], - accent: "#39ffd2", - }, - { - id: "mock-nebula-paint", - title: "Nebula Paint Studio", - type: "software", - source: "native", - genre: ["Creative", "Utilities"], - installed: true, - installPath: "C:/Program Files/Nebula Paint", - executablePath: "C:/Program Files/Nebula Paint/paint.exe", - description: "A TV-friendly concept art tool for quick capture, markup, and launcher artwork editing.", - longDescription: "Nebula Paint Studio is a professional-grade digital painting application optimized for controller input and TV display. Create stunning concept art, edit game launcher artwork, and collaborate seamlessly with frame-perfect precision.", - lastPlayed: "2026-05-11T06:20:00Z", - installedAt: "2026-03-12T09:00:00Z", - playtimeMinutes: 223, - supportsController: true, - achievementsSupported: false, - multiplayer: false, - coOp: false, - screenshots: [ - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%239d4fe0' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3ENebula Paint Studio - Screenshot 1%3C/text%3E%3C/svg%3E", - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%234fd8ff' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3ENebula Paint Studio - Screenshot 2%3C/text%3E%3C/svg%3E", - ], - accent: "#9d4fe0", - }, - { - id: "mock-ember", - title: "Emberline", - type: "game", - source: "epic", - genre: ["Action", "Indie"], - installed: false, - installPath: null, - executablePath: null, - description: "Wishlist entry from a linked store. Install support will be routed through the Epic integration later.", - longDescription: "Emberline is an indie action game with stunning visuals and engaging gameplay. Add it to your library to stay updated on new releases, sales, and updates.", - lastPlayed: null, - installedAt: null, - playtimeMinutes: 0, - supportsController: true, - steamDeckVerified: false, - achievementsSupported: true, - achievementsUnlocked: 0, - achievementsTotal: 32, - multiplayer: false, - coOp: false, - screenshots: [ - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%231f7aff' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EEmberline - Screenshot 1%3C/text%3E%3C/svg%3E", - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%239d4fe0' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3EEmberline - Screenshot 2%3C/text%3E%3C/svg%3E", - ], - accent: "#1f7aff", - }, - { - id: "mock-retro-core", - title: "RetroCore Station", - type: "launcher", - source: "emulated", - genre: ["Launcher", "Retro"], - installed: true, - installPath: "D:/Emulation/RetroCore", - executablePath: "D:/Emulation/RetroCore/retrocore.exe", - description: "A controller-native emulator hub prepared for future ROM library scanning and save sync.", - longDescription: "RetroCore Station is your gateway to classic gaming. Play thousands of retro games with full controller support, save synchronization, and achievement tracking across multiple emulation systems.", - lastPlayed: "2026-05-14T04:10:00Z", - installedAt: "2026-05-03T15:10:00Z", - playtimeMinutes: 517, - supportsController: true, - achievementsSupported: true, - achievementsUnlocked: 88, - achievementsTotal: 210, - multiplayer: true, - coOp: true, - screenshots: [ - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%23ffb84f' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3ERetroCore Station - Screenshot 1%3C/text%3E%3C/svg%3E", - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%234fd8ff' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3ERetroCore Station - Screenshot 2%3C/text%3E%3C/svg%3E", - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='960' height='540'%3E%3Crect fill='%239d4fe0' width='960' height='540'/%3E%3Ctext x='480' y='270' font-size='24' fill='white' text-anchor='middle' dominant-baseline='middle'%3ERetroCore Station - Screenshot 3%3C/text%3E%3C/svg%3E", - ], - accent: "#ffb84f", - }, - { - id: "mock-orbit-tools", - title: "Orbit Mod Tools", - type: "tool", - source: "other", - genre: ["Utilities", "Game Development"], - installed: false, - installPath: null, - executablePath: null, - description: "Tooling placeholder for future mod SDK detection, dependency checks, and per-game utilities.", - longDescription: "Orbit Mod Tools provides a comprehensive suite of utilities for game modding, including SDK management, dependency resolution, and per-game optimization tools. Essential for serious mod creators.", - lastPlayed: null, - installedAt: null, - playtimeMinutes: 0, - supportsController: false, - achievementsSupported: false, - multiplayer: false, - coOp: false, - screenshots: [], - accent: "#ff6b9a", - }, - ].map((item, index) => normalizeLibraryItem(item, index)); - -export const summarizeLibrary = (items) => { - const visible = items.filter((item) => !item.hidden); - return { - total: visible.length, - games: visible.filter((item) => item.type === "game").length, - verified: visible.filter((item) => item.steamDeckVerified).length, - hidden: items.filter((item) => item.hidden).length, - installed: visible.filter((item) => item.installed).length, - }; -}; diff --git a/Bigscreen/src/views/lock/lock.css b/Bigscreen/src/views/lock/lock.css deleted file mode 100644 index 23ececf..0000000 --- a/Bigscreen/src/views/lock/lock.css +++ /dev/null @@ -1,347 +0,0 @@ -.lock-view { - justify-content: center; - align-items: center; - background: radial-gradient(circle at 55% 48%, rgba(79, 216, 255, 0.1), transparent 62%); -} - -.lock-layout { - width: min(1240px, 96vw); - min-height: min(720px, 82vh); - display: grid; - grid-template-columns: minmax(320px, 1fr) minmax(360px, 520px); - gap: clamp(24px, 4vw, 58px); - background: - linear-gradient(165deg, rgba(56, 82, 128, 0.18), rgba(19, 30, 56, 0.74)), - radial-gradient(circle at 65% 35%, rgba(79, 216, 255, 0.08), transparent 52%), - rgba(13, 18, 31, 0.78); - border-radius: 20px; - border: 1px solid rgba(255, 255, 255, 0.08); - box-shadow: - 0 24px 62px rgba(0, 0, 0, 0.38), - inset 0 1px 0 rgba(255, 255, 255, 0.12); - backdrop-filter: blur(14px); - padding: clamp(28px, 4vw, 48px); -} - -.lock-view.is-success .lock-layout { - box-shadow: - 0 0 0 1px rgba(79, 216, 255, 0.3), - 0 24px 62px rgba(0, 0, 0, 0.38), - 0 0 48px rgba(79, 216, 255, 0.24); -} - -.lock-left { - display: flex; - flex-direction: column; - justify-content: center; - gap: var(--nebula-spacing-lg); -} - -.lock-user { - display: inline-flex; - align-items: center; - gap: 12px; -} - -.lock-avatar { - width: 42px; - height: 42px; - border-radius: 999px; - border: 2px solid rgba(79, 216, 255, 0.44); - background: - radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.85), transparent 45%), - linear-gradient(145deg, rgba(112, 189, 255, 0.66), rgba(66, 78, 142, 0.8)); -} - -.lock-username { - margin: 0; - font-weight: 640; - font-size: 18px; -} - -.lock-title-row { - display: flex; - align-items: center; - gap: 14px; - flex-wrap: wrap; -} - -.lock-title { - margin: 0; - font-size: clamp(36px, 5vw, 52px); - font-weight: 540; - letter-spacing: -0.01em; -} - -.lock-controller-badge { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 34px; - height: 34px; - padding: 0 10px; - border-radius: 999px; - font-size: 14px; - font-weight: 700; - letter-spacing: 0.02em; - border: 1px solid rgba(255, 255, 255, 0.2); - background: rgba(255, 255, 255, 0.08); - color: var(--nebula-color-text); - box-shadow: 0 0 18px rgba(79, 216, 255, 0.16); -} - -.lock-controller-badge--xbox { - border-color: rgba(107, 190, 70, 0.55); - background: rgba(107, 190, 70, 0.18); -} - -.lock-controller-badge--playstation { - border-color: rgba(70, 130, 220, 0.55); - background: rgba(70, 130, 220, 0.18); -} - -.lock-controller-badge--switch { - border-color: rgba(230, 70, 70, 0.55); - background: rgba(230, 70, 70, 0.16); -} - -.lock-glyph { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 28px; - height: 28px; - margin: 0 4px; - padding: 0 8px; - border-radius: var(--nebula-radius-sm); - font-size: 14px; - font-weight: 700; - vertical-align: middle; - background: rgba(255, 255, 255, 0.1); - border: 1px solid rgba(255, 255, 255, 0.2); - color: var(--nebula-color-text); -} - -.lock-glyph-menu { - border-color: rgba(79, 216, 255, 0.45); - box-shadow: 0 0 14px rgba(79, 216, 255, 0.22); -} - -.lock-status .lock-glyph-menu { - transform: translateY(-1px); -} - -.lock-copy { - margin: 0; - color: var(--nebula-color-muted); - font-size: 20px; - line-height: 1.45; - max-width: 540px; -} - -.lock-user-setup { - display: grid; - gap: 12px; - max-width: 420px; -} - -.lock-field { - display: grid; - gap: 6px; -} - -.lock-field span { - font-size: 13px; - color: var(--nebula-color-muted); -} - -.lock-field input { - height: 44px; - border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.18); - background: rgba(9, 15, 30, 0.65); - color: var(--nebula-color-text); - padding: 0 12px; - font-size: 16px; -} - -.lock-field input:focus { - outline: none; - border-color: rgba(79, 216, 255, 0.72); - box-shadow: 0 0 0 2px rgba(79, 216, 255, 0.2); -} - -.lock-create-user { - height: 46px; - border: 1px solid rgba(79, 216, 255, 0.4); - border-radius: 12px; - background: linear-gradient(160deg, rgba(51, 87, 135, 0.55), rgba(18, 33, 58, 0.8)); - color: var(--nebula-color-text); - font-weight: 600; - font-size: 15px; - letter-spacing: 0.01em; -} - -.lock-create-user.is-focused { - box-shadow: - 0 0 0 2px rgba(79, 216, 255, 0.55), - 0 0 24px rgba(79, 216, 255, 0.3); -} - -.lock-dots { - display: flex; - align-items: center; - gap: clamp(14px, 1.6vw, 22px); - min-height: 30px; - transition: transform var(--nebula-duration-fast) var(--nebula-ease-console); -} - -.lock-dot { - width: 22px; - height: 22px; - border-radius: 999px; - border: 2px solid rgba(191, 206, 230, 0.38); - background: rgba(255, 255, 255, 0.05); - opacity: 0.75; - transform: scale(0.92); - transition: - transform var(--nebula-duration-fast) var(--nebula-ease-console), - border-color var(--nebula-duration-fast) var(--nebula-ease-console), - background-color var(--nebula-duration-fast) var(--nebula-ease-console), - box-shadow var(--nebula-duration-fast) var(--nebula-ease-console); -} - -.lock-dot.active { - border-color: rgba(79, 216, 255, 0.72); - box-shadow: 0 0 16px rgba(79, 216, 255, 0.34); -} - -.lock-dot.filled { - background: rgba(215, 230, 255, 0.9); - border-color: rgba(215, 230, 255, 0.95); - transform: scale(1); - animation: dotFill var(--nebula-duration-fast) var(--nebula-ease-console); -} - -.lock-dots.is-success .lock-dot.filled { - border-color: rgba(79, 216, 255, 0.95); - background: rgba(79, 216, 255, 0.9); - box-shadow: 0 0 20px rgba(79, 216, 255, 0.58); -} - -.lock-dots.is-error .lock-dot { - border-color: color-mix(in srgb, var(--nebula-color-danger) 72%, rgba(255, 255, 255, 0.3)); - box-shadow: 0 0 20px color-mix(in srgb, var(--nebula-color-danger) 38%, transparent); -} - -.lock-dots.is-shaking { - animation: dotsShake 420ms var(--nebula-ease-console); -} - -.lock-status { - margin: 0; - min-height: 24px; - font-size: 16px; - color: var(--nebula-color-muted); -} - -.lock-status.is-danger { - color: var(--nebula-color-danger); -} - -.lock-right { - align-self: center; - display: grid; - grid-template-columns: repeat(3, minmax(96px, 1fr)); - gap: clamp(10px, 1.4vw, 18px); - justify-items: center; - background: rgba(15, 24, 45, 0.6); - border: 1px solid rgba(255, 255, 255, 0.08); -} - -.lock-num { - width: clamp(82px, 7.3vw, 112px); - height: clamp(82px, 7.3vw, 112px); - border: none; - border-radius: 18px; - font-size: clamp(42px, 3.8vw, 68px); - font-weight: 330; - line-height: 1; - color: color-mix(in srgb, var(--nebula-color-text) 95%, rgba(215, 230, 255, 0.94)); - background: - radial-gradient(circle at 35% 30%, rgba(255, 255, 255, 0.12), transparent 55%), - linear-gradient(162deg, rgba(61, 90, 140, 0.18), rgba(15, 23, 43, 0.6)); - box-shadow: - 0 10px 22px rgba(0, 0, 0, 0.24), - inset 0 1px 0 rgba(255, 255, 255, 0.08); - transition: - transform var(--nebula-duration-nav) var(--nebula-ease-console), - box-shadow var(--nebula-duration-nav) var(--nebula-ease-console), - border-color var(--nebula-duration-nav) var(--nebula-ease-console); - display: inline-flex; - align-items: baseline; - justify-content: center; - gap: 8px; -} - -.lock-num.is-zero { - grid-column: 2; -} - -.lock-num.is-focused { - transform: scale(1.1); - box-shadow: - 0 0 0 2px rgba(79, 216, 255, 0.5), - 0 0 28px rgba(79, 216, 255, 0.36), - 0 12px 30px rgba(0, 0, 0, 0.28); -} - -.lock-map { - font-size: 13px; - font-weight: 650; - letter-spacing: 0.02em; - color: color-mix(in srgb, var(--nebula-color-muted) 80%, #fff); - transform: translateY(-8px); -} - -.lock-num.is-focused .lock-map { - color: color-mix(in srgb, var(--nebula-color-accent) 85%, #fff); -} - -@keyframes dotFill { - 0% { - opacity: 0.35; - transform: scale(0.62); - } - - 100% { - opacity: 1; - transform: scale(1); - } -} - -@keyframes dotsShake { - 0% { - transform: translateX(0); - } - - 20% { - transform: translateX(-9px); - } - - 40% { - transform: translateX(8px); - } - - 60% { - transform: translateX(-6px); - } - - 80% { - transform: translateX(4px); - } - - 100% { - transform: translateX(0); - } -} diff --git a/Bigscreen/src/views/lock/lock.html b/Bigscreen/src/views/lock/lock.html deleted file mode 100644 index 0db89fc..0000000 --- a/Bigscreen/src/views/lock/lock.html +++ /dev/null @@ -1,39 +0,0 @@ -
    -
    -
    -

    Nebula OS

    -
    - -

    --:--

    -
    -
    -
    -
    - -
    -

    --:--

    -

    -- --- ----

    -

    Press any key to unlock

    -
    - -
    -

    Enter Security PIN

    -
    -
    - - - - - - - - - - - - -
    -

    Default v0 PIN: 1234

    -

    -
    -
    diff --git a/Bigscreen/src/views/lock/lock.js b/Bigscreen/src/views/lock/lock.js deleted file mode 100644 index 294665d..0000000 --- a/Bigscreen/src/views/lock/lock.js +++ /dev/null @@ -1,473 +0,0 @@ -import { PROFILE_LABELS } from "../../core/gamepadProfile.js"; - -const PROFILE_BADGE_LABELS = { - xbox: "Xbox", - playstation: "PS", - switch: "Switch", -}; - -const LOCK_TEMPLATE = ` -
    -
    -
    -
    - -

    Nebula User

    -
    -
    -

    Enter your passkey

    - -
    -

    Using your controller, enter your 6-digit passkey.

    -
    -

    -
    - -
    - - - - - - - - - - - - - -
    -
    -
    -`; - -export const createLockView = ({ state, renderView, keyboard }) => { - const ENTRY_DEBOUNCE_MS = 120; - const FAIL_CLEAR_MS = 600; - - let digits = []; - let setupDigits = []; - let setupSeed = ""; - let setupPhase = "create"; - let busy = false; - let lastEntryAt = 0; - let keyboardListener = null; - let profileListener = null; - let awaitingConfirm = false; - - const config = () => state.passkey.getConfig(); - - const controllerLabel = () => PROFILE_LABELS[state.controllerProfile] ?? PROFILE_LABELS.generic; - - const menuGlyphMarkup = () => { - const glyph = state.glyphs.menu ?? "Menu"; - return ``; - }; - - const menuConfirmMarkup = (prefix = "Press ", suffix = " to confirm.") => - `${prefix}${menuGlyphMarkup()}${suffix}`; - - const ACTION_TO_DIGIT = { - up: "1", - left: "2", - down: "3", - right: "4", - l2: "5", - r2: "6", - l1: "7", - r1: "8", - y: "9", - clear: "0", - }; - - const playTone = (frequency = 860, durationMs = 34, gainValue = 0.03) => { - const AudioContextClass = window.AudioContext ?? window.webkitAudioContext; - if (!AudioContextClass) { - return; - } - - try { - const ctx = new AudioContextClass(); - const oscillator = ctx.createOscillator(); - const gain = ctx.createGain(); - oscillator.type = "triangle"; - oscillator.frequency.value = frequency; - gain.gain.value = gainValue; - oscillator.connect(gain); - gain.connect(ctx.destination); - oscillator.start(); - - window.setTimeout(() => { - oscillator.stop(); - ctx.close(); - }, durationMs); - } catch (_error) { - // no-op - } - }; - - const pulseHaptic = (ms = 10) => { - navigator.vibrate?.(ms); - }; - - const setStatus = (text = "", danger = false, { html = false } = {}) => { - const status = document.querySelector("[data-status]"); - if (!status) { - return; - } - - if (html) { - status.innerHTML = text; - } else { - status.textContent = text; - } - status.classList.toggle("is-danger", danger); - }; - - const setConfirmStatus = () => { - awaitingConfirm = true; - setStatus(menuConfirmMarkup(), false, { html: true }); - }; - - const updateControllerBadge = () => { - const badge = document.querySelector("[data-controller-badge]"); - if (!badge) { - return; - } - - const profile = state.controllerProfile ?? "generic"; - badge.hidden = profile === "generic"; - badge.className = `lock-controller-badge lock-controller-badge--${profile}`; - badge.textContent = PROFILE_BADGE_LABELS[profile] ?? state.glyphs.menu ?? ""; - badge.title = `${controllerLabel()} controller detected`; - }; - - const updateCopy = () => { - const copy = document.querySelector("[data-copy]"); - if (!copy) { - return; - } - - const length = config().length; - const label = controllerLabel(); - - if (state.passkeySetupRequired) { - if (setupPhase === "confirm") { - copy.innerHTML = `Re-enter the same ${length}-digit passkey, then ${menuConfirmMarkup("press ", ".")}`; - return; - } - copy.textContent = `Use your ${label} controller buttons to enter your ${length}-digit passkey.`; - return; - } - - if (config().requireConfirm) { - copy.innerHTML = `Use your ${label} controller buttons to enter ${length} digits, then ${menuConfirmMarkup("press ", ".")}`; - return; - } - - copy.textContent = `Use your ${label} controller buttons to enter your ${length}-digit passkey.`; - }; - - const updateMapLabels = () => { - document.querySelectorAll("[data-map]").forEach((element) => { - const action = element.dataset.map; - element.textContent = state.glyphs[action] ?? action?.toUpperCase?.() ?? ""; - }); - }; - - const renderDots = (stateClass = "") => { - const dots = document.querySelector("[data-passkey-dots]"); - if (!dots) { - return; - } - - dots.className = `lock-dots ${stateClass}`.trim(); - dots.innerHTML = ""; - - for (let index = 0; index < config().length; index += 1) { - const dot = document.createElement("span"); - dot.className = "lock-dot"; - - if (index < digits.length) { - dot.classList.add("filled"); - } - if (index === digits.length && digits.length < config().length) { - dot.classList.add("active"); - } - - dots.append(dot); - } - }; - - const clearDigits = () => { - digits = []; - awaitingConfirm = false; - renderDots(); - }; - - const lockoutText = (remainingMs) => `Too many attempts. Retry in ${Math.ceil(remainingMs / 1000)}s.`; - - const applyFailure = (text) => { - setStatus(text, true); - renderDots("is-error is-shaking"); - playTone(220, 84, 0.05); - - window.setTimeout(() => { - clearDigits(); - }, FAIL_CLEAR_MS); - }; - - const applySuccess = () => { - const view = document.querySelector(".lock-view"); - setStatus("", false); - renderDots("is-success"); - view?.classList.add("is-success"); - playTone(1200, 66, 0.04); - pulseHaptic(24); - - window.setTimeout(() => { - state.locked = false; - state.activeView = "home"; - renderView("home"); - }, 240); - }; - - const submitDigits = async () => { - if (busy) { - return; - } - - if (digits.length !== config().length) { - setStatus(`Enter ${config().length} digits.`, true); - return; - } - - busy = true; - - if (!config().enabled) { - applySuccess(); - busy = false; - return; - } - - if (state.passkeySetupRequired) { - if (setupPhase === "create") { - setupDigits = [...digits]; - setupSeed = setupDigits.join("|"); - clearDigits(); - setupPhase = "confirm"; - updateCopy(); - setConfirmStatus(); - busy = false; - return; - } - - const confirmSeed = digits.join("|"); - const same = setupSeed.length > 0 - && setupDigits.length === digits.length - && setupDigits.every((digit, index) => digit === digits[index]) - && confirmSeed === setupSeed; - if (!same) { - setupPhase = "create"; - setupDigits = []; - setupSeed = ""; - updateCopy(); - applyFailure("Passkeys did not match. Try again."); - busy = false; - return; - } - - await state.passkey.setSequence(setupDigits); - state.passkeySetupRequired = false; - applySuccess(); - busy = false; - return; - } - - const result = await state.passkey.verifySequence(digits); - if (result.ok) { - applySuccess(); - busy = false; - return; - } - - if (result.reason === "lockout") { - applyFailure(lockoutText(result.lockoutRemainingMs ?? state.passkey.getLockoutRemainingMs())); - busy = false; - return; - } - - if (result.reason === "setup-required") { - state.passkeySetupRequired = true; - setupPhase = "create"; - setupDigits = []; - updateCopy(); - applyFailure("Passkey setup required."); - busy = false; - return; - } - - applyFailure(`Incorrect passkey. ${result.attemptsLeft ?? 0} attempts left.`); - busy = false; - }; - - const pushDigit = (digit) => { - if (busy || state.passkey.inLockout()) { - if (state.passkey.inLockout()) { - setStatus(lockoutText(state.passkey.getLockoutRemainingMs()), true); - } - return; - } - - const now = performance.now(); - if (now - lastEntryAt < ENTRY_DEBOUNCE_MS) { - return; - } - lastEntryAt = now; - - if (digits.length >= config().length) { - return; - } - - digits.push(String(digit)); - renderDots(); - setStatus(""); - playTone(); - pulseHaptic(8); - - if (digits.length === config().length && !config().requireConfirm) { - if (state.passkeySetupRequired) { - setConfirmStatus(); - } else { - submitDigits(); - } - } else if (digits.length === config().length && config().requireConfirm) { - setConfirmStatus(); - } - }; - - const deleteLast = () => { - if (!digits.length || busy) { - return; - } - digits = digits.slice(0, -1); - awaitingConfirm = false; - renderDots(); - setStatus(""); - }; - - const clearAll = () => { - if (!digits.length || busy) { - return; - } - clearDigits(); - setStatus(""); - }; - - const handleNumberKey = (event) => { - if (!config().keyboardSupport || !state.locked) { - return; - } - - const digitMatch = event.code.match(/^(Digit|Numpad)(\d)$/); - if (!digitMatch) { - return; - } - - event.preventDefault(); - pushDigit(digitMatch[2]); - }; - - return { - id: "lock", - render: () => LOCK_TEMPLATE, - mount: () => { - keyboard?.close?.(); - if (state.userSetupRequired) { - renderView("user-setup"); - return; - } - - setupPhase = "create"; - setupDigits = []; - setupSeed = ""; - clearDigits(); - updateCopy(); - setStatus(""); - - const username = document.querySelector("[data-username]"); - if (username) { - username.textContent = state.profileName ?? "Nebula User"; - } - - document.documentElement.style.setProperty("--nebula-focus-strength", "1"); - document.documentElement.dataset.animSpeed = config().animationSpeed; - document.documentElement.classList.toggle("high-contrast", config().highContrast); - updateMapLabels(); - updateControllerBadge(); - state.refreshControllerGlyphs?.(); - - if (keyboardListener) { - window.removeEventListener("keydown", keyboardListener); - } - keyboardListener = handleNumberKey; - window.addEventListener("keydown", keyboardListener); - - if (profileListener) { - window.removeEventListener("nebula-controller-profile", profileListener); - } - profileListener = () => { - updateMapLabels(); - updateCopy(); - updateControllerBadge(); - if (awaitingConfirm) { - setConfirmStatus(); - } - if (state.activeView === "lock") { - window.dispatchEvent(new CustomEvent("nebula-navigation-refresh")); - } - }; - window.addEventListener("nebula-controller-profile", profileListener); - }, - getNavigationContract: () => { - const root = document.querySelector("[data-focus-root]"); - const defaultFocus = root?.querySelector("[data-digit='5']") ?? root?.querySelector("[data-digit='1']") ?? null; - - return { - focusRoot: root, - defaultFocus, - layout: { type: "grid", cols: 3, rows: 4 }, - hintsTemplate: "#lock-hints-template", - nebulaNavigation: state.nebula.navigation, - captureDirectionalInput: true, - onAccept: (element) => { - const digit = element?.dataset.digit; - if (!digit) { - return; - } - pushDigit(digit); - }, - onBack: () => { - deleteLast(); - }, - onMenu: () => { - if (digits.length === config().length) { - submitDigits(); - return; - } - setStatus(`Enter ${config().length} digits first.`, true); - }, - onAction: (action) => { - const mappedDigit = ACTION_TO_DIGIT[action]; - if (mappedDigit) { - pushDigit(mappedDigit); - return true; - } - - return false; - }, - }; - }, - }; -}; diff --git a/Bigscreen/src/views/onboarding/userSetup.css b/Bigscreen/src/views/onboarding/userSetup.css deleted file mode 100644 index 50184de..0000000 --- a/Bigscreen/src/views/onboarding/userSetup.css +++ /dev/null @@ -1,84 +0,0 @@ -.user-setup-view { - justify-content: center; - align-items: center; -} - -.user-setup-layout { - width: min(760px, 94vw); - min-height: min(520px, 66vh); - display: flex; - align-items: center; - justify-content: center; - padding: clamp(24px, 3vw, 38px); -} - -.user-setup-main { - width: min(560px, 100%); - display: flex; - flex-direction: column; - gap: 16px; -} - -.user-setup-eyebrow { - margin: 0; - font-size: 13px; - letter-spacing: 0.08em; - text-transform: uppercase; - color: var(--nebula-color-muted); -} - -.user-setup-title { - margin: 0; - font-size: clamp(34px, 4vw, 48px); - font-weight: 560; -} - -.user-setup-copy { - margin: 0; - color: var(--nebula-color-muted); - font-size: 18px; - line-height: 1.45; -} - -.user-setup-fields { - display: grid; - gap: 10px; - margin-top: 10px; -} - -.user-setup-field { - border: 1px solid rgba(255, 255, 255, 0.12); - border-radius: 12px; - background: rgba(11, 19, 36, 0.58); - padding: 12px 14px; -} - -.user-setup-field.is-active { - border-color: rgba(79, 216, 255, 0.7); - box-shadow: 0 0 0 1px rgba(79, 216, 255, 0.35); -} - -.user-setup-label { - display: block; - font-size: 12px; - color: var(--nebula-color-muted); - margin-bottom: 6px; -} - -.user-setup-value { - margin: 0; - min-height: 24px; - font-size: 20px; -} - -.user-setup-status { - margin: 8px 0 0; - min-height: 22px; - font-size: 15px; - color: var(--nebula-color-muted); -} - -.user-setup-status.is-danger { - color: var(--nebula-color-danger); -} - diff --git a/Bigscreen/src/views/onboarding/userSetup.js b/Bigscreen/src/views/onboarding/userSetup.js deleted file mode 100644 index 3b5a131..0000000 --- a/Bigscreen/src/views/onboarding/userSetup.js +++ /dev/null @@ -1,203 +0,0 @@ -import { createUser } from "../../core/users.js"; - -const USER_SETUP_TEMPLATE = ` -
    -
    -
    -

    First Time Setup

    -

    Create Your Profile

    -

    Use your controller to enter your first and last name, then press Done.

    - -
    -
    - First Name -

    -

    -
    -
    - Last Name (Optional) -

    -

    -
    -
    - -

    -
    -
    -
    -`; - -export const createUserSetupView = ({ state, renderView, keyboard }) => { - const model = { - activeField: "firstName", - firstName: "", - lastName: "", - busy: false, - }; - - const setStatus = (text = "", danger = false) => { - const status = document.querySelector("[data-user-setup-status]"); - if (!status) { - return; - } - status.textContent = text; - status.classList.toggle("is-danger", danger); - }; - - const renderFields = () => { - const firstValue = document.querySelector("[data-field-value='firstName']"); - const lastValue = document.querySelector("[data-field-value='lastName']"); - if (firstValue) { - firstValue.textContent = model.firstName || "-"; - } - if (lastValue) { - lastValue.textContent = model.lastName || "-"; - } - - const firstCard = document.querySelector("[data-field-card='firstName']"); - const lastCard = document.querySelector("[data-field-card='lastName']"); - firstCard?.classList.toggle("is-active", model.activeField === "firstName"); - lastCard?.classList.toggle("is-active", model.activeField === "lastName"); - }; - - const appendCharacter = (character) => { - const field = model.activeField; - const maxLength = 20; - if (character === " ") { - if (!model[field]) { - return; - } - if (model[field].length >= maxLength) { - return; - } - model[field] += " "; - return; - } - if (model[field].length >= maxLength) { - return; - } - model[field] += character; - }; - - const backspaceCharacter = () => { - const field = model.activeField; - if (!model[field]) { - return; - } - model[field] = model[field].slice(0, -1); - }; - - const clearField = () => { - model[model.activeField] = ""; - }; - - const saveUser = async () => { - if (model.busy) { - return; - } - const firstName = model.firstName.trim(); - const lastName = model.lastName.trim(); - if (!firstName) { - setStatus("First name is required.", true); - return; - } - - model.busy = true; - setStatus("Creating profile..."); - try { - const user = await createUser({ firstName, lastName }); - if (!user) { - throw new Error("Could not create user."); - } - state.user = user; - state.profileName = user.name; - state.userSetupRequired = false; - // New profile creation should always start with a fresh passkey setup. - // This avoids inheriting stale passkey state from previous local data. - state.passkey.resetSequence(); - state.passkeySetupRequired = true; - state.activeView = "lock"; - keyboard.close(); - renderView("lock"); - } catch (error) { - setStatus(error?.message ?? "Failed to create user.", true); - } finally { - model.busy = false; - } - }; - - const selectField = (field) => { - model.activeField = field; - renderFields(); - }; - - const nextField = () => { - selectField(model.activeField === "firstName" ? "lastName" : "firstName"); - setStatus(`Editing ${model.activeField === "firstName" ? "First Name" : "Last Name"}.`); - }; - - const prevField = () => { - nextField(); - }; - - return { - id: "user-setup", - render: () => USER_SETUP_TEMPLATE, - mount: () => { - if (!state.userSetupRequired) { - keyboard.close(); - renderView("lock"); - return; - } - model.activeField = "firstName"; - model.firstName = ""; - model.lastName = ""; - model.busy = false; - renderFields(); - keyboard.open({ - onKey: (key) => { - appendCharacter(key); - renderFields(); - setStatus(""); - }, - onBackspace: () => { - backspaceCharacter(); - renderFields(); - setStatus(""); - }, - onClear: () => { - clearField(); - renderFields(); - setStatus(""); - }, - onSubmit: () => { - saveUser(); - }, - onPrevField: () => { - prevField(); - }, - onNextField: () => { - nextField(); - }, - }); - setStatus("Enter first name, then press Enter or Menu."); - }, - getNavigationContract: () => { - return { - focusRoot: null, - defaultFocus: null, - layout: { type: "grid", cols: 1, rows: 1 }, - hintsTemplate: "#keyboard-hints-template", - nebulaNavigation: state.nebula.navigation, - captureDirectionalInput: false, - onAccept: () => {}, - onBack: () => { - keyboard.handleAction("back"); - }, - onMenu: () => { - keyboard.handleAction("menu"); - return false; - }, - onAction: () => false, - }; - }, - }; -}; diff --git a/Bigscreen/src/views/overlays/guidePanel.css b/Bigscreen/src/views/overlays/guidePanel.css deleted file mode 100644 index 4637bd5..0000000 --- a/Bigscreen/src/views/overlays/guidePanel.css +++ /dev/null @@ -1,105 +0,0 @@ -.guide-panel-overlay { - position: fixed; - inset: 0; - z-index: 40; - display: grid; - place-items: center; - padding: var(--nebula-spacing-lg); - background: var(--nebula-color-overlay); -} - -.guide-panel-overlay[hidden] { - display: none !important; -} - -.guide-panel-sheet { - width: min(560px, 94vw); - max-height: min(80vh, 640px); - display: flex; - flex-direction: column; - gap: var(--nebula-spacing-md); -} - -.guide-panel-sheet-head { - margin: 0; -} - -.guide-panel-sheet-title { - margin: 0 0 var(--nebula-spacing-xs); -} - -.guide-panel-sheet-desc { - margin: 0; -} - -.guide-panel-sheet-body { - flex: 1; - min-height: 120px; -} - -.guide-panel-search-input { - width: 100%; - min-height: 52px; - padding: 0 var(--nebula-spacing-md); - border-radius: var(--nebula-radius-md); - border: 1px solid var(--nebula-color-border-mid); - background: var(--nebula-color-panel-alt); - color: var(--nebula-color-text); - font-size: 18px; -} - -.guide-panel-search-input:focus { - outline: 2px solid var(--nebula-color-accent); - outline-offset: 2px; -} - -.guide-panel-hint { - margin-top: var(--nebula-spacing-md); -} - -.guide-panel-placeholder-card { - padding: var(--nebula-spacing-lg); - border-radius: var(--nebula-radius-md); - border: 1px solid var(--nebula-color-border); - background: var(--nebula-color-panel-alt); -} - -.guide-panel-placeholder-title { - margin: 0 0 var(--nebula-spacing-sm); - font-size: 18px; - font-weight: 700; -} - -.guide-panel-placeholder-note { - margin: var(--nebula-spacing-md) 0 0; - font-size: 12px; - color: var(--nebula-color-muted); -} - -.guide-panel-close { - align-self: flex-end; - min-height: 48px; - padding: 0 var(--nebula-spacing-lg); - border: none; - border-radius: var(--nebula-radius-pill); - background: var(--nebula-color-panel-alt); - color: var(--nebula-color-text); - font-weight: 600; - cursor: pointer; -} - -.guide-panel-close.is-focused { - border: 2px solid var(--nebula-color-accent); -} - -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} diff --git a/Bigscreen/src/views/overlays/guidePanel.js b/Bigscreen/src/views/overlays/guidePanel.js deleted file mode 100644 index 78a4c43..0000000 --- a/Bigscreen/src/views/overlays/guidePanel.js +++ /dev/null @@ -1,152 +0,0 @@ -const PANEL_COPY = { - search: { - title: "Search", - description: "Search games, apps, and settings across NebulaOS.", - placeholder: "Search NebulaOS…", - }, - notifications: { - title: "Notifications", - description: "System alerts and download updates will appear here.", - }, - downloads: { - title: "Downloads", - description: "Active and completed downloads will be listed here.", - }, - controller: { - title: "Controller Settings", - description: "Map buttons, adjust dead zones, and test input.", - }, -}; - -export const createGuidePanelOverlay = ({ mountRoot }) => { - let openState = false; - let activePanel = null; - let onClose = null; - let overlay = null; - - const renderMarkup = () => { - mountRoot.insertAdjacentHTML( - "beforeend", - ` - - `, - ); - overlay = mountRoot.querySelector("[data-guide-panel-overlay]"); - }; - - const getBodyHtml = (panelId) => { - const copy = PANEL_COPY[panelId]; - if (!copy) { - return `

    This panel is not available yet.

    `; - } - - if (panelId === "search") { - return ` - -

    Results will appear here. (Placeholder)

    - `; - } - - return ` -
    -

    ${copy.title}

    -

    ${copy.description}

    -

    Connected to guide quick actions · stub UI

    -
    - `; - }; - - const close = () => { - openState = false; - activePanel = null; - if (overlay) { - overlay.hidden = true; - } - onClose?.(); - onClose = null; - }; - - const bindOverlayEvents = () => { - overlay?.addEventListener("click", (event) => { - if (event.target === overlay) { - close(); - } - }); - overlay?.querySelector(".guide-panel-close")?.addEventListener("click", () => close()); - }; - - const open = (panelId, options = {}) => { - if (!overlay) { - renderMarkup(); - bindOverlayEvents(); - } - - const copy = PANEL_COPY[panelId] ?? { title: "Panel", description: "" }; - overlay.querySelector("[data-panel-title]").textContent = copy.title; - overlay.querySelector("[data-panel-desc]").textContent = copy.description ?? ""; - overlay.querySelector("[data-panel-body]").innerHTML = getBodyHtml(panelId); - - openState = true; - activePanel = panelId; - onClose = options.onClose ?? null; - overlay.hidden = false; - - const closeBtn = overlay.querySelector("[data-guide-panel-close], .guide-panel-close"); - closeBtn?.focus({ preventScroll: true }); - closeBtn?.classList.add("is-focused"); - - console.log(`[GuidePanel] Opened: ${panelId}`); - }; - - const handleAction = (action) => { - if (!openState) { - return false; - } - - if (action === "accept") { - close(); - return true; - } - - if (action === "back" || action === "menu") { - close(); - return true; - } - - return true; - }; - - return { - open, - close, - isOpen: () => openState, - getActivePanel: () => activePanel, - handleAction, - }; -}; diff --git a/Bigscreen/src/views/overlays/keyboard.css b/Bigscreen/src/views/overlays/keyboard.css deleted file mode 100644 index 55ba1a1..0000000 --- a/Bigscreen/src/views/overlays/keyboard.css +++ /dev/null @@ -1,95 +0,0 @@ -.overlay-keyboard { - position: fixed; - left: 0; - right: 0; - bottom: 0; - z-index: 18; - padding: 10px 16px 14px; - pointer-events: none; -} - -.overlay-keyboard[hidden] { - display: none !important; -} - -.overlay-keyboard-inner { - width: min(1180px, 98vw); - margin: 0 auto; - border-radius: 14px; - border: 1px solid rgba(255, 255, 255, 0.12); - background: - linear-gradient(170deg, rgba(46, 74, 118, 0.46), rgba(14, 25, 43, 0.88)), - rgba(10, 17, 30, 0.9); - box-shadow: - 0 16px 30px rgba(0, 0, 0, 0.35), - inset 0 1px 0 rgba(255, 255, 255, 0.08); - backdrop-filter: blur(10px); - padding: 10px; -} - -.overlay-keyboard-rows { - display: flex; - flex-direction: column; - gap: 6px; -} - -.overlay-keyboard-row { - display: flex; - gap: 6px; - align-items: stretch; -} - -.overlay-keyboard-row.is-space-row { - justify-content: center; - padding-top: 2px; -} - -.overlay-keyboard-key { - flex: 1 1 0; - min-width: 0; - height: 40px; - border: 1px solid rgba(255, 255, 255, 0.16); - border-radius: 8px; - background: linear-gradient(160deg, rgba(60, 95, 143, 0.38), rgba(13, 24, 42, 0.84)); - color: var(--nebula-color-text); - font-size: 13px; - font-weight: 650; - padding: 0 6px; -} - -.overlay-keyboard-key[data-width="2"] { - flex: 1.55 1 0; -} - -.overlay-keyboard-key.is-space { - flex: 0 1 70%; - max-width: 760px; - min-height: 42px; -} - -.overlay-keyboard-key.is-modifier, -.overlay-keyboard-key.is-enter { - font-size: 11px; - letter-spacing: 0.02em; -} - -.overlay-keyboard-key.is-modifier.is-active { - border-color: rgba(79, 216, 255, 0.55); - background: linear-gradient(160deg, rgba(79, 216, 255, 0.28), rgba(13, 24, 42, 0.9)); - color: rgba(79, 216, 255, 0.95); -} - -.overlay-keyboard.is-uppercase .overlay-keyboard-key.is-letter { - color: rgba(220, 240, 255, 0.98); -} - -.overlay-keyboard-key.is-enter { - border-color: rgba(122, 255, 168, 0.46); -} - -.overlay-keyboard-key.is-focused { - box-shadow: - 0 0 0 2px rgba(79, 216, 255, 0.5), - 0 0 20px rgba(79, 216, 255, 0.22); - transform: translateY(-1px); -} diff --git a/Bigscreen/src/views/overlays/keyboard.js b/Bigscreen/src/views/overlays/keyboard.js deleted file mode 100644 index bd0b2d6..0000000 --- a/Bigscreen/src/views/overlays/keyboard.js +++ /dev/null @@ -1,312 +0,0 @@ -const letter = (ch) => ({ id: ch.toUpperCase(), label: ch }); -const symbol = (ch) => ({ id: ch, label: ch }); - -const KEY_ROWS = [ - [ - symbol("'"), - ..."1234567890".split("").map((ch) => symbol(ch)), - symbol("-"), - symbol("="), - { id: "BACKSP", label: "Backsp", width: 2 }, - ], - [ - { id: "TAB", label: "Tab", width: 2 }, - ..."qwertyuiop".split("").map(letter), - symbol("["), - symbol("]"), - { id: "\\", label: "\\", width: 2 }, - ], - [ - { id: "CAPS", label: "Caps", width: 2 }, - ..."asdfghjkl".split("").map(letter), - symbol(";"), - symbol("#"), - { id: "ENTER", label: "Enter", width: 2 }, - ], - [ - { id: "SHIFT", label: "Shift", width: 2 }, - ..."zxcvbnm".split("").map(letter), - symbol(","), - symbol("."), - symbol("/"), - { id: "SHIFT_R", label: "Shift", width: 2 }, - ], - [{ id: "SPACE", label: "", width: 1, space: true }], -]; - -const KEYBOARD_TEMPLATE = ` - -`; - -const clamp = (value, min, max) => Math.max(min, Math.min(max, value)); - -const isLetter = (key) => /^[A-Z]$/.test(key); - -export const createKeyboardOverlay = ({ mountRoot }) => { - mountRoot.innerHTML = KEYBOARD_TEMPLATE; - - const root = mountRoot.querySelector("[data-overlay-keyboard]"); - const rowsRoot = mountRoot.querySelector("[data-overlay-keyboard-rows]"); - const keyButtons = []; - let openState = false; - let selectedRow = 0; - let selectedCol = 0; - let handlers = null; - let capsLock = false; - let shiftActive = false; - let capsButton = null; - const shiftButtons = []; - - const rowWidth = (rowIndex) => KEY_ROWS[rowIndex]?.length ?? 0; - - const lettersAreUppercase = () => capsLock !== shiftActive; - - const applySelection = () => { - keyButtons.forEach((button) => button.classList.remove("is-focused")); - const selected = keyButtons.find( - (button) => - Number(button.dataset.row) === selectedRow && Number(button.dataset.col) === selectedCol, - ); - selected?.classList.add("is-focused"); - }; - - const currentKey = () => { - const selected = keyButtons.find( - (button) => - Number(button.dataset.row) === selectedRow && Number(button.dataset.col) === selectedCol, - ); - return selected?.dataset.key ?? null; - }; - - const updateModifierState = () => { - const uppercase = lettersAreUppercase(); - - root?.classList.toggle("is-uppercase", uppercase); - capsButton?.classList.toggle("is-active", capsLock); - shiftButtons.forEach((button) => button.classList.toggle("is-active", shiftActive)); - - keyButtons.forEach((button) => { - const key = button.dataset.key; - if (!isLetter(key)) { - return; - } - const base = button.dataset.baseLabel ?? key.toLowerCase(); - button.textContent = uppercase ? base.toUpperCase() : base; - }); - }; - - const resolveOutput = (key) => { - if (key === "SPACE") { - return " "; - } - if (isLetter(key)) { - return lettersAreUppercase() ? key : key.toLowerCase(); - } - return key; - }; - - const consumeShift = () => { - if (!shiftActive) { - return; - } - shiftActive = false; - updateModifierState(); - }; - - const build = () => { - if (!rowsRoot) { - return; - } - rowsRoot.innerHTML = ""; - keyButtons.length = 0; - capsButton = null; - shiftButtons.length = 0; - - KEY_ROWS.forEach((row, rowIndex) => { - const rowEl = document.createElement("div"); - rowEl.className = "overlay-keyboard-row"; - if (row.some((key) => key.space)) { - rowEl.classList.add("is-space-row"); - } - - row.forEach((keyDef, colIndex) => { - const button = document.createElement("button"); - button.type = "button"; - button.className = "overlay-keyboard-key"; - button.dataset.row = String(rowIndex); - button.dataset.col = String(colIndex); - button.dataset.key = keyDef.id; - button.dataset.width = String(keyDef.width ?? 1); - if (isLetter(keyDef.id)) { - button.dataset.baseLabel = (keyDef.label ?? keyDef.id).toLowerCase(); - button.classList.add("is-letter"); - } - button.textContent = keyDef.label ?? keyDef.id; - - if (keyDef.width && keyDef.width > 1) { - button.classList.add("is-wide"); - } - if (keyDef.space) { - button.classList.add("is-space"); - button.setAttribute("aria-label", "Space"); - } - if (keyDef.id === "ENTER") { - button.classList.add("is-enter"); - } - if (keyDef.id === "CAPS") { - button.classList.add("is-modifier"); - capsButton = button; - } - if (keyDef.id === "SHIFT" || keyDef.id === "SHIFT_R") { - button.classList.add("is-modifier"); - shiftButtons.push(button); - } - - rowEl.append(button); - keyButtons.push(button); - }); - - rowsRoot.append(rowEl); - }); - - updateModifierState(); - }; - - build(); - - const open = (nextHandlers = {}) => { - handlers = nextHandlers; - openState = true; - capsLock = false; - shiftActive = false; - selectedRow = 2; - selectedCol = 1; - root.hidden = false; - updateModifierState(); - applySelection(); - }; - - const close = () => { - openState = false; - root.hidden = true; - handlers = null; - capsLock = false; - shiftActive = false; - updateModifierState(); - }; - - const move = (direction) => { - if (direction === "left") { - selectedCol = clamp(selectedCol - 1, 0, Math.max(0, rowWidth(selectedRow) - 1)); - return; - } - if (direction === "right") { - selectedCol = clamp(selectedCol + 1, 0, Math.max(0, rowWidth(selectedRow) - 1)); - return; - } - if (direction === "up") { - selectedRow = clamp(selectedRow - 1, 0, KEY_ROWS.length - 1); - selectedCol = clamp(selectedCol, 0, Math.max(0, rowWidth(selectedRow) - 1)); - return; - } - if (direction === "down") { - selectedRow = clamp(selectedRow + 1, 0, KEY_ROWS.length - 1); - selectedCol = clamp(selectedCol, 0, Math.max(0, rowWidth(selectedRow) - 1)); - } - }; - - const activateKey = (key) => { - if (!key) { - return; - } - - if (key === "ENTER") { - handlers?.onSubmit?.(); - return; - } - if (key === "BACKSP") { - handlers?.onBackspace?.(); - return; - } - if (key === "TAB") { - handlers?.onNextField?.(); - return; - } - if (key === "CAPS") { - capsLock = !capsLock; - updateModifierState(); - return; - } - if (key === "SHIFT" || key === "SHIFT_R") { - shiftActive = !shiftActive; - updateModifierState(); - return; - } - if (key === "SPACE") { - handlers?.onKey?.(" "); - consumeShift(); - return; - } - - const output = resolveOutput(key); - if (output) { - handlers?.onKey?.(output); - consumeShift(); - } - }; - - const handleAction = (action) => { - if (!openState) { - return false; - } - - if (action === "up" || action === "down" || action === "left" || action === "right") { - move(action); - applySelection(); - return true; - } - - if (action === "accept") { - activateKey(currentKey()); - return true; - } - - if (action === "back") { - handlers?.onBackspace?.(); - return true; - } - - if (action === "menu") { - handlers?.onSubmit?.(); - return true; - } - - if (action === "clear") { - handlers?.onClear?.(); - return true; - } - - if (action === "l1") { - handlers?.onPrevField?.(); - return true; - } - - if (action === "r1") { - handlers?.onNextField?.(); - return true; - } - - return false; - }; - - return { - open, - close, - isOpen: () => openState, - handleAction, - }; -}; diff --git a/Bigscreen/src/views/overlays/powerMenu.css b/Bigscreen/src/views/overlays/powerMenu.css deleted file mode 100644 index 224ff2f..0000000 --- a/Bigscreen/src/views/overlays/powerMenu.css +++ /dev/null @@ -1,34 +0,0 @@ -.power-overlay { - position: fixed; - inset: 0; - z-index: 20; - display: grid; - place-items: center; - background: var(--nebula-color-overlay); -} - -.power-overlay[hidden] { - display: none !important; -} - -.power-panel { - width: min(520px, 92vw); -} - -.power-title { - margin: 0 0 var(--nebula-spacing-md); -} - -.power-options { - display: grid; - gap: var(--nebula-spacing-sm); -} - -.power-option { - min-height: 62px; - border: none; - background: var(--nebula-color-panelAlt); - color: var(--nebula-color-text); - text-align: left; - padding: var(--nebula-spacing-md); -} diff --git a/Bigscreen/src/views/overlays/powerMenu.html b/Bigscreen/src/views/overlays/powerMenu.html deleted file mode 100644 index b13bafa..0000000 --- a/Bigscreen/src/views/overlays/powerMenu.html +++ /dev/null @@ -1,12 +0,0 @@ - diff --git a/Bigscreen/src/views/overlays/powerMenu.js b/Bigscreen/src/views/overlays/powerMenu.js deleted file mode 100644 index 6a4c474..0000000 --- a/Bigscreen/src/views/overlays/powerMenu.js +++ /dev/null @@ -1,107 +0,0 @@ -const POWER_MENU_TEMPLATE = ` - -`; - -const ACTION_LOG = { - sleep: "Sleep requested (stub)", - "restart-nebula": "Restart NebulaOS requested (stub)", - "restart-system": "Restart System requested (stub)", - shutdown: "Shut Down requested (stub)", -}; - -export const createPowerMenuOverlay = ({ mountRoot }) => { - mountRoot.insertAdjacentHTML("beforeend", POWER_MENU_TEMPLATE); - - const overlay = mountRoot.querySelector("[data-power-overlay]"); - const focusables = Array.from(overlay?.querySelectorAll("[data-focusable='true']") ?? []); - let focusedIndex = 0; - let openState = false; - let onClose = null; - - if (overlay) { - overlay.hidden = true; - } - - const focusAt = (index) => { - focusables.forEach((element) => { - element.classList.remove("is-focused"); - element.setAttribute("aria-selected", "false"); - }); - - focusedIndex = Math.max(0, Math.min(index, focusables.length - 1)); - const target = focusables[focusedIndex]; - target.classList.add("is-focused"); - target.setAttribute("aria-selected", "true"); - target.focus({ preventScroll: true }); - }; - - const close = () => { - openState = false; - overlay.hidden = true; - onClose?.(); - onClose = null; - }; - - const open = (options = {}) => { - openState = true; - onClose = options.onClose ?? null; - overlay.hidden = false; - focusAt(0); - }; - - const runAction = (action) => { - if (action === "cancel") { - close(); - return; - } - - if (ACTION_LOG[action]) { - console.log(`[PowerMenu] ${ACTION_LOG[action]}`); - close(); - } - }; - - const handleAction = (action) => { - if (!openState) { - return; - } - - if (action === "up") { - focusAt(focusedIndex - 1); - return; - } - - if (action === "down") { - focusAt(focusedIndex + 1); - return; - } - - if (action === "accept") { - const current = focusables[focusedIndex]; - runAction(current?.dataset.action ?? "cancel"); - return; - } - - if (action === "back" || action === "menu") { - close(); - } - }; - - return { - open, - close, - isOpen: () => openState, - handleAction, - }; -}; diff --git a/Bigscreen/src/views/placeholder/placeholder.js b/Bigscreen/src/views/placeholder/placeholder.js deleted file mode 100644 index ffd7b7d..0000000 --- a/Bigscreen/src/views/placeholder/placeholder.js +++ /dev/null @@ -1,50 +0,0 @@ -export const createPlaceholderView = (context, { id, title, subtitle, backTarget = "home" }) => ({ - id, - render: () => ` -
    -
    -
    -

    Nebula OS

    -
    - -

    --:--

    -
    -
    -
    -
    -
    -

    ${subtitle}

    -

    ${title}

    -

    This area is part of the NebulaOS shell roadmap. Navigation is wired; content ships in a future update.

    - -
    -
    - `, - mount() {}, - getNavigationContract: () => { - const focusRoot = document.querySelector(`[data-view="${id}"] [data-focus-root]`); - const defaultFocus = focusRoot?.querySelector("[data-focusable='true']"); - return { - focusRoot, - defaultFocus, - hintsTemplate: "#global-hints-template", - onAccept(focused) { - const target = focused?.dataset?.target; - if (target) { - context.renderView(target); - } - }, - onBack() { - context.renderView(backTarget); - }, - }; - }, -}); diff --git a/Bigscreen/src/views/settings/settings.css b/Bigscreen/src/views/settings/settings.css deleted file mode 100644 index 6cc4074..0000000 --- a/Bigscreen/src/views/settings/settings.css +++ /dev/null @@ -1,181 +0,0 @@ -.settings-view { - gap: var(--nebula-spacing-xl); -} - -.settings-header-copy { - display: grid; - gap: var(--nebula-spacing-xs); - padding-left: var(--nebula-spacing-xl); -} - -.settings-header-copy .muted { - margin: 0; - letter-spacing: 0.14em; - text-transform: uppercase; - font-size: 14px; - font-weight: 600; - opacity: 0.72; -} - -.settings-header-copy .view-title { - font-size: clamp(32px, 3.2vw, 44px); - font-weight: 700; - letter-spacing: -0.02em; -} - -.settings-body { - display: grid; - gap: var(--nebula-spacing-lg); - padding: 0 var(--nebula-spacing-xl); -} - -.settings-category-bar { - display: flex; - gap: 10px; - padding-bottom: var(--nebula-spacing-md); - border-bottom: 2px solid rgba(79, 216, 255, 0.15); - position: relative; -} - -.settings-category { - min-height: 56px; - padding: 0 24px; - border-radius: var(--nebula-radius-sm); - border: none; - background: rgba(24, 38, 68, 0.5); - font-weight: 660; - font-size: 16px; - position: relative; - transition: - background-color var(--nebula-duration-nav) var(--nebula-ease-console), - color var(--nebula-duration-nav) var(--nebula-ease-console); -} - -.settings-category.is-focused { - background: rgba(38, 58, 98, 0.7); -} - -.settings-category.is-active { - background: rgba(50, 78, 128, 0.8); - color: var(--nebula-color-accent); -} - -.settings-category::after { - content: ""; - position: absolute; - left: 12px; - right: 12px; - bottom: -14px; - height: 3px; - background: linear-gradient(90deg, var(--nebula-color-accent), rgba(79, 216, 255, 0.6)); - border-radius: 2px 2px 0 0; - box-shadow: 0 0 12px rgba(79, 216, 255, 0.6); - transform: scaleX(0); - transform-origin: center; - transition: transform var(--nebula-duration-nav) var(--nebula-ease-console); -} - -.settings-category.is-active::after { - transform: scaleX(1); -} - -.settings-panel { - display: flex; - flex-direction: column; - gap: var(--nebula-spacing-lg); - min-height: 440px; - background: - linear-gradient(160deg, rgba(65, 108, 189, 0.18), rgba(24, 36, 72, 0.65)), - radial-gradient(circle at 75% 25%, rgba(79, 216, 255, 0.08), transparent 48%), - var(--nebula-color-panel); - border-radius: var(--nebula-radius-md); - padding: var(--nebula-spacing-lg); - box-shadow: - 0 8px 24px rgba(2, 6, 18, 0.35), - inset 0 1px 0 rgba(255, 255, 255, 0.08); -} - -.settings-panel-head { - display: grid; - gap: 8px; - padding-bottom: var(--nebula-spacing-md); - border-bottom: 1px solid rgba(79, 216, 255, 0.12); -} - -.settings-panel-title { - margin: 0; - font-size: 28px; - font-weight: 720; - letter-spacing: -0.01em; -} - -.settings-card-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: var(--nebula-spacing-md); -} - -.settings-card { - border: 1px solid rgba(79, 216, 255, 0.15); - border-radius: var(--nebula-radius-md); - padding: var(--nebula-spacing-lg); - min-height: 180px; - background: - linear-gradient(165deg, rgba(70, 108, 186, 0.28), rgba(22, 34, 68, 0.88)); - text-align: left; - display: grid; - align-content: space-between; - position: relative; - overflow: hidden; - transition: - transform var(--nebula-duration-nav) var(--nebula-ease-console), - border-color var(--nebula-duration-nav) var(--nebula-ease-console); -} - -.settings-card::before { - content: ""; - position: absolute; - inset: 0; - background: linear-gradient(135deg, rgba(79, 216, 255, 0.12), transparent 60%); - opacity: 0; - transition: opacity var(--nebula-duration-nav) var(--nebula-ease-console); -} - -.settings-card.is-focused { - transform: scale(1.04); - border-color: rgba(79, 216, 255, 0.5); -} - -.settings-card.is-focused::before { - opacity: 1; -} - -.settings-card-title { - margin: 0; - font-size: 20px; - font-weight: 680; - letter-spacing: -0.01em; - position: relative; - z-index: 1; -} - -.settings-card p { - margin: 0; - position: relative; - z-index: 1; -} - -.settings-card-info { - border-style: dashed; - opacity: 0.85; -} - -.settings-card.is-disabled { - opacity: 0.55; - border-style: dashed; - filter: grayscale(0.15); -} - -.settings-card.is-disabled.is-focused { - transform: none; -} diff --git a/Bigscreen/src/views/settings/settings.html b/Bigscreen/src/views/settings/settings.html deleted file mode 100644 index e36e1e0..0000000 --- a/Bigscreen/src/views/settings/settings.html +++ /dev/null @@ -1,48 +0,0 @@ -
    -
    -
    -

    Nebula OS

    -
    - -

    --:--

    -
    -
    -
    -
    - -
    -

    System

    -

    Settings

    -
    - -
    -
    - - - - - -
    - -
    -
    -

    Network

    -

    Optimize connection and online routing.

    -
    -
    - - -
    -

    Status

    -

    Applying Nebula profile

    -
    -
    -
    -
    -
    diff --git a/Bigscreen/src/views/settings/settings.js b/Bigscreen/src/views/settings/settings.js deleted file mode 100644 index 1be1101..0000000 --- a/Bigscreen/src/views/settings/settings.js +++ /dev/null @@ -1,228 +0,0 @@ -const SETTINGS_TEMPLATE = ` -
    -
    -
    -

    Nebula OS

    -
    - -

    --:--

    -
    -
    -
    -
    - -
    -

    System

    -

    Settings

    -
    - -
    -
    - - - - - -
    - -
    -
    -

    Network

    -

    Optimize connection and online routing.

    -
    -
    - - - - - -
    -

    Status

    -

    Applying Nebula profile

    -
    -
    -
    -
    -
    -`; - -const CATEGORIES = { - network: "Network", - audio: "Audio", - display: "Display", - storage: "Storage", - system: "System", -}; - -export const createSettingsView = ({ state, renderView }) => { - const updatePasskey = (partial) => { - state.passkey.updateConfig(partial); - refreshPanel(); - }; - - const refreshPanel = () => { - const title = document.querySelector("[data-panel-title]"); - const copy = document.querySelector("[data-panel-copy]"); - const passkeyEnabled = document.querySelector("[data-passkey-enabled]"); - const passkeyLength = document.querySelector("[data-passkey-length]"); - const passkeyConfirm = document.querySelector("[data-passkey-confirm]"); - const passkeyKeyboard = document.querySelector("[data-passkey-keyboard]"); - const status = document.querySelector("[data-status-note]"); - const passkeyConfig = state.passkey.getConfig(); - - document.querySelectorAll(".settings-category").forEach((button) => { - button.classList.toggle("is-active", button.dataset.cat === state.settingsCategory); - }); - - if (title) { - title.textContent = CATEGORIES[state.settingsCategory] ?? "Settings"; - } - if (copy) { - if (state.settingsCategory === "system") { - copy.textContent = "Configure passkey security and controller login behavior."; - } else { - copy.textContent = `Tune ${CATEGORIES[state.settingsCategory] ?? "system"} options with controller-first cards.`; - } - } - if (passkeyEnabled) { - passkeyEnabled.textContent = passkeyConfig.enabled ? "Enabled" : "Disabled"; - } - if (passkeyLength) { - passkeyLength.textContent = `${passkeyConfig.length} digits`; - } - if (passkeyConfirm) { - passkeyConfirm.textContent = passkeyConfig.requireConfirm ? "Enabled" : "Disabled"; - } - if (passkeyKeyboard) { - passkeyKeyboard.textContent = passkeyConfig.keyboardSupport ? "Enabled" : "Disabled"; - } - if (status) { - status.textContent = state.settingsCategory === "system" - ? `Attempts: ${passkeyConfig.maxAttempts}, cooldown: ${passkeyConfig.cooldownSeconds}s` - : `${CATEGORIES[state.settingsCategory] ?? "System"} profile synced`; - } - - document.querySelectorAll("[data-toggle^='passkey']").forEach((button) => { - button.classList.toggle("is-disabled", state.settingsCategory !== "system"); - button.setAttribute("aria-disabled", state.settingsCategory === "system" ? "false" : "true"); - }); - }; - - const toggleCurrent = (suffix = "") => { - const key = suffix ? `${state.settingsCategory}_${suffix}` : state.settingsCategory; - state.settingsValues[key] = !Boolean(state.settingsValues[key]); - refreshPanel(); - }; - - return { - id: "settings", - render: () => SETTINGS_TEMPLATE, - mount: () => { - const root = document.querySelector("[data-focus-root]"); - root?.addEventListener("focusin", (event) => { - const focused = event.target.closest("[data-focusable='true']"); - const col = Number(focused?.dataset.col ?? 0); - document.documentElement.style.setProperty("--nebula-accent-line-x", `${24 + col * 110}px`); - }); - refreshPanel(); - document.documentElement.style.setProperty("--nebula-focus-strength", "1"); - }, - getNavigationContract: () => { - const root = document.querySelector("[data-focus-root]"); - return { - focusRoot: root, - defaultFocus: root?.querySelector("[data-cat='network']") ?? null, - layout: { type: "grid", cols: 5, rows: 2 }, - hintsTemplate: "#global-hints-template", - nebulaNavigation: state.nebula.navigation, - onAccept: (element) => { - if (!element) { - return; - } - - const category = element.dataset.cat; - const toggle = element.dataset.toggle; - - if (category) { - state.settingsCategory = category; - refreshPanel(); - return; - } - - if (toggle === "primary") { - toggleCurrent(); - return; - } - - if (toggle === "secondary") { - toggleCurrent("secondary"); - return; - } - - if (state.settingsCategory !== "system") { - return; - } - - if (toggle === "passkey-enabled") { - updatePasskey({ enabled: !state.passkey.getConfig().enabled }); - return; - } - - if (toggle === "passkey-change") { - state.passkey.resetSequence(); - state.passkeySetupRequired = true; - state.locked = true; - state.activeView = "lock"; - renderView("lock"); - return; - } - - if (toggle === "passkey-length") { - return; - } - - if (toggle === "passkey-confirm") { - updatePasskey({ requireConfirm: !state.passkey.getConfig().requireConfirm }); - return; - } - - if (toggle === "passkey-keyboard") { - updatePasskey({ keyboardSupport: !state.passkey.getConfig().keyboardSupport }); - } - }, - onBack: () => { - state.activeView = "home"; - renderView("home"); - }, - onMenu: () => {}, - onAction: (action, element) => { - if (state.settingsCategory !== "system") { - return false; - } - - if (element?.dataset.toggle !== "passkey-length") { - return false; - } - - return false; - }, - }; - }, - }; -};