Files
pihkaal-me/app/components/Settings/BottomScreen/Menus/Options/Language.vue

245 lines
5.4 KiB
Vue

<script setup lang="ts">
import gsap from "gsap";
const { locales, locale, setLocale } = useI18n();
const store = useSettingsStore();
const confirmationModal = useConfirmationModal();
const { assets } = useAssets();
const { onRender } = useScreen();
const BUTTON_KEYS = [
"english",
"german",
"french",
"spanish",
"italian",
"japanese",
] as const;
const BUTTON_POSITIONS = [
[15, 32],
[143, 32],
[15, 80],
[143, 80],
[15, 128],
[143, 128],
] as const;
const isAnimating = ref(true);
const bLabel = ref($t("common.goBack"));
const aLabel = ref($t("common.select"));
const { selected, selectorPosition } = useButtonNavigation({
buttons: {
english: [10, 27, 106, 41],
german: [138, 27, 106, 41],
french: [10, 75, 106, 41],
spanish: [138, 75, 106, 41],
italian: [10, 123, 106, 41],
japanese: [138, 123, 106, 41],
},
initialButton:
BUTTON_KEYS[locales.value.findIndex((l) => l.code === locale.value)] ??
BUTTON_KEYS[0]!,
onActivate: () => handleActivateA(),
navigation: {
english: {
right: "german",
down: "french",
},
german: {
left: "english",
down: "spanish",
},
french: {
up: "english",
right: "spanish",
down: "italian",
},
spanish: {
up: "german",
left: "french",
down: "japanese",
},
italian: {
up: "french",
right: "japanese",
},
japanese: {
up: "spanish",
left: "italian",
},
},
disabled: isAnimating,
selectorAnimation: {
duration: 0.1,
ease: "power2.out",
},
});
const SLIDE_OFFSET = 48;
const SLIDE_DURATION = 0.15;
const OUTRO_OFFSET = 96;
const OUTRO_DURATION = 0.25;
const ROW_STAGGER = SLIDE_DURATION + 0.075;
const ROW_COUNT = 3;
const selectedRow = computed(() => {
const index = BUTTON_KEYS.indexOf(selected.value);
return Math.floor(index / 2);
});
const animation = reactive({
rowOffsetY: Array(ROW_COUNT).fill(SLIDE_OFFSET) as number[],
rowOpacity: Array(ROW_COUNT).fill(0) as number[],
});
const animateIntro = async () => {
isAnimating.value = true;
const timeline = gsap.timeline({
onComplete: () => {
isAnimating.value = false;
},
});
for (let i = 0; i < ROW_COUNT; i++) {
timeline
.to(
animation.rowOffsetY,
{ [i]: 0, duration: SLIDE_DURATION, ease: "none" },
i * ROW_STAGGER,
)
.to(
animation.rowOpacity,
{ [i]: 1, duration: SLIDE_DURATION, ease: "none" },
i * ROW_STAGGER,
);
}
timeline.call(
() => {
bLabel.value = $t("common.cancel");
aLabel.value = $t("common.confirm");
},
[],
SUBMENU_LABEL_CHANGE_DELAY,
);
await timeline;
};
const animateOutro = async () => {
isAnimating.value = true;
const timeline = gsap.timeline({
onComplete: () => {
isAnimating.value = false;
},
});
for (let i = 0; i < ROW_COUNT; i++) {
timeline
.to(
animation.rowOffsetY,
{ [i]: OUTRO_OFFSET, duration: OUTRO_DURATION, ease: "none" },
0,
)
.to(
animation.rowOpacity,
{ [i]: 0, duration: OUTRO_DURATION, ease: "none" },
0,
);
}
await timeline;
};
onMounted(() => {
animateIntro();
});
const handleActivateB = async () => {
if (isAnimating.value) return;
await animateOutro();
store.closeSubMenu();
};
const AVAILABLE_LOCALES = ["en", "fr"];
const handleActivateA = () => {
if (isAnimating.value) return;
const selectedLocale = locales.value[BUTTON_KEYS.indexOf(selected.value)]!;
if (!AVAILABLE_LOCALES.includes(selectedLocale.code)) {
confirmationModal.open({
text: $t("settings.options.language.unavailable"),
timeout: 2000,
});
return;
}
setLocale(selectedLocale.code);
confirmationModal.open({
text: $t(
"settings.options.language.confirmation",
{},
{ locale: selectedLocale.code },
),
onClosed: async () => {
await animateOutro();
store.closeSubMenu(true);
},
keepButtonsDown: true,
timeout: 2000,
});
};
onRender((ctx) => {
ctx.font = "10px NDS10";
ctx.fillStyle = "#010101";
for (let i = 0; i < locales.value.length; i += 1) {
const row = Math.floor(i / 2);
const [x, y] = BUTTON_POSITIONS[i]!;
const isSelected = selected.value === BUTTON_KEYS[i];
const buttonImage = isSelected
? assets.images.settings.bottomScreen.options.languageButtonActive
: assets.images.settings.bottomScreen.options.languageButton;
ctx.save();
ctx.globalAlpha = animation.rowOpacity[row]!;
ctx.translate(0, animation.rowOffsetY[row]!);
buttonImage.draw(ctx, x, y, isSelected ? { colored: true } : undefined);
const name = locales.value[i]?.name;
if (!name) {
throw new Error(`Missing locale or locale name: ${BUTTON_KEYS[i]}`);
}
fillTextHCentered(ctx, name, x, y + 20, 96);
ctx.restore();
}
});
defineOptions({
render: () => null,
});
</script>
<template>
<CommonButtons
:y-offset="confirmationModal.buttonsYOffset + store.submenuButtonsOffsetY"
:b-label="bLabel"
:a-label="aLabel"
no-keyboard-a
@activate-a="handleActivateA"
@activate-b="handleActivateB"
/>
<CommonButtonSelector
:rect="[
selectorPosition[0],
selectorPosition[1] + animation.rowOffsetY[selectedRow]!,
selectorPosition[2],
selectorPosition[3],
]"
:opacity="animation.rowOpacity[selectedRow]!"
/>
</template>