feat(contact): notifications system + action

This commit is contained in:
2025-11-17 21:22:26 +01:00
parent f7d33bc0cd
commit e4935a24a2
12 changed files with 146 additions and 36 deletions

View File

@@ -1,20 +1,31 @@
<script setup lang="ts">
const props = defineProps<{
okLabel: "Copy" | "Open";
}>();
const store = useContactStore();
const topBarImage = useTemplateRef("topBarImage");
const bottomBarImage = useTemplateRef("bottomBarImage");
const bottomBarOkImage = useTemplateRef("bottomBarOkImage");
useRender((ctx) => {
if (!topBarImage.value || !bottomBarImage.value) return;
if (!topBarImage.value || !bottomBarImage.value || !bottomBarOkImage.value)
return;
ctx.globalAlpha = store.isIntro ? store.intro.stage3Opacity : 1;
// top bar
ctx.drawImage(topBarImage.value, 0, store.isIntro ? store.intro.topBarY : 0);
ctx.drawImage(
bottomBarImage.value,
0,
store.isIntro ? store.intro.bottomBarY : SCREEN_HEIGHT - 24,
);
// bottom bar
ctx.translate(0, store.isIntro ? store.intro.bottomBarY : SCREEN_HEIGHT - 24);
ctx.drawImage(bottomBarImage.value, 0, 0);
ctx.drawImage(bottomBarOkImage.value, 144, 4);
ctx.font = "10px NDS10";
ctx.fillStyle = "#000000";
ctx.fillText(props.okLabel, 144 + 35, 4 + 13);
});
</script>
@@ -29,4 +40,9 @@ useRender((ctx) => {
src="/assets/images/contact/bottom-screen/bottom-bar.png"
hidden
/>
<img
ref="bottomBarOkImage"
src="/assets/images/contact/bottom-screen/ok-button.png"
hidden
/>
</template>

View File

@@ -6,7 +6,17 @@ import Bars from "./Bars.vue";
const store = useContactStore();
const { selectorPosition } = useButtonNavigation({
const ACTIONS = {
github: ["Open", "Github profile", "https://github.com/pihkaal"],
email: ["Copy", "Email", "hello@pihkaal.me"],
website: ["Copy", "Website link", "https://pihkaal.me"],
cv: ["Open", "CV", "https://pihkaal.me/cv"],
} as const satisfies Record<
string,
[action: "Copy" | "Open", verb: string, content: string]
>;
const { selectedButton, selectorPosition } = useButtonNavigation({
buttons: {
github: [26, 27, 202, 42],
email: [26, 59, 202, 42],
@@ -30,8 +40,19 @@ const { selectorPosition } = useButtonNavigation({
},
},
initialButton: "github",
onButtonClick: (buttonName) => {
console.log("Clicked on selected button:", buttonName);
onButtonClick: async (button) => {
const [action, verb, content] = ACTIONS[button];
if (action === "Copy") {
try {
await navigator.clipboard.writeText(content);
store.pushNotification(`${verb} copied to clipboard`);
} catch (error) {
console.error("Failed to copy to clipboard:", error);
}
} else {
await navigateTo(content, { open: { target: "_blank " } });
store.pushNotification(`${verb} opened`);
}
},
});
</script>
@@ -45,5 +66,5 @@ const { selectorPosition } = useButtonNavigation({
:opacity="store.isIntro ? store.intro.stage3Opacity : 1"
/>
<Bars />
<Bars :ok-label="ACTIONS[selectedButton][0]" />
</template>

View File

@@ -0,0 +1,83 @@
<script setup lang="ts">
// text color:
const store = useContactStore();
const notificationImage = useTemplateRef("notificationImage");
const titleImage = useTemplateRef("titleImage");
useRender((ctx) => {
if (!notificationImage.value || !titleImage.value) return;
ctx.font = "10px NDS10";
// notifications
for (let i = store.notifications.length - 1; i >= 0; i--) {
const index = store.notifications.length - 1 - i;
const y = 169 - 24 * index + store.notificationsYOffset;
if (y < -24) break;
ctx.drawImage(notificationImage.value, 21, y);
const content = store.notifications[i]!;
ctx.fillStyle = content.includes("opened") ? "#00fbba" : "#e3f300";
ctx.fillText(store.notifications[i]!, 27, y + 15);
}
// title
ctx.globalAlpha = store.isIntro ? store.intro.stage1Opacity : 1;
ctx.drawImage(
titleImage.value,
21,
store.isIntro
? store.intro.titleY
: 169 - 24 * store.notifications.length + store.notificationsYOffset,
);
// notifications count (left bar)
const MAX = 36;
const MAX_VISIBLE = 8;
let visibleNotifications = Math.min(store.notifications.length, MAX_VISIBLE);
const extraActive =
store.notificationsYOffset > 0 && store.notifications.length > MAX_VISIBLE;
if (extraActive) {
visibleNotifications += 1;
}
ctx.fillStyle = "#415969";
for (let i = 0; i < visibleNotifications; i++) {
ctx.fillRect(3, 161 - i * 4, 12, 2);
}
ctx.fillStyle = "#b2c3db";
const startY = 161 - visibleNotifications * 4;
const top = MAX - MAX_VISIBLE - (extraActive ? 1 : 0);
for (
let i = 0;
i < store.notifications.length - visibleNotifications && i < top;
i++
) {
if (i === top - 1) {
ctx.fillRect(7, startY - i * 4, 4, 2);
} else if (i === top - 2) {
ctx.fillRect(5, startY - i * 4, 8, 2);
} else {
ctx.fillRect(3, startY - i * 4, 12, 2);
}
}
});
</script>
<template>
<img
ref="notificationImage"
src="/assets/images/contact/bottom-screen/notification.png"
hidden
/>
<img
ref="titleImage"
src="/assets/images/contact/top-screen/title.png"
hidden
/>
</template>

View File

@@ -1,24 +0,0 @@
<script setup lang="ts">
const store = useContactStore();
const titleImage = useTemplateRef("titleImage");
useRender((ctx) => {
if (!titleImage.value) return;
ctx.globalAlpha = store.isIntro ? store.intro.stage1Opacity : 1;
ctx.drawImage(
titleImage.value,
21,
store.isIntro ? store.intro.titleY : SCREEN_HEIGHT - 23,
);
});
</script>
<template>
<img
ref="titleImage"
src="/assets/images/contact/top-screen/title.png"
hidden
/>
</template>

View File

@@ -1,7 +1,7 @@
<script setup lang="ts">
import Background from "./Background.vue";
import LeftBar from "./LeftBar.vue";
import Title from "./Title.vue";
import Notifications from "./Notifications.vue";
const store = useContactStore();
@@ -15,5 +15,6 @@ onMounted(() => {
<Background />
<LeftBar />
<Title />
<Notifications />
</template>