mirror of https://github.com/hykilpikonna/AquaDX
feat: ➕ pfp cropper + enhancements
parent
7d214ba214
commit
16112d4f1d
|
@ -39,6 +39,7 @@
|
||||||
"lxgw-wenkai-lite-webfont": "^1.7.0",
|
"lxgw-wenkai-lite-webfont": "^1.7.0",
|
||||||
"modern-normalize": "^3.0.1",
|
"modern-normalize": "^3.0.1",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
|
"svelte-easy-crop": "^4.0.0",
|
||||||
"svelte5-router": "^3.0.1"
|
"svelte5-router": "^3.0.1"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.7.0+sha512.dc09430156b427f5ecfc79888899e1c39d2d690f004be70e05230b72cb173d96839587545d09429b55ac3c429c801b4dc3c0e002f653830a420fa2dd4e3cf9cf"
|
"packageManager": "pnpm@9.7.0+sha512.dc09430156b427f5ecfc79888899e1c39d2d690f004be70e05230b72cb173d96839587545d09429b55ac3c429c801b4dc3c0e002f653830a420fa2dd4e3cf9cf"
|
||||||
|
|
|
@ -75,7 +75,7 @@
|
||||||
img
|
img
|
||||||
width: 1.5rem
|
width: 1.5rem
|
||||||
height: 1.5rem
|
height: 1.5rem
|
||||||
border-radius: 50%
|
border-radius: vars.$border-radius
|
||||||
object-fit: cover
|
object-fit: cover
|
||||||
|
|
||||||
.pfp
|
.pfp
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import { fade } from "svelte/transition";
|
import { fade } from "svelte/transition";
|
||||||
import { FADE_IN, FADE_OUT } from "../../libs/config";
|
import { FADE_IN, FADE_OUT } from "../../libs/config";
|
||||||
import GameSettingFields from "./GameSettingFields.svelte";
|
import GameSettingFields from "./GameSettingFields.svelte";
|
||||||
import { ts } from "../../libs/i18n";
|
import { t, ts } from "../../libs/i18n";
|
||||||
import useLocalStorage from "../../libs/hooks/useLocalStorage.svelte";
|
import useLocalStorage from "../../libs/hooks/useLocalStorage.svelte";
|
||||||
|
|
||||||
const rounding = useLocalStorage("rounding", true);
|
const rounding = useLocalStorage("rounding", true);
|
||||||
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
<div out:fade={FADE_OUT} in:fade={FADE_IN} class="fields">
|
<div out:fade={FADE_OUT} in:fade={FADE_IN} class="fields">
|
||||||
<p class="warning">
|
<p class="warning">
|
||||||
These settings affect Mai and Wacca.
|
{ts("settings.gameNotice")}
|
||||||
</p>
|
</p>
|
||||||
<GameSettingFields game="general"/>
|
<GameSettingFields game="general"/>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
|
|
@ -158,6 +158,7 @@ export const EN_REF_SETTINGS = {
|
||||||
'settings.mai2.name': 'Player Name',
|
'settings.mai2.name': 'Player Name',
|
||||||
'settings.profile.picture': 'Profile Picture',
|
'settings.profile.picture': 'Profile Picture',
|
||||||
'settings.profile.upload-new': 'Upload New',
|
'settings.profile.upload-new': 'Upload New',
|
||||||
|
'settings.profile.bad-format': 'Invalid image format. Supported types are PNG, JPG, JPEG, WEBP & GIF.',
|
||||||
'settings.profile.save': 'Save',
|
'settings.profile.save': 'Save',
|
||||||
'settings.profile.name': 'Display Name',
|
'settings.profile.name': 'Display Name',
|
||||||
'settings.profile.username': 'Username',
|
'settings.profile.username': 'Username',
|
||||||
|
@ -167,7 +168,8 @@ export const EN_REF_SETTINGS = {
|
||||||
'settings.profile.unset': 'Unset',
|
'settings.profile.unset': 'Unset',
|
||||||
'settings.profile.unchanged': 'Unchanged',
|
'settings.profile.unchanged': 'Unchanged',
|
||||||
'settings.export': 'Export Player Data',
|
'settings.export': 'Export Player Data',
|
||||||
'settings.cabNotice': "Note: These settings will only affect your own cab/setup. If you're playing on someone else's setup, please contact them to change these settings."
|
'settings.cabNotice': "Note: These settings will only affect your own cab/setup. If you're playing on someone else's setup, please contact them to change these settings.",
|
||||||
|
'settings.gameNotice': "These only apply to Mai and Wacca."
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EN_REF_USERBOX = {
|
export const EN_REF_USERBOX = {
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
import { pfp } from "../../libs/ui";
|
import { pfp } from "../../libs/ui";
|
||||||
import { t, ts } from "../../libs/i18n";
|
import { t, ts } from "../../libs/i18n";
|
||||||
import { FADE_IN, FADE_OUT } from "../../libs/config";
|
import { FADE_IN, FADE_OUT } from "../../libs/config";
|
||||||
|
import Cropper from "svelte-easy-crop";
|
||||||
import UserBox from "../../components/settings/ChuniSettings.svelte";
|
import UserBox from "../../components/settings/ChuniSettings.svelte";
|
||||||
import Mai2Settings from "../../components/settings/Mai2Settings.svelte";
|
import Mai2Settings from "../../components/settings/Mai2Settings.svelte";
|
||||||
import WaccaSettings from "../../components/settings/WaccaSettings.svelte";
|
import WaccaSettings from "../../components/settings/WaccaSettings.svelte";
|
||||||
|
@ -32,6 +33,11 @@
|
||||||
|
|
||||||
// Fetch user data
|
// Fetch user data
|
||||||
const getMe = () => USER.me().then((m) => {
|
const getMe = () => USER.me().then((m) => {
|
||||||
|
if (pfpCropURL != null) {
|
||||||
|
URL.revokeObjectURL(pfpCropURL);
|
||||||
|
pfpField.value = "";
|
||||||
|
pfpCropURL = null;
|
||||||
|
}
|
||||||
me = m
|
me = m
|
||||||
|
|
||||||
CARD.userGames(m.username).then(games => {
|
CARD.userGames(m.username).then(games => {
|
||||||
|
@ -50,6 +56,8 @@
|
||||||
|
|
||||||
let changed: string[] = []
|
let changed: string[] = []
|
||||||
let pfpField: HTMLInputElement
|
let pfpField: HTMLInputElement
|
||||||
|
let pfpCropURL: string | null = null;
|
||||||
|
let pfpCrop = { width: 0, height: 0, x: 0, y: 0 };
|
||||||
|
|
||||||
function submit(field: string, value: string) {
|
function submit(field: string, value: string) {
|
||||||
if (submitting) return
|
if (submitting) return
|
||||||
|
@ -60,16 +68,53 @@
|
||||||
}).catch(e => error = e.message).finally(() => submitting = "")
|
}).catch(e => error = e.message).finally(() => submitting = "")
|
||||||
}
|
}
|
||||||
|
|
||||||
function uploadPfp(file: File) {
|
function uploadPfp() {
|
||||||
if (submitting) return
|
if (submitting) return
|
||||||
submitting = 'profilePicture'
|
// 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");
|
||||||
USER.uploadPfp(file).then(() => {
|
let ctx = canvas.getContext("2d");
|
||||||
me.profilePicture = file.name
|
canvas.width = 256;
|
||||||
// reload
|
canvas.height = 256;
|
||||||
getMe()
|
let img = document.createElement("img");
|
||||||
}).catch(e => error = e.message).finally(() => submitting = "")
|
img.onload = () => {
|
||||||
|
ctx?.drawImage(img, pfpCrop.x, pfpCrop.y, pfpCrop.width, pfpCrop.height, 0, 0, 256, 256);
|
||||||
|
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/jpg":
|
||||||
|
case "image/jpeg":
|
||||||
|
case "image/webp":
|
||||||
|
pfpCropURL = URL.createObjectURL(file);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
error = t("settings.profile.bad-format");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const passwordAction = (node: HTMLInputElement, whether: boolean) => {
|
const passwordAction = (node: HTMLInputElement, whether: boolean) => {
|
||||||
if (whether) node.type = 'password'
|
if (whether) node.type = 'password'
|
||||||
|
@ -107,8 +152,9 @@
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Genuinely don't know why this is giving me an intellisense error. Works fine. -->
|
||||||
<input id="profile-upload" type="file" accept="image/*" style="display: none" bind:this={pfpField}
|
<input id="profile-upload" type="file" accept="image/*" style="display: none" bind:this={pfpField}
|
||||||
on:change={() => pfpField.files && uploadPfp(pfpField.files[0])} />
|
on:change={handlePfpUpload} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#each profileFields as [field, name], i (field)}
|
{#each profileFields as [field, name], i (field)}
|
||||||
|
@ -155,6 +201,22 @@
|
||||||
<StatusOverlays {error} loading={!me || !!submitting} />
|
<StatusOverlays {error} loading={!me || !!submitting} />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
{#if pfpCropURL != null}
|
||||||
|
<div class="overlay" transition:fade>
|
||||||
|
<div>
|
||||||
|
<div class="cropper-container">
|
||||||
|
<Cropper maxZoom={1e9} oncropcomplete={(e) => pfpCrop = e.pixels} image={pfpCropURL ?? "assets/imgs/no_profile.png"} aspect={1} cropShape="round"></Cropper>
|
||||||
|
</div>
|
||||||
|
<button on:click={uploadPfp}>
|
||||||
|
{t("settings.profile.save")}
|
||||||
|
</button>
|
||||||
|
<button on:click={getMe}>
|
||||||
|
{t("back")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style lang="sass">
|
<style lang="sass">
|
||||||
@use "../../vars"
|
@use "../../vars"
|
||||||
|
|
||||||
|
@ -196,5 +258,10 @@
|
||||||
max-height: 100px
|
max-height: 100px
|
||||||
border-radius: vars.$border-radius
|
border-radius: vars.$border-radius
|
||||||
object-fit: cover
|
object-fit: cover
|
||||||
|
aspect-ratio: 1
|
||||||
|
|
||||||
|
.cropper-container
|
||||||
|
position: relative
|
||||||
|
width: 400px
|
||||||
|
aspect-ratio: 1
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in New Issue