Compare commits

..

2 Commits

Author SHA1 Message Date
SpikeHD
7c086828dc fix: add lockfile 2023-11-29 19:33:37 -08:00
SpikeHD
70999c093b fix: remove libs and use preact-router 2023-11-29 19:33:21 -08:00
20 changed files with 2156 additions and 1656 deletions

View File

@@ -11,10 +11,8 @@
},
"dependencies": {
"@tauri-apps/api": "^1.5.1",
"color.js": "^1.2.0",
"preact": "^10.16.0",
"preact-router": "^4.1.2",
"react-icons": "^4.12.0",
"zustand": "^4.4.6"
},
"devDependencies": {

2108
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,3 +2,4 @@
# will have compiled files and executables
/target/
WixTools/**/*

1289
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,11 +13,9 @@ edition = "2021"
tauri-build = { version = "1.5", features = [] }
[dependencies]
tauri = { version = "1.5", features = ["api-all"] }
tauri = { version = "1.5", features = ["shell-open"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
reqwest = "0.11.22"
window-shadows = "0.2.2"
[features]
# this feature is used for production builds or when `devPath` points to the filesystem

View File

@@ -1,27 +0,0 @@
use std::fs::File;
use std::io::Write;
use std::path::Path;
/// Downloads a file from the given URL.
/// Saves it to the specified file.
/// This will overwrite the file if it already exists.
/// url: The URL to download from.
/// to_file: The file to save to.
#[tauri::command]
pub async fn download_file(url: String, to_file: String) -> Result<String, String> {
let mut response = reqwest::get(&url).await
.expect("Failed to send request");
let mut dest = {
let fname = Path::new(&to_file);
match File::create(&fname) {
Ok(f) => f,
Err(_) => return Err("Failed to create file".to_string()),
}
};
while let Some(chunk) = response.chunk().await
.expect("Unable to read chunk") {
dest.write_all(&chunk)
.expect("Failed to write chunk to file")
}
Ok("Downloaded".to_string())
}

View File

@@ -1,37 +1,15 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use std::fs::create_dir;
use tauri::Manager;
use window_shadows::set_shadow;
mod http;
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
http::download_file
])
.setup(|app| {
// Create base directories.
let app_data_dir = app.path_resolver()
.app_data_dir()
.expect("Failed to get app data directory");
let background_directory = app_data_dir.join("bg");
if !background_directory.exists() {
create_dir(background_directory.as_path())
.expect("Failed to create app data directory");
}
// Enable window shadows.
let main_window = app.get_window("main")
.expect("Unable to fetch Tauri window.");
#[cfg(any(windows, target_os = "macos"))]
set_shadow(&main_window, true).unwrap();
Ok(())
})
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@@ -7,27 +7,20 @@
},
"package": {
"productName": "Cultivation",
"version": "0.1.0"
"version": "0.0.0"
},
"tauri": {
"allowlist": {
"fs": {
"scope": ["$DATA", "$DATA/io.grasscutter.cultivation", "$DATA/io.grasscutter.cultivation/**"]
},
"protocol": {
"all": true,
"asset": true,
"assetScope": ["$DATA", "$DATA/io.grasscutter.cultivation", "$DATA/io.grasscutter.cultivation/**"]
},
"http": {
"scope": ["https://*.*/**"]
},
"all": true
"all": false,
"shell": {
"all": false,
"open": true
}
},
"bundle": {
"active": true,
"targets": "all",
"identifier": "io.grasscutter.cultivation",
"identifier": "com.tauri.dev",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
@@ -43,7 +36,6 @@
{
"fullscreen": false,
"resizable": true,
"decorations": false,
"title": "Cultivation",
"width": 1280,
"height": 730

View File

@@ -1,122 +0,0 @@
import { useEffect, useState } from "preact/hooks";
import useSettings from "@backend/stores/settings.ts";
import { create } from "zustand";
import { persist, createJSONStorage } from "zustand/middleware";
import { invoke } from "@tauri-apps/api";
import { fetch } from "@tauri-apps/api/http";
import { exists } from "@tauri-apps/api/fs";
import { convertFileSrc } from "@tauri-apps/api/tauri";
import { prominent } from "color.js";
import { LauncherResponse, StoreWrite, SupportedGames } from "@backend/types.ts";
import { AppDataPath, LauncherUrls } from "@app/constants.ts";
export type GameDataStore = {
backgroundHash: string;
fetchLatestBackground: () => Promise<void>;
};
export const useGenshinStore = create<GameDataStore>()(
persist(
(set): GameDataStore => ({
backgroundHash: "",
fetchLatestBackground: () => fetchLatestBackground(
set as StoreWrite, LauncherUrls.GENSHIN_IMPACT)
}),
{
name: "genshin_data",
storage: createJSONStorage(() => localStorage)
}
)
);
export const useStarRailStore = create<GameDataStore>()(
persist(
(set) => ({
backgroundHash: "",
fetchLatestBackground: () => fetchLatestBackground(
set as StoreWrite, LauncherUrls.STAR_RAIL)
}),
{
name: "starrail_data",
storage: createJSONStorage(() => localStorage)
}
)
);
/**
* Fetches the latest background image.
*
* @param set The store write function.
* @param serviceUrl The URL used for fetching the background.
*/
export async function fetchLatestBackground(set: StoreWrite, serviceUrl: string): Promise<void> {
// Fetch the launcher data.
const launcherData = await fetch<LauncherResponse>(serviceUrl);
const responseData = launcherData.data;
// Check if the background exists on the system.
const backgroundUrl = responseData.data.adv.background;
const backgroundHash = backgroundUrl.split("/").pop()?.substring(0, 32);
if (backgroundHash == undefined) throw new Error("Unable to find the hash of the background.");
// Check if the file exists on the system.
const filePath = `${AppDataPath}/bg/${backgroundHash}.png`;
if (!await exists(filePath)) {
// Download the image.
await invoke("download_file", {
url: backgroundUrl, toFile: filePath
});
// Update the store.
set({ backgroundHash });
}
}
/**
* Attempts to get the file of the background.
*
* @param hash The hash of the background.
*/
export async function getBackgroundFile(hash: string): Promise<string> {
return convertFileSrc(`${AppDataPath}/bg/${hash}.png`);
}
type BackgroundData = {
url: string;
colors: string[];
}
/**
* React hook which returns the URL of the locally cached background image.
*/
export function useBackground(): BackgroundData {
const { selectedGame } = useSettings();
const { backgroundHash, fetchLatestBackground } =
selectedGame == SupportedGames.GenshinImpact ? useGenshinStore() : useStarRailStore();
const [background, setBackground] = useState<string | null>(null);
const [colorPalette, setColorPalette] = useState<string[] | null>(null);
useEffect(() => {
(async () => {
if (backgroundHash != "") {
const filePath = await getBackgroundFile(backgroundHash);
setBackground(filePath);
setColorPalette(await prominent(filePath,
{ amount: 5, format: "hex" }) as string[]);
} else {
fetchLatestBackground();
}
})();
}, [backgroundHash, fetchLatestBackground]);
return {
url: background ?? "",
colors: colorPalette ?? []
};
}

View File

@@ -1,26 +0,0 @@
import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";
import { SupportedGames } from "@backend/types.ts";
export type SettingsStore = {
selectedGame: SupportedGames | any;
setGame: (game: SupportedGames) => void;
};
const useSettings = create<SettingsStore>()(
persist(
(set) => ({
selectedGame: SupportedGames.GenshinImpact,
setGame: (selectedGame) => set({ selectedGame })
}),
{
name: "settings",
storage: createJSONStorage(() => localStorage)
}
)
);
export default useSettings;

View File

@@ -1,74 +0,0 @@
export type StoreWrite = (partial: unknown, replace?: boolean | undefined) => void;
export type SDKResponse = {
retcode: number;
message: string;
};
export type LauncherResponse = SDKResponse & {
data: {
adv: BackgroundData; // This is the background shown to the user.
banner: NewsFeedData[]; // These are shown in a slideshow-style card.
icon: IconData[]; // These are shown in the right sidebar.
post: PostData[]; // These are shown in a multi-tab card.
}
};
export type BackgroundData = {
background: string; // This is the direct URL to the background image.
icon: string;
url: string;
version: string; // This should be parsed into a number.
bg_checksum: string; // Unknown hash algorithm.
};
export type NewsFeedData = {
banner_id: string;
name: string; // This is almost always blank.
img: string; // This is a banner-sized image for the card.
url: string; // This is the associated URL with the card.
// It should be opened in the user's browser when clicked on.
order: string; // This should be parsed into a number.
// Shown to the user in the order of lowest -> highest.
};
export type IconData = {
icon_id: string;
img: string;
tittle: string; // Intentionally misspelled.
url: string;
qr_img: string;
qr_desc: string; // This is almost always blank.
img_hover: string;
other_links: [];
title: string;
icon_link: string;
links: {
title: string;
url: string;
};
enable_red_dot: boolean;
red_dot_content: string;
};
export type PostData = {
post_id: string;
type: PostType;
tittle: string; // Intentionally misspelled.
url: string; // This is where the user should be directed to.
show_time: string; // This is a date formatted as mm/dd.
order: string; // This should be parsed into a number.
// Shown to the user in the order of lowest -> highest.
title: string; // Redundant; same content as 'tittle'.
};
export enum PostType {
Info = "POST_TYPE_INFO",
Activity = "POST_TYPE_ACTIVITY",
Announcement = "POST_TYPE_ANNOUNCE"
}
export enum SupportedGames {
GenshinImpact = "genshin_impact",
StarRail = "starrail"
}

View File

@@ -1,13 +1,4 @@
import { appDataDir } from "@tauri-apps/api/path";
export const AppDataPath = (await appDataDir()).slice(0, -1);
export const PageRoutes = {
HOME: "/",
SETTINGS: "/settings"
};
export const LauncherUrls = {
GENSHIN_IMPACT: "https://sdk-os-static.mihoyo.com/hk4e_global/mdk/launcher/api/content?filter_adv=false&key=gcStgarh&language=en-us&launcher_id=10",
STAR_RAIL: "https://hkrpg-launcher-static.hoyoverse.com/hkrpg_global/mdk/launcher/api/content?filter_adv=false&key=vplOVX8Vn7cwG8yb&language=en-us&launcher_id=35"
};

View File

@@ -1,25 +1,16 @@
import Router, { Route } from "preact-router";
import { Route, Router } from "preact-router";
import TopBar from "@components/TopBar.tsx";
import Launcher from "@ui/layout/Launcher.tsx";
import { useBackground } from "@backend/stores/mihoyo.ts";
import { PageRoutes } from "@app/constants.ts";
import "@css/App.scss";
function App() {
const background = useBackground();
return (
<div class={"App"}
style={{
backgroundImage: `url(${background.url})`
}}
>
<TopBar color={background.colors[2]} />
<div className={"App"}>
<TopBar />
<Router>
<Route path={PageRoutes.HOME} component={Launcher} />

View File

@@ -1,6 +1,6 @@
function InfoBoard() {
return (
<div class={"InfoBoard"}>
<div className={"InfoBoard"}>
</div>
);

View File

@@ -1,22 +1,7 @@
import "@css/components/TopBar.scss";
interface IProps {
color: string | null;
}
function TopBar(props: IProps) {
function TopBar() {
return (
<div class={"TopBar"} data-tauri-drag-region
style={{ backgroundColor: `${props.color}55` }}
>
<div class={"flex flex-row gap-1 text-white"}>
<p>Cultivation</p>
<p>2.0.0</p>
</div>
<div>
<div>
</div>
</div>
);
}

View File

@@ -1,33 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
@apply w-full h-screen;
margin: 0;
outline: 0;
padding: 0;
overflow: hidden;
}
.App {
@apply w-full h-screen bg-no-repeat bg-contain;
}
p {
margin: 0;
}
h1 {
margin: 0;
}
h2 {
margin: 0;
}
h3 {
margin: 0;
}

View File

@@ -1,6 +0,0 @@
.TopBar {
@apply flex flex-row justify-between items-center w-full;
@apply pl-4 pr-4 backdrop-blur-3xl;
height: 40px;
}

View File

@@ -1,3 +0,0 @@
.Launcher {
@apply flex w-full h-full;
}

View File

@@ -1,24 +1,18 @@
import NewsFeed from "@components/NewsFeed.tsx";
import InfoBoard from "@components/InfoBoard.tsx";
import "@css/layout/Launcher.scss";
interface Props {}
function Launcher() {
function Launcher(_props: Props) {
return (
<div class={"Launcher"}>
<div class={"Launcher_Announcements"}>
<div className={"App_Body"}>
<div>
<NewsFeed />
<InfoBoard />
</div>
<div>
<div class={"Launcher_Links"}>
</div>
<div class={"Launcher_QuickSettings"}>
</div>
</div>
</div>
);

View File

@@ -28,7 +28,7 @@
"@ui/*": ["src/ui/*"],
"@css/*": ["src/ui/css/*"],
"@components/*": ["src/ui/components/*"],
"@backend/*": ["src/backend/*"]
"@backend/*": ["src/backend/*"],
},
},
"include": ["src"],