Files
corner/src/views/Photo.vue
T
2025-10-27 18:34:14 +08:00

157 lines
4.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import { Vue, Component, toNative } from 'vue-facing-decorator';
interface PhotoMetadata {
id: string
owner_key: string // This will be filtered out for public requests
upload_time: string
original_photo: string
edited_photo: string
thumbnail: string
thumbnail_edited: string
exif: {[id: string]: string}
}
// Take in a string, use its hash to produce a number from 0 to 1
function detRandom(seed: string): number {
return Array.from(seed).reduce((acc, char) => (acc + char.charCodeAt(0) * 65535) % 22859, 0) / 22859
}
@Component({})
class Photos extends Vue {
photos: PhotoMetadata[]
photoRows: PhotoMetadata[][]
async created() {
this.photos = await (await fetch('https://p.aza.moe/photos')).json()
this.photos.sort((a, b) => (a.exif.DateTime < b.exif.DateTime ? 1 : -1))
let rowProbabilityTable = {
1: 0, 2: 0.3, 3: 0.5
}
// Generate photo rows: there is a 10% chance that a photo will be the only photo in its row
this.photoRows = []
let currentRow: PhotoMetadata[] = []
this.photos.forEach((p) => {
if (currentRow.length === 0) currentRow.push(p)
else if (currentRow.length >= 3) {
this.photoRows.push(currentRow)
currentRow = [ p ]
}
else {
const singleChance = detRandom(p.original_photo)
if (singleChance < rowProbabilityTable[currentRow.length]) {
this.photoRows.push(currentRow)
currentRow = [ p ]
} else currentRow.push(p)
}
})
if (currentRow.length > 0) this.photoRows.push(currentRow)
}
url(s: string): string {
s = s.replace('data/photos', 'static').replace('./', '')
return `https://p.aza.moe/${s}`
}
randomRotation(s: string): string {
const angle = (detRandom(s) * 20) - 10 // -10 to +10 degrees
return `rotate(${angle}deg)`
}
async clickPhoto(p: PhotoMetadata, e: MouseEvent) {
console.log("Clicked photo:", p.id)
const dom = e.currentTarget as HTMLDivElement
const photoEl = dom.querySelector('.photo-wrapper') as HTMLDivElement
photoEl.style.viewTransitionName = `photo-${p.id}`
const transition = document.startViewTransition(() => {
dom.classList.toggle('active')
document.getElementsByClassName('blur')[0].toggleAttribute('hidden')
})
await transition.finished
photoEl.style.viewTransitionName = ''
}
}
export default toNative(Photos)
</script>
<template>
<div class="title">
<div class="font-script-en bold">The Wandering Gallery</div>
<div class="subtitle <sm:hidden">想要把旅行中用相机拍到好看照片时的喜悦分享给照片里出现的路人所以买了便携照片打印机搭了这个网页</div>
</div>
<div class="outer-grid">
<div v-for="row in photoRows" :key="row[0].id" flex justify-center :class="`grid-cols-${row.length}`">
<div v-for="p in row" :key="p.id" @click.capture="async e => await clickPhoto(p, e)"
class="img-container" cursor-pointer>
<img class="photo" w-full h-full object-contain opacity-0 :src="url(p.thumbnail_edited)" :alt="p.id"/>
<div class="photo-abs-container" absolute inset-0 flex justify-center items-center>
<div class="photo-wrapper" :style="{transform: randomRotation(p.id)}">
<img class="photo" w-full object-contain :src="url(p.thumbnail_edited)" :alt="p.id" />
</div>
</div>
<div flex w-full justify-center absolute position-top-none>
<img class="pin" src="/thumb%20tack%202%20plain.png" alt=""/>
</div>
</div>
</div>
</div>
<div class="blur" hidden pos-fixed inset-0 backdrop-blur-sm z-5></div>
</template>
<style scoped lang="sass">
@use "../css/colors"
@use "../css/responsive"
.blur
z-index: 2500
.title
margin-top: 8rem
margin-bottom: 6rem
.bold
font-size: 3em
.img-container
margin: -0.5rem
max-width: 50%
position: relative
.img-container.active
position: unset
img.pin
display: none
.photo-abs-container
position: fixed
z-index: 3000
.photo-wrapper
transform: rotate(0deg) !important
img.photo
z-index: 3001
img.photo
clip-path: inset(2.8% 1.8% 2.4%)
pointer-events: none
div.photo-abs-container
position: absolute
inset: 0
z-index: 1000
div.photo-wrapper
filter: drop-shadow(0px 1px 2px rgba(0, 0, 0, 0.3))
img.pin
width: 40px
height: 40px
z-index: 2000
</style>