mirror of https://github.com/hykilpikonna/AquaDX
implement memorial photo viewer (#119)
* commit current progress\ will prob work on my mac ltr * more transferring to different device * grammar * [F] Fix warning inconsistency * [O] Split status overlays * [S] Better styling * [+] i18n * [+] Display photos tab conditionally --------- Co-authored-by: Azalea <22280294+hykilpikonna@users.noreply.github.com>v1-dev
parent
6c21afaa57
commit
eef40e39d1
Binary file not shown.
|
@ -4,9 +4,10 @@
|
|||
import UserHome from "./pages/UserHome.svelte";
|
||||
import Home from "./pages/Home.svelte";
|
||||
import Ranking from "./pages/Ranking.svelte";
|
||||
import { USER } from "./libs/sdk";
|
||||
import { CARD, USER } from "./libs/sdk";
|
||||
import type { AquaNetUser } from "./libs/generalTypes";
|
||||
import Settings from "./pages/User/Settings.svelte";
|
||||
import MaiPhoto from "./pages/MaiPhoto.svelte";
|
||||
import { pfp, tooltip } from "./libs/ui"
|
||||
import { ANNOUNCEMENT } from "./libs/config";
|
||||
import { t } from "./libs/i18n";
|
||||
|
@ -25,9 +26,18 @@
|
|||
|
||||
export let url = "";
|
||||
let me: AquaNetUser
|
||||
let playedMai = false
|
||||
|
||||
if (USER.isLoggedIn()) USER.me().then(m => me = m).catch(e => console.error(e))
|
||||
if (USER.isLoggedIn())
|
||||
{
|
||||
USER.me().then(m => {
|
||||
me = m
|
||||
CARD.userGames(me.username).then(game => {
|
||||
playedMai = !!game.mai2
|
||||
})
|
||||
}).catch(e => console.error(e))
|
||||
|
||||
}
|
||||
let path = window.location.pathname;
|
||||
</script>
|
||||
|
||||
|
@ -44,11 +54,14 @@
|
|||
</div>
|
||||
{/if}
|
||||
<a href="/home">{t('navigation.home').toLowerCase()}</a>
|
||||
<div on:click={() => alert("Coming soon™")} on:keydown={e => e.key === "Enter" && alert("Coming soon™")}
|
||||
role="button" tabindex="0">{t('navigation.maps').toLowerCase()}</div>
|
||||
<!-- <div on:click={() => alert("Coming soon™")} on:keydown={e => e.key === "Enter" && alert("Coming soon™")}
|
||||
role="button" tabindex="0">{t('navigation.maps').toLowerCase()}</div> -->
|
||||
<a href="/ranking">{t('navigation.rankings').toLowerCase()}</a>
|
||||
{#if playedMai}
|
||||
<a href="/pictures">photo</a>
|
||||
{/if}
|
||||
{#if me}
|
||||
<a href="/u/{me.username}" use:tooltip={t('navigation.profile')}>
|
||||
<a href="/u/{me.username}" use:tooltip={t('navigation.profile')}>
|
||||
<img alt="profile" class="pfp" use:pfp={me}/>
|
||||
</a>
|
||||
{/if}
|
||||
|
@ -62,6 +75,7 @@
|
|||
<Route path="/u/:username" component={UserHome} />
|
||||
<Route path="/u/:username/:game" component={UserHome} />
|
||||
<Route path="/settings" component={Settings} />
|
||||
<Route path="/pictures" component={MaiPhoto} />
|
||||
</Router>
|
||||
|
||||
<style lang="sass">
|
||||
|
|
|
@ -1,87 +1,54 @@
|
|||
<!-- Svelte 4.2.11 -->
|
||||
|
||||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition'
|
||||
import type { ConfirmProps } from "../libs/generalTypes";
|
||||
import { DISCORD_INVITE } from "../libs/config";
|
||||
import Icon from "@iconify/svelte";
|
||||
import { t } from "../libs/i18n"
|
||||
|
||||
// Props
|
||||
export let confirm: ConfirmProps | null = null
|
||||
export let error: string | null
|
||||
export let loading: boolean = false
|
||||
</script>
|
||||
|
||||
{#if confirm}
|
||||
<div class="overlay" transition:fade>
|
||||
<div>
|
||||
<h2>{confirm.title}</h2>
|
||||
<span>{confirm.message}</span>
|
||||
|
||||
<div class="actions">
|
||||
{#if confirm.cancel}
|
||||
<!-- Svelte LSP is very annoying here -->
|
||||
<button on:click={() => {
|
||||
confirm && confirm.cancel && confirm.cancel()
|
||||
|
||||
// Set to null
|
||||
confirm = null
|
||||
}}>{t('action.cancel')}</button>
|
||||
{/if}
|
||||
<button on:click={() => confirm && confirm.confirm()} class:error={confirm.dangerous}>{t('action.confirm')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if error}
|
||||
<div class="overlay" transition:fade>
|
||||
<div>
|
||||
<h2 class="error">{t('status.error')}</h2>
|
||||
<span>{t('status.error.hint')}<a href={DISCORD_INVITE}>{t('status.error.hint.link')}</a></span>
|
||||
<span>{t('status.detail', { detail: error })}</span>
|
||||
|
||||
<div class="actions">
|
||||
<button on:click={() => location.reload()} class="error">
|
||||
{t('action.refresh')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if loading && !error}
|
||||
<div class="overlay loading" transition:fade>
|
||||
<Icon class="icon" icon="svg-spinners:pulse-2"/>
|
||||
<span><span>LOADING</span></span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="sass">
|
||||
.actions
|
||||
display: flex
|
||||
gap: 16px
|
||||
|
||||
button
|
||||
width: 100%
|
||||
|
||||
.loading.overlay
|
||||
font-size: 28rem
|
||||
|
||||
:global(.icon)
|
||||
opacity: 0.5
|
||||
|
||||
> span
|
||||
position: absolute
|
||||
inset: 0
|
||||
display: flex
|
||||
justify-content: center
|
||||
align-items: center
|
||||
background: transparent
|
||||
|
||||
letter-spacing: 20px
|
||||
margin-left: 20px
|
||||
|
||||
font-size: 1.5rem
|
||||
</style>
|
||||
<!-- Svelte 4.2.11 -->
|
||||
|
||||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition'
|
||||
import type { ConfirmProps } from "../libs/generalTypes";
|
||||
import { DISCORD_INVITE } from "../libs/config";
|
||||
import { t } from "../libs/i18n"
|
||||
import Loading from './ui/Loading.svelte';
|
||||
import Error from './ui/Error.svelte';
|
||||
|
||||
// Props
|
||||
export let confirm: ConfirmProps | null = null
|
||||
export let error: string | null
|
||||
export let loading: boolean = false
|
||||
</script>
|
||||
|
||||
{#if confirm}
|
||||
<div class="overlay" transition:fade>
|
||||
<div>
|
||||
<h2>{confirm.title}</h2>
|
||||
<span>{confirm.message}</span>
|
||||
|
||||
<div class="actions">
|
||||
{#if confirm.cancel}
|
||||
<!-- Svelte LSP is very annoying here -->
|
||||
<button on:click={() => {
|
||||
confirm && confirm.cancel && confirm.cancel()
|
||||
|
||||
// Set to null
|
||||
confirm = null
|
||||
}}>{t('action.cancel')}</button>
|
||||
{/if}
|
||||
<button on:click={() => confirm && confirm.confirm()} class:error={confirm.dangerous}>{t('action.confirm')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if error}
|
||||
<Error {error}/>
|
||||
{/if}
|
||||
|
||||
{#if loading && !error}
|
||||
<Loading/>
|
||||
{/if}
|
||||
|
||||
<style lang="sass">
|
||||
.actions
|
||||
display: flex
|
||||
gap: 16px
|
||||
|
||||
button
|
||||
width: 100%
|
||||
</style>
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
</script>
|
||||
|
||||
<div out:fade={FADE_OUT} in:fade={FADE_IN} class="fields">
|
||||
<p class="warning">
|
||||
<blockquote>
|
||||
{ts("settings.gameNotice")}
|
||||
</p>
|
||||
</blockquote>
|
||||
<GameSettingFields game="general"/>
|
||||
<div class="field">
|
||||
<div class="bool">
|
||||
|
@ -59,14 +59,4 @@
|
|||
|
||||
> input
|
||||
flex: 1
|
||||
|
||||
.warning
|
||||
background: #aa555510
|
||||
padding: 10px
|
||||
border-left: solid 2px vars.$c-error
|
||||
|
||||
&::before
|
||||
color: vars.$c-error
|
||||
font-weight: bold
|
||||
content: "!"
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<script lang="ts">
|
||||
import { fade } from "svelte/transition";
|
||||
import { t } from "../../libs/i18n";
|
||||
import { DISCORD_INVITE } from "../../libs/config";
|
||||
|
||||
export let error: string;
|
||||
</script>
|
||||
|
||||
<div class="overlay" transition:fade>
|
||||
<div>
|
||||
<h2 class="error">{t('status.error')}</h2>
|
||||
<span>{t('status.error.hint')}<a href={DISCORD_INVITE}>{t('status.error.hint.link')}</a></span>
|
||||
<span>{t('status.detail', { detail: error })}</span>
|
||||
|
||||
<div class="actions">
|
||||
<button on:click={() => location.reload()} class="error">
|
||||
{t('action.refresh')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="sass">
|
||||
.actions
|
||||
display: flex
|
||||
gap: 16px
|
||||
|
||||
button
|
||||
width: 100%
|
||||
</style>
|
|
@ -0,0 +1,30 @@
|
|||
<script lang="ts">
|
||||
import Icon from '@iconify/svelte';
|
||||
import { fade } from 'svelte/transition'
|
||||
</script>
|
||||
|
||||
<div class="overlay loading" transition:fade>
|
||||
<Icon class="icon" icon="svg-spinners:pulse-2"/>
|
||||
<span><span>LOADING</span></span>
|
||||
</div>
|
||||
|
||||
<style lang="sass">
|
||||
.loading.overlay
|
||||
font-size: 28rem
|
||||
|
||||
:global(.icon)
|
||||
opacity: 0.5
|
||||
|
||||
> span
|
||||
position: absolute
|
||||
inset: 0
|
||||
display: flex
|
||||
justify-content: center
|
||||
align-items: center
|
||||
background: transparent
|
||||
|
||||
letter-spacing: 20px
|
||||
margin-left: 20px
|
||||
|
||||
font-size: 1.5rem
|
||||
</style>
|
|
@ -229,7 +229,15 @@ export const EN_REF_USERBOX = {
|
|||
'userbox.new.error.invalidUrl': 'The URL you inputted is invalid.'
|
||||
}
|
||||
|
||||
export const EN_REF_MAI_PHOTO = {
|
||||
'maiphoto.title': 'Mai Memorial Photo Gallery',
|
||||
'maiphoto.url_warning': 'Note: If you want to share a photo with your friend, please save the photo. Do not copy image URL because the URL contains sensitive information.',
|
||||
'maiphoto.none': 'No photo found. You can upload photo by clicking upload at the end of each game session.',
|
||||
}
|
||||
|
||||
export const EN_REF = { ...EN_REF_USER, ...EN_REF_Welcome, ...EN_REF_GENERAL,
|
||||
...EN_REF_LEADERBOARD, ...EN_REF_HOME, ...EN_REF_SETTINGS, ...EN_REF_USERBOX }
|
||||
...EN_REF_LEADERBOARD, ...EN_REF_HOME, ...EN_REF_SETTINGS, ...EN_REF_USERBOX,
|
||||
...EN_REF_MAI_PHOTO
|
||||
}
|
||||
|
||||
export type LocalizedMessages = typeof EN_REF
|
||||
|
|
|
@ -2,6 +2,7 @@ import {
|
|||
EN_REF_GENERAL,
|
||||
EN_REF_HOME,
|
||||
EN_REF_LEADERBOARD,
|
||||
EN_REF_MAI_PHOTO,
|
||||
EN_REF_SETTINGS,
|
||||
EN_REF_USER,
|
||||
EN_REF_USERBOX,
|
||||
|
@ -234,6 +235,11 @@ export const zhUserbox: typeof EN_REF_USERBOX = {
|
|||
'userbox.new.error.invalidUrl': '输入的 URL 无效。'
|
||||
};
|
||||
|
||||
export const zhMaiPhoto: typeof EN_REF_MAI_PHOTO = {
|
||||
'maiphoto.title': 'Mai 纪念照片库',
|
||||
'maiphoto.url_warning': '注意:如果想与朋友分享图片的话,请先保存照片再发出去。不要复制图片 URL,因为 URL 中包含 AquaDX 账号信息。',
|
||||
'maiphoto.none': '还没有图片哦~ 可以在每次游戏结束的时候点击上传来上传照片。',
|
||||
}
|
||||
|
||||
export const ZH = { ...zhUser, ...zhWelcome, ...zhGeneral,
|
||||
...zhLeaderboard, ...zhHome, ...zhSettings, ...zhUserbox }
|
||||
...zhLeaderboard, ...zhHome, ...zhSettings, ...zhUserbox, ...zhMaiPhoto }
|
||||
|
|
|
@ -284,6 +284,8 @@ export const CARD = {
|
|||
export const GAME = {
|
||||
trend: (username: string, game: GameName): Promise<TrendEntry[]> =>
|
||||
post(`/api/v2/game/${game}/trend`, { username }),
|
||||
photos: (): Promise<string[]> =>
|
||||
post(`/api/v2/game/mai2/my-photo`, { }),
|
||||
userSummary: (username: string, game: GameName): Promise<GenericGameSummary> =>
|
||||
post(`/api/v2/game/${game}/user-summary`, { username }),
|
||||
ranking: (game: GameName): Promise<GenericRanking[]> =>
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<script lang="ts">
|
||||
import {GAME} from "../libs/sdk";
|
||||
import {AQUA_HOST} from "../libs/config";
|
||||
import Loading from "../components/ui/Loading.svelte";
|
||||
import Error from "../components/ui/Error.svelte";
|
||||
import { t } from "../libs/i18n";
|
||||
|
||||
const token = localStorage.getItem("token")
|
||||
</script>
|
||||
|
||||
<main class="content">
|
||||
<div class="outer-title-options">
|
||||
<h2>{t("maiphoto.title")}</h2>
|
||||
</div>
|
||||
|
||||
{#await GAME.photos()}
|
||||
<Loading/>
|
||||
{:then photos}
|
||||
{#if photos.length === 0}
|
||||
<blockquote>{t('maiphoto.none')}</blockquote>
|
||||
{:else}
|
||||
<blockquote>{t('maiphoto.url_warning')}</blockquote>
|
||||
{/if}
|
||||
<div class="pictures">
|
||||
{#each photos as photo}
|
||||
<div class="photo-container">
|
||||
<img class="rounded-2xl" src="{AQUA_HOST}/api/v2/game/mai2/my-photo/{photo}?token={token}" alt="Memorial" />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:catch error}
|
||||
<Error {error}/>
|
||||
{/await}
|
||||
</main>
|
||||
|
||||
<style lang="sass">
|
||||
@use "../vars"
|
||||
|
||||
.pictures
|
||||
display: flex
|
||||
flex-wrap: wrap
|
||||
justify-content: center
|
||||
row-gap: 1rem
|
||||
gap: 1rem
|
||||
|
||||
.photo-container
|
||||
flex: 1 1 300px
|
||||
min-width: 280px
|
||||
max-width: 100%
|
||||
display: flex
|
||||
justify-content: center
|
||||
|
||||
.photo-container img
|
||||
width: 100%
|
||||
height: auto
|
||||
object-fit: contain
|
||||
</style>
|
Loading…
Reference in New Issue