diff --git a/AquaNet/.env b/AquaNet/.env index 16146a10..2c5fa305 100644 --- a/AquaNet/.env +++ b/AquaNet/.env @@ -7,3 +7,4 @@ VITE_TURNSTILE_SITE_KEY=0x4AAAAAAASGA2KQEIelo9P9 VITE_DISCORD_INVITE=https://discord.gg/FNgveqFF7s VITE_TELEGRAM_INVITE=https://t.me/+zBL4RZdyfvUzZGU1 VITE_QQ_INVITE=https://qm.qq.com/q/dpYmGoVHnG +VITE_GITHUB_REPOSITORY=https://github.com/MewoLab/AquaDX \ No newline at end of file diff --git a/AquaNet/public/assets/theme/cn.css b/AquaNet/public/assets/theme/cn.css deleted file mode 100644 index ba1fd70b..00000000 --- a/AquaNet/public/assets/theme/cn.css +++ /dev/null @@ -1,163 +0,0 @@ -/* - -Happy April Fools! -This theme will stay here. -Note that I made it with Stylish in mind, it's quite jank. - -*/ -* { - font-family: "ヒラギノ角ゴ Pro W3", "メイリオ", Meiryo, "MS Pゴシック", - "MS P Gothic", sans-serif; -} -nav > a, -nav > *.active, -.setting-icon path { - color: unset !important; -} -.aqua-tooltip { - background: black; -} -.fw-block { - background: none !important; - box-shadow: none !important; -} -#app { - background: url(/assets/theme/cn/logo.bin), - #f9f9db; - background-repeat: no-repeat; - background-position: 50% 4px; - max-width: 528px !important; - margin: 0 auto; - padding: 100px 0 0 0 !important; - height: unset !important; - box-shadow: -8px 0 0 0 #fdd500, -12px 0 0 0 #f9f9db, 8px 0 0 0 #fdd500, - 12px 0 0 0 #f9f9db; -} -nav:has(.logo) { - position: absolute !important; - top: 0; - left: 0; - width: calc(100% - 96px); -} -nav { - color: black; -} -.user-pfp { - margin-top: -56px !important; -} -.outer-title-options, -.outer-title-options *, -nav.tabs { - color: white !important; -} -.outer-title-options { - margin-top: 0 !important; - display: unset !important; -} -.outer-title-options h2 { - width: 460px; - position: relative; - right: 20px; - display: flex; - justify-content: center; - margin: 0 0 10px 0 !important; - background: url(/assets/theme/cn/header.bin); -} -.chuni-userbox-row { - flex-wrap: wrap; -} -.chuni-userbox button { - width: calc(100% / 4) !important; - font-size: 0px; -} -.chuni-userbox-row button { - width: unset !important; - flex: 0 1 calc(100% / 3) !important; -} -.chuni-userbox-row button img { - overflow: hidden; - font-size: 10px; -} -.chuni-nameplate { - background: none !important; - position: relative !important; - left: 20px; -} -.chuni-userbox { - background: none !important; -} -main { - max-width: calc(460px - 40px) !important; - margin: 16px auto 0 auto !important; - background: #2c4056 !important; - border-radius: unset !important; - padding: 10px 20px !important; -} -main:has(.user-pfp) { - margin: 64px auto 0 auto !important; -} -.rating-composition { - display: flex !important; - flex-wrap: wrap; - gap: 0 !important; -} -.rating-composition > div { - width: 47.5%; - margin: 1.25%; -} -.map-detail-container { - background: none !important; - border-radius: 0 !important; -} -.lv { - border-radius: 0 !important; - display: flex; - align-items: center; - justify-content: center; - padding: 0 !important; - width: 50px !important; -} -.rank-text { - min-width: 20px !important; -} -.chuni-userbox-container { - flex-wrap: wrap; -} -.profile-bio-text { - white-space: unset !important; -} -.chuni-penguin-container { - padding: 64px 0; - width: 100%; - background: linear-gradient( - 180deg, - rgba(249, 249, 219, 1) 0%, - rgba(249, 249, 219, 1) 69%, - rgba(231, 231, 202, 1) 70%, - rgba(231, 231, 202, 1) 100% - ); -} -body { - background: #fdd500 !important; - color: white; -} - -@media (max-width: 1200px) { - #app { - background-position: 50% 60px !important; - padding-top: 150px !important; - } -} - -@media (max-width: 1028px) { - #app { - background-size: 90%; - } - - .user-pfp { - margin-top: -36px !important; - } - .user-pfp nav { - top: -10px !important; - } -} \ No newline at end of file diff --git a/AquaNet/public/assets/theme/cn/header.bin b/AquaNet/public/assets/theme/cn/header.bin deleted file mode 100644 index a92eec75..00000000 Binary files a/AquaNet/public/assets/theme/cn/header.bin and /dev/null differ diff --git a/AquaNet/public/assets/theme/cn/logo.bin b/AquaNet/public/assets/theme/cn/logo.bin deleted file mode 100644 index 4e0e6965..00000000 Binary files a/AquaNet/public/assets/theme/cn/logo.bin and /dev/null differ diff --git a/AquaNet/src/App.svelte b/AquaNet/src/App.svelte index 5403b50c..127d820e 100644 --- a/AquaNet/src/App.svelte +++ b/AquaNet/src/App.svelte @@ -16,6 +16,7 @@ import Communities from "./pages/Home/Communities.svelte"; import LinkCard from "./pages/Home/LinkCard.svelte"; import SetupInstructions from "./pages/Home/SetupInstructions.svelte"; + import PageNotFound from "./pages/PageNotFound.svelte"; console.log(`%c ┏━┓ ┳━┓━┓┏━ @@ -41,15 +42,6 @@ playedMai = !!game.mai2 }) }).catch(e => console.error(e)) - - const themeStyle = document.createElement("link"); - themeStyle.rel = "stylesheet"; - switch (localStorage.getItem("theme")) { - case "cn": - themeStyle.href = "/assets/theme/cn.css"; - }; - if (themeStyle.href) - document.head.appendChild(themeStyle); } let path = window.location.pathname; @@ -93,8 +85,10 @@ + + diff --git a/AquaNet/src/components/settings/WaccaSettings.svelte b/AquaNet/src/components/settings/WaccaSettings.svelte index 9d442758..9997517a 100644 --- a/AquaNet/src/components/settings/WaccaSettings.svelte +++ b/AquaNet/src/components/settings/WaccaSettings.svelte @@ -4,6 +4,6 @@ import GameSettingFields from "./GameSettingFields.svelte"; -
+
diff --git a/AquaNet/src/components/ui/Error.svelte b/AquaNet/src/components/ui/Error.svelte index 7055ecd2..585976f4 100644 --- a/AquaNet/src/components/ui/Error.svelte +++ b/AquaNet/src/components/ui/Error.svelte @@ -10,15 +10,15 @@

{t('status.error')}

- {#if !expected} - {t('status.error.hint')}{t('status.error.hint.link')} - {/if} {error} - + {t("status.error.hint")}
+
@@ -26,14 +26,19 @@ diff --git a/AquaNet/src/libs/config.ts b/AquaNet/src/libs/config.ts index 3f62b182..e8cb12e6 100644 --- a/AquaNet/src/libs/config.ts +++ b/AquaNet/src/libs/config.ts @@ -10,6 +10,7 @@ export const TURNSTILE_SITE_KEY = import.meta.env.VITE_TURNSTILE_SITE_KEY export const DISCORD_INVITE = import.meta.env.VITE_DISCORD_INVITE export const TELEGRAM_INVITE = import.meta.env.VITE_TELEGRAM_INVITE export const QQ_INVITE = import.meta.env.VITE_QQ_INVITE +export const GITHUB_REPOSITORY = import.meta.env.VITE_GITHUB_REPOSITORY // UI export const FADE_OUT = { duration: 200 } diff --git a/AquaNet/src/libs/i18n/en_ref.ts b/AquaNet/src/libs/i18n/en_ref.ts index 49b31724..fb727efa 100644 --- a/AquaNet/src/libs/i18n/en_ref.ts +++ b/AquaNet/src/libs/i18n/en_ref.ts @@ -77,17 +77,19 @@ export const EN_REF_GENERAL = { 'game.ongeki': 'Ongeki', 'game.wacca': 'Wacca', 'status.error': 'Error', - 'status.error.hint': 'Something went wrong, please try again later or ', - 'status.error.hint.link': 'join our discord for support.', + 'status.error.hint': 'Support', 'status.detail': 'Detail: ${detail}', - 'action.refresh': 'Refresh', + 'action.refresh': 'Retry', 'action.cancel': 'Cancel', 'action.confirm': 'Confirm', + 'action.home': 'Home', 'navigation.profile': 'Profile', 'navigation.maps': 'Maps', 'navigation.home': 'Home', 'navigation.rankings': 'Rankings', - 'navigation.notice': 'Notice' + 'navigation.notice': 'Notice', + 'loading': `Please wait...`, + '404': 'Page not found (${pathname}). If this is in error, please report it via an appropriate support channel.' } export const EN_REF_HOME = { @@ -102,7 +104,7 @@ export const EN_REF_HOME = { 'home.manage-cards-description': 'Link, unlink, and manage your cards.', 'home.link-card': 'Link Card', 'home.link-cards-description': 'Link your Amusement IC / Aime card to play games.', - 'home.join-community': 'Join Community', + 'home.join-community': 'Community & Support', 'home.join-community-description': 'Join our community for support and chatting with other players.', 'home.setup': 'Setup Network Connection', 'home.setup-description': 'Configure a game to connect to our servers.', @@ -131,13 +133,13 @@ export const EN_REF_HOME = { 'home.community.discord': 'Discord', 'home.community.telegram': 'Telegram (Chinese)', 'home.community.qq': 'QQ (Chinese)', + 'home.community.github': 'GitHub Repository', 'home.import.unknown-game': 'Unknown game type. Currently only Mai and Chuni are supported for importing.', 'home.import.new-data': 'Data to import', 'home.import.data-conflict': 'Proceed will override your current data', } export const EN_REF_SETUP = { - 'loading': `Please wait...`, 'setup.welcome': `Welcome! If you have a game set up, please follow the instructions below to set up the connection with AquaDX.`, 'setup.keychip-warning': `Your keychip is linked to your account and should be kept secure. Do not give others your keychip.`, 'setup.steps.one': `Pick a method of setting up network communications. Some browsers may not be able to do automatic setup.`, @@ -157,6 +159,7 @@ export const EN_REF_SETUP = { export const EN_REF_SETTINGS = { 'settings.title': 'Settings', + 'settings.page-title': '${page} Settings', 'settings.tabs.profile': 'Profile', 'settings.tabs.global': 'Global', 'settings.tabs.chu3': 'Chuni', diff --git a/AquaNet/src/pages/Home.svelte b/AquaNet/src/pages/Home.svelte index fe4480d5..e335ab96 100644 --- a/AquaNet/src/pages/Home.svelte +++ b/AquaNet/src/pages/Home.svelte @@ -5,7 +5,7 @@ import ActionCard from "../components/ActionCard.svelte"; import { t } from "../libs/i18n"; import ImportDataAction from "./Home/ImportDataAction.svelte"; - import DashboardTabs from "./Home/DashboardTabs.svelte"; + import DashboardTabs from "../components/DashboardTabs.svelte"; USER.ensureLoggedIn(); diff --git a/AquaNet/src/pages/Home/Communities.svelte b/AquaNet/src/pages/Home/Communities.svelte index 7abaf3b6..34f543c8 100644 --- a/AquaNet/src/pages/Home/Communities.svelte +++ b/AquaNet/src/pages/Home/Communities.svelte @@ -3,30 +3,36 @@

{t('home.join-community')}

-
+
{#if DISCORD_INVITE} -

{t('home.community.discord')}

+ {t('home.community.discord')}
{/if} {#if TELEGRAM_INVITE} -

{t('home.community.telegram')}

+ {t('home.community.telegram')}
{/if} {#if QQ_INVITE} -

{t('home.community.qq')}

+ {t('home.community.qq')} +
+ {/if} + + {#if GITHUB_REPOSITORY} + + {t('home.community.github')} {/if}
@@ -34,5 +40,6 @@
diff --git a/AquaNet/src/pages/Home/LinkCard.svelte b/AquaNet/src/pages/Home/LinkCard.svelte index 47923bf5..1b076b35 100644 --- a/AquaNet/src/pages/Home/LinkCard.svelte +++ b/AquaNet/src/pages/Home/LinkCard.svelte @@ -8,7 +8,7 @@ import Icon from "@iconify/svelte" import StatusOverlays from "../../components/StatusOverlays.svelte" import { t } from "../../libs/i18n" - import DashboardTabs from "./DashboardTabs.svelte"; + import DashboardTabs from "../../components/DashboardTabs.svelte"; // State let state: 'ready' | 'linking-AC' | 'linking-SN' | 'loading' = "loading" diff --git a/AquaNet/src/pages/Home/SetupInstructions.svelte b/AquaNet/src/pages/Home/SetupInstructions.svelte index 1ee885e3..aa6f153a 100644 --- a/AquaNet/src/pages/Home/SetupInstructions.svelte +++ b/AquaNet/src/pages/Home/SetupInstructions.svelte @@ -7,7 +7,7 @@ import { codeToHtml } from 'shiki' import { AQUA_CONNECTION, DISCORD_INVITE, FADE_IN, FADE_OUT } from "../../libs/config"; import { t } from "../../libs/i18n"; - import DashboardTabs from "./DashboardTabs.svelte"; + import DashboardTabs from "../../components/DashboardTabs.svelte"; import { patchUserSegatools } from "../../libs/setup"; let user: AquaNetUser diff --git a/AquaNet/src/pages/PageNotFound.svelte b/AquaNet/src/pages/PageNotFound.svelte new file mode 100644 index 00000000..7c8a704f --- /dev/null +++ b/AquaNet/src/pages/PageNotFound.svelte @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/AquaNet/src/pages/User/Settings.svelte b/AquaNet/src/pages/User/Settings.svelte index d9c60778..c98aab94 100644 --- a/AquaNet/src/pages/User/Settings.svelte +++ b/AquaNet/src/pages/User/Settings.svelte @@ -5,281 +5,58 @@ import type { AquaNetUser } from "../../libs/generalTypes"; import { CARD, USER } from "../../libs/sdk"; import StatusOverlays from "../../components/StatusOverlays.svelte"; - import Icon from "@iconify/svelte"; - import { pfp } from "../../libs/ui"; import { t, ts } from "../../libs/i18n"; - import { FADE_IN, FADE_OUT } from "../../libs/config"; - import Cropper from "svelte-easy-crop"; - import UserBox from "../../components/settings/ChuniSettings.svelte"; + import ChuniSettings from "../../components/settings/ChuniSettings.svelte"; import Mai2Settings from "../../components/settings/Mai2Settings.svelte"; import WaccaSettings from "../../components/settings/WaccaSettings.svelte"; import GeneralGameSettings from "../../components/settings/GeneralGameSettings.svelte"; import OngekiSettings from "../../components/settings/OngekiSettings.svelte"; - import useLocalStorage from "../../libs/hooks/useLocalStorage.svelte"; + import UserSettings from "../../components/settings/UserSettings.svelte"; + import type { Component } from "svelte"; + import { EN_REF } from "../../libs/i18n/en_ref"; USER.ensureLoggedIn() let me: AquaNetUser; let error: string; - let submitting = "" - let tab = 0 - let tabs = ['profile'] + export let page: string = "profile"; - const profileFields = [ - [ 'displayName', t('settings.profile.name') ], - [ 'username', t('settings.profile.username') ], - [ 'password', t('settings.profile.password') ], - /* Neither of these did anything of importance - [ 'country', t('settings.profile.country') ], - [ 'profileLocation', t('settings.profile.location') ],*/ - [ 'profileBio', t('settings.profile.bio') ], - ] as const - - // Fetch user data - const getMe = () => USER.me().then((m) => { - if (pfpCropURL != null) { - URL.revokeObjectURL(pfpCropURL); - pfpField.value = ""; - pfpCropURL = null; - } - me = m - - CARD.userGames(m.username).then(games => { - tabs = [ - ...tabs, - ...['chu3', 'mai2','wacca', 'ongeki'].filter(v => games[v as keyof typeof games]), // :xdx: - 'global' - ] - }) - }).catch(e => error = e.message) - getMe() - - let changed: string[] = [] - let pfpField: HTMLInputElement - let pfpCropURL: string | null = null; - let pfpCrop = { width: 0, height: 0, x: 0, y: 0 }; - - function submit(field: string, value: string) { - if (submitting) return - submitting = field - - USER.setting(field, value).then(() => { - changed = changed.filter(c => c !== field) - }).catch(e => error = e.message).finally(() => submitting = "") + const pages: Record = { + "profile": UserSettings, "chu3": ChuniSettings, + "mai2": Mai2Settings, "wacca": WaccaSettings, + "ongeki": OngekiSettings, "global": GeneralGameSettings } - function uploadPfp() { - if (submitting) return - // Don't know why this isn't just a part of the cropper module. Have to do this myself.. What a shame - let canvas = document.createElement("canvas"); - let ctx = canvas.getContext("2d"); - const size = Math.round(Math.min(pfpCrop.width, pfpCrop.height, 1024)); - canvas.width = size; - canvas.height = size; - let img = document.createElement("img"); - img.onload = () => { - ctx?.drawImage(img, pfpCrop.x, pfpCrop.y, pfpCrop.width, pfpCrop.height, 0, 0, size, size); - canvas.toBlob(blob => { - if (!blob) return; - submitting = 'profilePicture' - USER.uploadPfp(blob as File).then(() => { - me.profilePicture = me.username - // reload - // this doesn't work btw - setTimeout(getMe, 200); - }).catch(e => error = e.message).finally(() => submitting = "") - }); - } - img.src = pfpCropURL ?? ""; - } - function handlePfpUpload(e: Event & { target: HTMLInputElement }) { - if (!e.target) return; - let files = e?.target?.files; - if (!files || files.length <= 0) return; - let file = files[0]; - console.log(me.username, me); - switch (file.type) { - case "image/gif": - USER.uploadPfp(file).then(() => { - me.profilePicture = me.username - // reload - setTimeout(getMe, 200); - }).catch(e => error = e.message).finally(() => submitting = "") - break; - case "image/png": - case "image/jpeg": - case "image/webp": - pfpCropURL = URL.createObjectURL(file); - break; - default: - error = t("settings.profile.bad-format"); - } - }; - function logOut() { - localStorage.removeItem("token"); - location.href = "/"; - } + if (!pages[page] && page) + error = t("404", {pathname: new URL(location.href).pathname}); - const passwordAction = (node: HTMLInputElement, whether: boolean) => { - if (whether) node.type = 'password' - } + USER.me().then(m => me = m) + .catch(e => error = e.message)

{t('settings.title')}

- {#if tab === 0 && me} - -
-
- -
- {#if me && me.profilePicture} -
pfpField.click()} on:keydown={e => e.key === 'Enter' && pfpField.click()} - role="button" tabindex="0" class="clickable"> - Profile -
- {:else} - - {/if} -
- - -
+

+ {t('settings.page-title', {page: ts(`settings.tabs.${page}`)})} +

- {#each profileFields as [field, name], i (field)} -
- -
- {#if field == "profileBio"} - - {:else} - changed = [...changed, field]} - placeholder={field === 'password' ? t('settings.profile.unchanged') : t('settings.profile.unset')}/> - {/if} - - {#if changed.includes(field) && me[field]} - - {/if} -
-
- {/each} -
-
- submit('optOutOfLeaderboard', me.optOutOfLeaderboard.toString())}/> - -
-
-
-
- -
-
-
- {:else if tabs[tab] === 'chu3'} - - - {:else if tabs[tab] === 'mai2'} - - {:else if tabs[tab] === 'wacca'} - - {:else if tabs[tab] === 'ongeki'} - - {:else if tabs[tab] === 'global'} - + {#if pages[page]} + {/if} - -
- -{#if pfpCropURL != null} -
-
-
- pfpCrop = e.pixels} image={pfpCropURL ?? "assets/imgs/no_profile.png"} aspect={1} cropShape="round"> -
- - -
-
-{/if} - + + h2.header + margin: 0 0 0.5rem 0 + \ No newline at end of file diff --git a/AquaNet/src/pages/UserHome.svelte b/AquaNet/src/pages/UserHome.svelte index b7ecea7c..87b3ce80 100644 --- a/AquaNet/src/pages/UserHome.svelte +++ b/AquaNet/src/pages/UserHome.svelte @@ -30,15 +30,13 @@ registerChart() export let username: string; - export let game: GameName | "auto" = "auto" + export let game: GameName; let calElement: HTMLElement let error: string; let me: AquaNetUser title(`User ${username}`) const rounding = useLocalStorage("rounding", true); - const titleText = game != "auto" ? GAME_TITLE[game] : "?" - interface MusicAndPlay extends MusicMeta, GenericGamePlaylog {} let d: { @@ -57,7 +55,7 @@ USER.isLoggedIn() && USER.me().then(u => me = u) CARD.userGames(username).then(games => { - if (game == "auto") { + if (!game) { let targetGames = Object.entries(games) .map(d => { if (d[1]) @@ -66,9 +64,7 @@ }).sort((a,b) => { return b[1]?.lastLogin - a[1]?.lastLogin; }); - if (targetGames[0]) - window.location.href = `/u/${username}/${targetGames[0][0]}` - return; + game = targetGames[0][0] as GameName; } if (!games[game]) { // Find a valid game @@ -118,11 +114,10 @@ }).catch((e) => { error = e.message; console.error(e) } ); } - if (Object.keys(GAME_TITLE).includes(game) || game == "auto") init() + if (Object.keys(GAME_TITLE).includes(game) || !game) init() else error = t("UserHome.InvalidGame", {game}) const setRival = (isAdd: boolean) => { - if (game == "auto") return; isLoading = true GAME.setRival(game, username, isAdd).then(() => { d!.user.rival = isAdd @@ -167,7 +162,7 @@ {/each} {#if me && me.username === username} - + {/if} @@ -188,7 +183,7 @@
-

{titleText} {t('UserHome.Statistics')}

+

{GAME_TITLE[game] ?? "?"} {t('UserHome.Statistics')}

@@ -313,16 +308,16 @@ - - - + + + {#if d.user.ratingComposition.new} {:else} - + {/if}
@@ -344,12 +339,12 @@ {r.notes?.[r.level === 10 ? 0 : r.level]?.lv?.toFixed(1) ?? r.worldsEndTag ?? '-'} - - {("" + getMult(r.achievement, game != "auto" ? game : "mai2")[2]).replace("p", "+")} + + {("" + getMult(r.achievement, game)[2]).replace("p", "+")} { rounding.value ? - roundFloor(r.achievement, game != "auto" ? game : "mai2", 1) : + roundFloor(r.achievement, game, 1) : (r.achievement / 10000).toFixed(4) }%