Compare commits
3 Commits
4b7b5b5154
...
e454a48532
| Author | SHA1 | Date | |
|---|---|---|---|
| e454a48532 | |||
| 43cad4f36b | |||
| d126eed766 |
@@ -326,26 +326,6 @@ const dispatchScreenEvent = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const dispatchScreenTouchEvent = (
|
|
||||||
type: string,
|
|
||||||
canvas: HTMLCanvasElement,
|
|
||||||
intersection: THREE.Intersection,
|
|
||||||
) => {
|
|
||||||
const coords = toScreenCoords(canvas, intersection);
|
|
||||||
if (!coords) return;
|
|
||||||
|
|
||||||
const touch = new Touch({ identifier: 0, target: canvas, ...coords });
|
|
||||||
|
|
||||||
canvas.dispatchEvent(
|
|
||||||
new TouchEvent(type, {
|
|
||||||
bubbles: true,
|
|
||||||
cancelable: true,
|
|
||||||
touches: type === "touchend" ? [] : [touch],
|
|
||||||
changedTouches: [touch],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const BUTTON_MAP = {
|
const BUTTON_MAP = {
|
||||||
[X_BUTTON]: "X",
|
[X_BUTTON]: "X",
|
||||||
[A_BUTTON]: "A",
|
[A_BUTTON]: "A",
|
||||||
@@ -441,10 +421,40 @@ const handleMouseUp = (event: MouseEvent) => {
|
|||||||
if (canvas) dispatchScreenEvent("mouseup", canvas, intersection);
|
if (canvas) dispatchScreenEvent("mouseup", canvas, intersection);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SWIPE_THRESHOLD = 30;
|
||||||
|
let swipeStartX = 0;
|
||||||
|
let swipeStartY = 0;
|
||||||
|
|
||||||
|
const dispatchSwipe = (endX: number, endY: number) => {
|
||||||
|
const deltaX = endX - swipeStartX;
|
||||||
|
const deltaY = endY - swipeStartY;
|
||||||
|
const absDeltaX = Math.abs(deltaX);
|
||||||
|
const absDeltaY = Math.abs(deltaY);
|
||||||
|
|
||||||
|
if (Math.max(absDeltaX, absDeltaY) < SWIPE_THRESHOLD) return;
|
||||||
|
|
||||||
|
const direction =
|
||||||
|
absDeltaX > absDeltaY
|
||||||
|
? deltaX > 0
|
||||||
|
? "RIGHT"
|
||||||
|
: "LEFT"
|
||||||
|
: deltaY > 0
|
||||||
|
? "DOWN"
|
||||||
|
: "UP";
|
||||||
|
|
||||||
|
window.dispatchEvent(
|
||||||
|
new KeyboardEvent("keydown", { key: `NDS_SWIPE_${direction}` }),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const handleTouchStart = (event: TouchEvent) => {
|
const handleTouchStart = (event: TouchEvent) => {
|
||||||
const touch = event.touches[0];
|
const touch = event.touches[0];
|
||||||
if (!touch) return;
|
if (!touch) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
swipeStartX = touch.clientX;
|
||||||
|
swipeStartY = touch.clientY;
|
||||||
|
|
||||||
if (!hasAnimated.value) {
|
if (!hasAnimated.value) {
|
||||||
animateIntro();
|
animateIntro();
|
||||||
return;
|
return;
|
||||||
@@ -452,7 +462,6 @@ const handleTouchStart = (event: TouchEvent) => {
|
|||||||
|
|
||||||
handleInteraction(touch.clientX, touch.clientY, (canvas, intersection) => {
|
handleInteraction(touch.clientX, touch.clientY, (canvas, intersection) => {
|
||||||
dispatchScreenEvent("mousedown", canvas, intersection);
|
dispatchScreenEvent("mousedown", canvas, intersection);
|
||||||
dispatchScreenTouchEvent("touchstart", canvas, intersection);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -462,14 +471,22 @@ const handleTouchEnd = (event: TouchEvent) => {
|
|||||||
const touch = event.changedTouches[0];
|
const touch = event.changedTouches[0];
|
||||||
if (!touch) return;
|
if (!touch) return;
|
||||||
|
|
||||||
|
dispatchSwipe(touch.clientX, touch.clientY);
|
||||||
|
|
||||||
const intersection = raycast(touch.clientX, touch.clientY);
|
const intersection = raycast(touch.clientX, touch.clientY);
|
||||||
if (!intersection?.uv) return;
|
if (!intersection?.uv) return;
|
||||||
|
|
||||||
const canvas = getScreenCanvas(intersection.object.name);
|
const canvas = getScreenCanvas(intersection.object.name);
|
||||||
if (canvas) {
|
if (canvas) {
|
||||||
dispatchScreenEvent("click", canvas, intersection);
|
dispatchScreenEvent("click", canvas, intersection);
|
||||||
dispatchScreenTouchEvent("touchend", canvas, intersection);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.dispatchEvent(
|
||||||
|
new MouseEvent("mouseup", {
|
||||||
|
clientX: touch.clientX,
|
||||||
|
clientY: touch.clientY,
|
||||||
|
}),
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -33,16 +33,19 @@ const registerScreenMouseWheelCallback = (
|
|||||||
return () => screenMouseWheelCallbacks.delete(callback);
|
return () => screenMouseWheelCallbacks.delete(callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCanvasClick = (event: MouseEvent) => {
|
const toLogicalCoords = (clientX: number, clientY: number) => {
|
||||||
if (!canvas.value) return;
|
const rect = canvas.value!.getBoundingClientRect();
|
||||||
|
|
||||||
const rect = canvas.value.getBoundingClientRect();
|
|
||||||
const scaleX = LOGICAL_WIDTH / rect.width;
|
const scaleX = LOGICAL_WIDTH / rect.width;
|
||||||
const scaleY = LOGICAL_HEIGHT / rect.height;
|
const scaleY = LOGICAL_HEIGHT / rect.height;
|
||||||
|
return [
|
||||||
|
(clientX - rect.left) * scaleX,
|
||||||
|
(clientY - rect.top) * scaleY,
|
||||||
|
] as const;
|
||||||
|
};
|
||||||
|
|
||||||
const x = (event.clientX - rect.left) * scaleX;
|
const handleCanvasClick = (event: MouseEvent) => {
|
||||||
const y = (event.clientY - rect.top) * scaleY;
|
if (!canvas.value) return;
|
||||||
|
const [x, y] = toLogicalCoords(event.clientX, event.clientY);
|
||||||
for (const callback of screenClickCallbacks) {
|
for (const callback of screenClickCallbacks) {
|
||||||
callback(x, y);
|
callback(x, y);
|
||||||
}
|
}
|
||||||
@@ -50,14 +53,7 @@ const handleCanvasClick = (event: MouseEvent) => {
|
|||||||
|
|
||||||
const handleCanvasMouseDown = (event: MouseEvent) => {
|
const handleCanvasMouseDown = (event: MouseEvent) => {
|
||||||
if (!canvas.value) return;
|
if (!canvas.value) return;
|
||||||
|
const [x, y] = toLogicalCoords(event.clientX, event.clientY);
|
||||||
const rect = canvas.value.getBoundingClientRect();
|
|
||||||
const scaleX = LOGICAL_WIDTH / rect.width;
|
|
||||||
const scaleY = LOGICAL_HEIGHT / rect.height;
|
|
||||||
|
|
||||||
const x = (event.clientX - rect.left) * scaleX;
|
|
||||||
const y = (event.clientY - rect.top) * scaleY;
|
|
||||||
|
|
||||||
for (const callback of screenMouseDownCallbacks) {
|
for (const callback of screenMouseDownCallbacks) {
|
||||||
callback(x, y);
|
callback(x, y);
|
||||||
}
|
}
|
||||||
@@ -97,6 +93,7 @@ const handleTouchStart = (event: TouchEvent) => {
|
|||||||
const touch = event.touches[0];
|
const touch = event.touches[0];
|
||||||
if (!touch) return;
|
if (!touch) return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
swipeStartX = touch.clientX;
|
swipeStartX = touch.clientX;
|
||||||
swipeStartY = touch.clientY;
|
swipeStartY = touch.clientY;
|
||||||
|
|
||||||
@@ -113,6 +110,14 @@ const handleTouchEnd = (event: TouchEvent) => {
|
|||||||
if (!touch) return;
|
if (!touch) return;
|
||||||
|
|
||||||
dispatchSwipe(touch.clientX, touch.clientY);
|
dispatchSwipe(touch.clientX, touch.clientY);
|
||||||
|
|
||||||
|
canvas.value?.dispatchEvent(
|
||||||
|
new MouseEvent("click", {
|
||||||
|
clientX: touch.clientX,
|
||||||
|
clientY: touch.clientY,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
document.dispatchEvent(
|
document.dispatchEvent(
|
||||||
new MouseEvent("mouseup", {
|
new MouseEvent("mouseup", {
|
||||||
clientX: touch.clientX,
|
clientX: touch.clientX,
|
||||||
@@ -188,9 +193,7 @@ onMounted(() => {
|
|||||||
canvas.value.addEventListener("click", handleCanvasClick);
|
canvas.value.addEventListener("click", handleCanvasClick);
|
||||||
canvas.value.addEventListener("mousedown", handleCanvasMouseDown);
|
canvas.value.addEventListener("mousedown", handleCanvasMouseDown);
|
||||||
canvas.value.addEventListener("wheel", handleCanvasWheel, { passive: true });
|
canvas.value.addEventListener("wheel", handleCanvasWheel, { passive: true });
|
||||||
canvas.value.addEventListener("touchstart", handleTouchStart, {
|
canvas.value.addEventListener("touchstart", handleTouchStart);
|
||||||
passive: true,
|
|
||||||
});
|
|
||||||
canvas.value.addEventListener("touchend", handleTouchEnd, { passive: true });
|
canvas.value.addEventListener("touchend", handleTouchEnd, { passive: true });
|
||||||
canvas.value.addEventListener("mousedown", handleSwipeMouseDown);
|
canvas.value.addEventListener("mousedown", handleSwipeMouseDown);
|
||||||
canvas.value.addEventListener("mouseup", handleSwipeMouseUp);
|
canvas.value.addEventListener("mouseup", handleSwipeMouseUp);
|
||||||
|
|||||||
@@ -132,8 +132,6 @@ const animateIntro = async () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const galleryStore = useGalleryStore();
|
|
||||||
|
|
||||||
const animateOutro = async () => {
|
const animateOutro = async () => {
|
||||||
isAnimating.value = true;
|
isAnimating.value = true;
|
||||||
|
|
||||||
@@ -157,7 +155,6 @@ const animateOutro = async () => {
|
|||||||
typeText(descriptionText, description.value, DESCRIPTION_DURATION, true, {
|
typeText(descriptionText, description.value, DESCRIPTION_DURATION, true, {
|
||||||
onComplete: async () => {
|
onComplete: async () => {
|
||||||
await promiseTimeout(ANIMATION_SLEEP * 1000);
|
await promiseTimeout(ANIMATION_SLEEP * 1000);
|
||||||
galleryStore.shouldAnimateOutro = true;
|
|
||||||
router.push("/");
|
router.push("/");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export const useAppStore = defineStore("app", {
|
|||||||
settings,
|
settings,
|
||||||
previousScreen: "home" as AppScreen,
|
previousScreen: "home" as AppScreen,
|
||||||
screen: "home" as AppScreen,
|
screen: "home" as AppScreen,
|
||||||
|
visitedGallery: false,
|
||||||
camera: null as THREE.Camera | null,
|
camera: null as THREE.Camera | null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -76,7 +77,7 @@ export const useAppStore = defineStore("app", {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "gallery":
|
case "gallery":
|
||||||
achievements.unlock("gallery_visit");
|
this.visitedGallery = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "contact":
|
case "contact":
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ export const useGalleryStore = defineStore("gallery", {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
this.shouldAnimateOutro = true;
|
||||||
navigateTo("/gallery");
|
navigateTo("/gallery");
|
||||||
}, ANIMATION.NAVIGATE_DELAY);
|
}, ANIMATION.NAVIGATE_DELAY);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -40,6 +40,12 @@ export const useHomeStore = defineStore("home", {
|
|||||||
this.$reset();
|
this.$reset();
|
||||||
this.selectedButton = selectedButton;
|
this.selectedButton = selectedButton;
|
||||||
this.animateIntro();
|
this.animateIntro();
|
||||||
|
|
||||||
|
if (app.visitedGallery) {
|
||||||
|
app.visitedGallery = false;
|
||||||
|
const achievements = useAchievementsStore();
|
||||||
|
achievements.unlock("gallery_visit");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
animateIntro() {
|
animateIntro() {
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import { createHash } from "crypto";
|
|||||||
import exifr from "exifr";
|
import exifr from "exifr";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const galleryDir = import.meta.dev
|
||||||
|
? join(process.cwd(), "public/gallery")
|
||||||
|
: join(process.cwd(), ".output/public/gallery");
|
||||||
|
|
||||||
const exifSchema = z.object({
|
const exifSchema = z.object({
|
||||||
DateTimeOriginal: z.date(),
|
DateTimeOriginal: z.date(),
|
||||||
Make: z.string(),
|
Make: z.string(),
|
||||||
@@ -19,17 +23,15 @@ const exifSchema = z.object({
|
|||||||
|
|
||||||
export default defineCachedEventHandler(
|
export default defineCachedEventHandler(
|
||||||
async () => {
|
async () => {
|
||||||
const publicDir = join(process.cwd(), "public/gallery");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const files = await readdir(publicDir);
|
const files = await readdir(galleryDir);
|
||||||
const imageFiles = files.filter((file) =>
|
const imageFiles = files.filter((file) =>
|
||||||
/\.(jpg|jpeg|png|webp)$/i.test(file),
|
/\.(jpg|jpeg|png|webp)$/i.test(file),
|
||||||
);
|
);
|
||||||
|
|
||||||
const imagesWithExif = await Promise.all(
|
const imagesWithExif = await Promise.all(
|
||||||
imageFiles.map(async (filename) => {
|
imageFiles.map(async (filename) => {
|
||||||
const filePath = join(publicDir, filename);
|
const filePath = join(galleryDir, filename);
|
||||||
const rawExif = await exifr.parse(filePath, {
|
const rawExif = await exifr.parse(filePath, {
|
||||||
tiff: true,
|
tiff: true,
|
||||||
exif: true,
|
exif: true,
|
||||||
@@ -72,9 +74,8 @@ export default defineCachedEventHandler(
|
|||||||
{
|
{
|
||||||
maxAge: 60 * 60 * 24,
|
maxAge: 60 * 60 * 24,
|
||||||
getKey: async () => {
|
getKey: async () => {
|
||||||
const publicDir = join(process.cwd(), "public/gallery");
|
|
||||||
try {
|
try {
|
||||||
const files = await readdir(publicDir);
|
const files = await readdir(galleryDir);
|
||||||
const imageFiles = files.filter((file) =>
|
const imageFiles = files.filter((file) =>
|
||||||
/\.(jpg|jpeg|png|webp)$/i.test(file),
|
/\.(jpg|jpeg|png|webp)$/i.test(file),
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user