Compare commits
2 Commits
39afc25aba
...
2891ed9620
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2891ed9620 | ||
|
|
0d6d08f952 |
BIN
public/Logo-200x60.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/assets/footer-1x4.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
4
public/assets/footer-1x4.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="420" height="50" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="420" height="50" fill="#dc3545"/>
|
||||
<text x="210" y="30" text-anchor="middle" fill="white" font-family="Arial" font-size="16" font-weight="bold">Footer 1x4</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 249 B |
BIN
public/assets/footer-2x2.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
4
public/assets/footer-2x2.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="780" height="60" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="780" height="60" fill="#ffc107"/>
|
||||
<text x="390" y="35" text-anchor="middle" fill="black" font-family="Arial" font-size="18" font-weight="bold">Footer 2x2</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 249 B |
BIN
public/assets/header-1x4.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
4
public/assets/header-1x4.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="420" height="50" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="420" height="50" fill="#007bff"/>
|
||||
<text x="210" y="30" text-anchor="middle" fill="white" font-family="Arial" font-size="16" font-weight="bold">Header 1x4</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 249 B |
BIN
public/assets/header-2x2.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
4
public/assets/header-2x2.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="780" height="60" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="780" height="60" fill="#28a745"/>
|
||||
<text x="390" y="35" text-anchor="middle" fill="white" font-family="Arial" font-size="18" font-weight="bold">Header 2x2</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 249 B |
@@ -4,7 +4,7 @@ import HelloWorld from './components/HelloWorld.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header>
|
||||
<!-- <header >
|
||||
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
|
||||
|
||||
<div class="wrapper">
|
||||
@@ -15,8 +15,9 @@ import HelloWorld from './components/HelloWorld.vue'
|
||||
<RouterLink to="/about">About</RouterLink>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
</header> -->
|
||||
|
||||
<RouterLink to="/">Home</RouterLink>
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
|
||||
BIN
src/assets/footer-1x4.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
src/assets/footer-2x2.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src/assets/header-1x4.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
src/assets/header-2x2.png
Normal file
@@ -69,7 +69,7 @@ const goBack = () => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="frame-section">
|
||||
<section class="frame-section" style="display: none;">
|
||||
<h2>เลือกกรอบ</h2>
|
||||
<div class="frame-options">
|
||||
<div
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { ref, onMounted, computed, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
const photos = ref<string[]>([])
|
||||
const layout = ref<'1x4' | '2x2'>('1x4')
|
||||
const frame = ref<number>(0)
|
||||
const shareUrl = ref('')
|
||||
const qrCodeUrl = ref('')
|
||||
const cachedImageUrl = ref('')
|
||||
const isGeneratingImage = ref(false)
|
||||
const hasError = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
// โหลดข้อมูลจาก localStorage
|
||||
@@ -27,8 +28,8 @@ onMounted(() => {
|
||||
frame.value = parseInt(savedFrame)
|
||||
}
|
||||
|
||||
// สร้าง URL สำหรับแชร์ (ในโปรเจกต์จริงควรอัพโหลดขึ้น cloud)
|
||||
generateShareUrl()
|
||||
// สร้าง PNG และบันทึกใน localStorage (ถ้ายังไม่มี)
|
||||
generateAndCacheImage()
|
||||
})
|
||||
|
||||
const getGridStyle = computed(() => {
|
||||
@@ -38,7 +39,7 @@ const getGridStyle = computed(() => {
|
||||
gridTemplateColumns: '1fr',
|
||||
gridTemplateRows: 'repeat(4, 1fr)',
|
||||
gap: '8px',
|
||||
aspectRatio: '1/2' // ทำให้แต่ละภาพยาวขึ้น (1:2 aspect ratio รวม)
|
||||
aspectRatio: '1/1' // ทำให้แต่ละภาพยาวขึ้น (1:2 aspect ratio รวม)
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
@@ -51,104 +52,158 @@ const getGridStyle = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const generateShareUrl = () => {
|
||||
// ในโปรเจกต์จริง ควรอัพโหลดรูปขึ้น cloud และสร้าง URL
|
||||
// ตัวอย่างนี้ใช้ data URL ชั่วคราว
|
||||
const photoData = {
|
||||
photos: photos.value,
|
||||
layout: layout.value,
|
||||
frame: frame.value,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
const generateAndCacheImage = async () => {
|
||||
try {
|
||||
hasError.value = false
|
||||
if (photos.value.length === 0) return
|
||||
|
||||
// เข้ารหัสข้อมูลเป็น base64 และสร้าง URL
|
||||
const encodedData = btoa(JSON.stringify(photoData))
|
||||
shareUrl.value = `${window.location.origin}/share/${encodedData}`
|
||||
isGeneratingImage.value = true
|
||||
|
||||
// สร้าง QR code URL (ใช้ service ภายนอก)
|
||||
qrCodeUrl.value = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(shareUrl.value)}`
|
||||
}
|
||||
// สร้าง key สำหรับ localStorage
|
||||
const storageKey = `photobooth-${layout.value}-${frame.value}-${photos.value.length}`
|
||||
|
||||
const downloadImage = async () => {
|
||||
if (photos.value.length === 0) return
|
||||
// ตรวจสอบว่ามี PNG ที่สร้างไว้แล้วหรือไม่
|
||||
const cachedImage = localStorage.getItem(storageKey)
|
||||
if (cachedImage) {
|
||||
cachedImageUrl.value = cachedImage
|
||||
await nextTick()
|
||||
return // มีแล้ว ไม่ต้องสร้างใหม่
|
||||
}
|
||||
|
||||
// สร้าง canvas สำหรับรวมรูป
|
||||
const canvas = document.createElement('canvas')
|
||||
const context = canvas.getContext('2d')
|
||||
// สร้าง canvas สำหรับรวมรูป
|
||||
const canvas = document.createElement('canvas')
|
||||
const context = canvas.getContext('2d')
|
||||
|
||||
if (!context) return
|
||||
if (!context) {
|
||||
throw new Error('Could not get canvas context')
|
||||
}
|
||||
|
||||
// ขนาดรูปแต่ละภาพ (720x960 = 3:4)
|
||||
const photoWidth = 720
|
||||
const photoHeight = 960
|
||||
// ขนาดรูปแต่ละภาพ (360x480 = 3:4) - ลดขนาดลงเพื่อป้องกันปัญหา canvas ใหญ่เกินไป
|
||||
const photoWidth = 360
|
||||
const photoHeight = 480
|
||||
const gap = 20 // ระยะห่างระหว่างรูป
|
||||
|
||||
let canvasWidth, canvasHeight
|
||||
let headerHeight = 0, footerHeight = 0
|
||||
let sidePadding = 0
|
||||
|
||||
if (layout.value === '1x4') {
|
||||
// 1x4: 1 คอลัมน์ 4 แถว
|
||||
canvasWidth = photoWidth
|
||||
canvasHeight = (photoHeight * 4) + (gap * 3)
|
||||
// 1x4: 1 คอลัมน์ 4 แถว + header/footer
|
||||
headerHeight = 50
|
||||
footerHeight = 50
|
||||
sidePadding = 30 // padding ซ้ายขวาเพื่อให้ total width = 420
|
||||
canvasWidth = 420
|
||||
canvasHeight = headerHeight + (photoHeight * 4) + (gap * 3) + footerHeight
|
||||
} else {
|
||||
// 2x2: 2 คอลัมน์ 2 แถว
|
||||
canvasWidth = (photoWidth * 2) + gap
|
||||
canvasHeight = (photoHeight * 2) + gap
|
||||
// 2x2: 2 คอลัมน์ 2 แถว + header/footer
|
||||
headerHeight = 60
|
||||
footerHeight = 60
|
||||
sidePadding = 20 // padding ซ้ายขวาเพื่อให้ total width = 780
|
||||
canvasWidth = 780
|
||||
canvasHeight = headerHeight + (photoHeight * 2) + gap + footerHeight
|
||||
}
|
||||
|
||||
console.log('Canvas dimensions:', canvasWidth, 'x', canvasHeight)
|
||||
canvas.width = canvasWidth
|
||||
canvas.height = canvasHeight
|
||||
|
||||
// วาด background ตาม frame
|
||||
drawFrameBackground(context, canvasWidth, canvasHeight, frame.value)
|
||||
|
||||
// วาดรูปภาพ
|
||||
// วาด header image
|
||||
const headerImagePath = layout.value === '1x4' ? '/assets/header-1x4.png' : '/assets/header-2x2.png'
|
||||
await drawHeaderFooterImage(context, headerImagePath, 0, 0, canvasWidth, headerHeight)
|
||||
|
||||
// วาด footer image
|
||||
const footerImagePath = layout.value === '1x4' ? '/assets/footer-1x4.png' : '/assets/footer-2x2.png'
|
||||
await drawHeaderFooterImage(context, footerImagePath, 0, canvasHeight - footerHeight, canvasWidth, footerHeight)
|
||||
|
||||
// วาดรูปภาพ (มี padding ซ้ายขวา)
|
||||
for (let i = 0; i < photos.value.length; i++) {
|
||||
const photoSrc = photos.value[i]
|
||||
if (!photoSrc) continue
|
||||
|
||||
const img = new Image()
|
||||
img.src = photos.value[i]
|
||||
img.src = photoSrc
|
||||
|
||||
await new Promise((resolve) => {
|
||||
img.onload = () => {
|
||||
let x, y
|
||||
|
||||
if (layout.value === '1x4') {
|
||||
// 1x4 layout
|
||||
x = 0
|
||||
y = i * (photoHeight + gap)
|
||||
// 1x4 layout - วาดรูปตรงกลาง (มี padding ซ้ายขวา)
|
||||
x = sidePadding
|
||||
y = headerHeight + i * (photoHeight + gap)
|
||||
} else {
|
||||
// 2x2 layout
|
||||
// 2x2 layout - วาดรูปตรงกลาง (มี padding ซ้ายขวา)
|
||||
const col = i % 2
|
||||
const row = Math.floor(i / 2)
|
||||
x = col * (photoWidth + gap)
|
||||
y = row * (photoHeight + gap)
|
||||
x = sidePadding + col * (photoWidth + gap)
|
||||
y = headerHeight + row * (photoHeight + gap)
|
||||
}
|
||||
|
||||
// วาดรูปภาพ
|
||||
context.drawImage(img, x, y, photoWidth, photoHeight)
|
||||
resolve(void 0)
|
||||
}
|
||||
img.onerror = () => {
|
||||
console.error(`Failed to load image ${i}`)
|
||||
resolve(void 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// สร้าง PNG และ download
|
||||
canvas.toBlob((blob) => {
|
||||
if (blob) {
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `photobooth-${Date.now()}.png`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
// สร้าง PNG data URL และบันทึกใน localStorage
|
||||
const dataUrl = canvas.toDataURL('image/png')
|
||||
|
||||
try {
|
||||
localStorage.setItem(storageKey, dataUrl)
|
||||
cachedImageUrl.value = dataUrl
|
||||
// ให้ Vue reactive update ก่อน
|
||||
await nextTick()
|
||||
} catch (error) {
|
||||
if (error instanceof DOMException && error.name === 'QuotaExceededError') {
|
||||
console.error('localStorage quota exceeded for cached image')
|
||||
alert('ไม่สามารถบันทึกภาพได้เนื่องจากพื้นที่เก็บข้อมูลไม่เพียงพอ')
|
||||
} else {
|
||||
console.error('Error saving cached image:', error)
|
||||
throw error
|
||||
}
|
||||
}, 'image/png')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in generateAndCacheImage:', error)
|
||||
hasError.value = true
|
||||
} finally {
|
||||
isGeneratingImage.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const downloadImage = async () => {
|
||||
if (photos.value.length === 0) return
|
||||
|
||||
// ตรวจสอบว่า cachedImageUrl มีหรือไม่ ถ้าไม่มีให้สร้างก่อน
|
||||
if (!cachedImageUrl.value) {
|
||||
await generateAndCacheImage()
|
||||
}
|
||||
|
||||
// Download จาก cachedImageUrl
|
||||
downloadFromDataUrl(cachedImageUrl.value, `photobooth-${Date.now()}.png`)
|
||||
}
|
||||
|
||||
const downloadFromDataUrl = (dataUrl: string, filename: string) => {
|
||||
const a = document.createElement('a')
|
||||
a.href = dataUrl
|
||||
a.download = filename
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
}
|
||||
|
||||
const drawFrameBackground = (context: CanvasRenderingContext2D, width: number, height: number, frameType: number) => {
|
||||
// วาด background ตาม frame type
|
||||
switch (frameType) {
|
||||
case 0: // Classic - white
|
||||
context.fillStyle = '#ffffff'
|
||||
context.fillStyle = '#000000'
|
||||
break
|
||||
case 1: // Modern - gradient
|
||||
const gradient1 = context.createLinearGradient(0, 0, width, height)
|
||||
@@ -172,32 +227,35 @@ const drawFrameBackground = (context: CanvasRenderingContext2D, width: number, h
|
||||
context.fillStyle = '#f8f9fa'
|
||||
break
|
||||
default:
|
||||
context.fillStyle = '#ffffff'
|
||||
context.fillStyle = '#000000'
|
||||
}
|
||||
|
||||
context.fillRect(0, 0, width, height)
|
||||
}
|
||||
|
||||
const shareImage = async () => {
|
||||
if (navigator.share) {
|
||||
try {
|
||||
await navigator.share({
|
||||
title: 'รูปถ่ายจาก DekThai Photobooth',
|
||||
text: 'ดูรูปถ่ายสุดพิเศษที่ฉันสร้าง!',
|
||||
url: shareUrl.value
|
||||
})
|
||||
} catch (error) {
|
||||
console.log('Error sharing:', error)
|
||||
copyToClipboard()
|
||||
const drawHeaderFooterImage = async (context: CanvasRenderingContext2D, imagePath: string, x: number, y: number, width: number, height: number) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
const img = new Image()
|
||||
img.onload = () => {
|
||||
context.drawImage(img, x, y, width, height)
|
||||
resolve()
|
||||
}
|
||||
} else {
|
||||
copyToClipboard()
|
||||
}
|
||||
}
|
||||
|
||||
const copyToClipboard = () => {
|
||||
navigator.clipboard.writeText(shareUrl.value).then(() => {
|
||||
alert('คัดลอกลิงก์แล้ว! สามารถแชร์ให้เพื่อนได้')
|
||||
img.onerror = () => {
|
||||
console.warn(`Failed to load header/footer image: ${imagePath}, drawing placeholder`)
|
||||
// วาด placeholder color แทน
|
||||
if (imagePath.includes('header-1x4')) {
|
||||
context.fillStyle = '#007bff' // น้ำเงินสำหรับ 1x4 header
|
||||
} else if (imagePath.includes('footer-1x4')) {
|
||||
context.fillStyle = '#dc3545' // แดงสำหรับ 1x4 footer
|
||||
} else if (imagePath.includes('header-2x2')) {
|
||||
context.fillStyle = '#28a745' // เขียวสำหรับ 2x2 header
|
||||
} else if (imagePath.includes('footer-2x2')) {
|
||||
context.fillStyle = '#ffc107' // เหลืองสำหรับ 2x2 footer
|
||||
}
|
||||
context.fillRect(x, y, width, height)
|
||||
resolve()
|
||||
}
|
||||
img.src = imagePath
|
||||
})
|
||||
}
|
||||
|
||||
@@ -220,14 +278,14 @@ const startOver = () => {
|
||||
<section class="result-section">
|
||||
<div class="photo-result">
|
||||
<div class="photo-frame" :class="`frame-${frame}`">
|
||||
<div class="photo-grid" :style="getGridStyle">
|
||||
<div
|
||||
v-for="(photo, index) in photos"
|
||||
:key="index"
|
||||
class="photo-cell"
|
||||
>
|
||||
<img :src="photo" :alt="`Photo ${index + 1}`" />
|
||||
</div>
|
||||
<div v-if="isGeneratingImage && !cachedImageUrl" class="loading-indicator">
|
||||
<div class="spinner"></div>
|
||||
<p>กำลังสร้างภาพ...</p>
|
||||
</div>
|
||||
|
||||
<img v-else-if="cachedImageUrl" :src="cachedImageUrl" alt="Combined Photo" class="combined-image" />
|
||||
<div v-else-if="hasError" class="error-message">
|
||||
<p>ไม่สามารถสร้างภาพได้</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -239,22 +297,6 @@ const startOver = () => {
|
||||
<span class="button-icon">📥</span>
|
||||
ดาวน์โหลด
|
||||
</button>
|
||||
|
||||
<button @click="shareImage" class="share-button">
|
||||
<span class="button-icon">📤</span>
|
||||
แชร์
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="qr-section">
|
||||
<h3>สแกน QR Code เพื่อดูรูป</h3>
|
||||
<div class="qr-container">
|
||||
<img :src="qrCodeUrl" alt="QR Code" class="qr-code" />
|
||||
<p class="qr-text">{{ shareUrl }}</p>
|
||||
<button @click="copyToClipboard" class="copy-link-button">
|
||||
คัดลอกลิงก์
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -328,6 +370,46 @@ const startOver = () => {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.combined-image {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loading-indicator {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #007bff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.error-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
color: #dc3545;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.actions-section {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
@@ -338,7 +420,7 @@ const startOver = () => {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.download-button, .share-button {
|
||||
.download-button {
|
||||
background: #007bff;
|
||||
color: white;
|
||||
border: none;
|
||||
@@ -354,77 +436,16 @@ const startOver = () => {
|
||||
box-shadow: 0 4px 15px rgba(0,123,255,0.3);
|
||||
}
|
||||
|
||||
.share-button {
|
||||
background: #28a745;
|
||||
box-shadow: 0 4px 15px rgba(40,167,69,0.3);
|
||||
}
|
||||
|
||||
.download-button:hover {
|
||||
background: #0056b3;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(0,123,255,0.4);
|
||||
}
|
||||
|
||||
.share-button:hover {
|
||||
background: #218838;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(40,167,69,0.4);
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.qr-section {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.qr-section h3 {
|
||||
color: #333;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.qr-container {
|
||||
background: white;
|
||||
padding: 2rem;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
max-width: 300px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.qr-code {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.qr-text {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
word-break: break-all;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.5rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.copy-link-button {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.copy-link-button:hover {
|
||||
background: #5a6268;
|
||||
}
|
||||
|
||||
.footer-section {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
@@ -449,10 +470,6 @@ const startOver = () => {
|
||||
}
|
||||
|
||||
/* Frame styles */
|
||||
.frame-0 {
|
||||
/* Classic frame - default white */
|
||||
}
|
||||
|
||||
.frame-1 {
|
||||
/* Modern frame */
|
||||
background: linear-gradient(45deg, #667eea, #764ba2);
|
||||
|
||||
@@ -81,9 +81,9 @@ const captureSinglePhoto = () => {
|
||||
cropY = (videoHeight - cropHeight) / 2
|
||||
}
|
||||
|
||||
// ตั้งค่าขนาด canvas เป็น 3:4 (720x960)
|
||||
const finalWidth = 720
|
||||
const finalHeight = 960
|
||||
// ตั้งค่าขนาด canvas เป็น 3:4 (360x480) - ลดขนาดเพื่อป้องกัน localStorage quota exceeded
|
||||
const finalWidth = 360
|
||||
const finalHeight = 480
|
||||
|
||||
canvas.width = finalWidth
|
||||
canvas.height = finalHeight
|
||||
@@ -95,8 +95,8 @@ const captureSinglePhoto = () => {
|
||||
0, 0, finalWidth, finalHeight // ตำแหน่งและขนาดใน canvas
|
||||
)
|
||||
|
||||
// แปลงเป็น base64
|
||||
const photoDataUrl = canvas.toDataURL('image/jpeg', 0.9)
|
||||
// แปลงเป็น base64 ด้วย quality 0.7 เพื่อลดขนาด
|
||||
const photoDataUrl = canvas.toDataURL('image/jpeg', 0.7)
|
||||
photos.value.push(photoDataUrl)
|
||||
currentPhotoIndex.value++
|
||||
|
||||
@@ -145,10 +145,30 @@ const startAutoCapture = () => {
|
||||
}
|
||||
|
||||
const proceedToNext = () => {
|
||||
// เก็บรูปภาพไว้ใน localStorage
|
||||
localStorage.setItem('photobooth-photos', JSON.stringify(photos.value))
|
||||
stopCamera()
|
||||
router.push('/printstep')
|
||||
try {
|
||||
// เคลียร์ cached images ที่เก่าออกก่อนเพื่อให้มีพื้นที่
|
||||
const keysToRemove = []
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i)
|
||||
if (key && key.startsWith('photobooth-1x4-') || key.startsWith('photobooth-2x2-')) {
|
||||
keysToRemove.push(key)
|
||||
}
|
||||
}
|
||||
keysToRemove.forEach(key => localStorage.removeItem(key))
|
||||
|
||||
// เก็บรูปภาพไว้ใน localStorage
|
||||
localStorage.setItem('photobooth-photos', JSON.stringify(photos.value))
|
||||
stopCamera()
|
||||
router.push('/printstep')
|
||||
} catch (error) {
|
||||
if (error instanceof DOMException && error.name === 'QuotaExceededError') {
|
||||
alert('รูปภาพมีขนาดใหญ่เกินไป กรุณาลองใหม่อีกครั้งหรือใช้รูปภาพขนาดเล็กลง')
|
||||
console.error('localStorage quota exceeded:', error)
|
||||
} else {
|
||||
console.error('Error saving photos:', error)
|
||||
alert('เกิดข้อผิดพลาดในการบันทึกภาพ')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
|
||||
@@ -41,9 +41,9 @@ const cropImageTo34 = (imageSrc: string): Promise<string> => {
|
||||
cropY = (imgHeight - cropHeight) / 2
|
||||
}
|
||||
|
||||
// ตั้งค่าขนาด canvas เป็น 3:4 (720x960)
|
||||
const finalWidth = 720
|
||||
const finalHeight = 960
|
||||
// ตั้งค่าขนาด canvas เป็น 3:4 (360x480) - ลดขนาดเพื่อป้องกัน localStorage quota exceeded
|
||||
const finalWidth = 360
|
||||
const finalHeight = 480
|
||||
|
||||
canvas.width = finalWidth
|
||||
canvas.height = finalHeight
|
||||
@@ -55,8 +55,8 @@ const cropImageTo34 = (imageSrc: string): Promise<string> => {
|
||||
0, 0, finalWidth, finalHeight // ตำแหน่งและขนาดใน canvas
|
||||
)
|
||||
|
||||
// แปลงเป็น base64
|
||||
const croppedDataUrl = canvas.toDataURL('image/jpeg', 0.9)
|
||||
// แปลงเป็น base64 ด้วย quality 0.7 เพื่อลดขนาด
|
||||
const croppedDataUrl = canvas.toDataURL('image/jpeg', 0.7)
|
||||
resolve(croppedDataUrl)
|
||||
}
|
||||
img.src = imageSrc
|
||||
@@ -103,9 +103,29 @@ const proceedToNext = () => {
|
||||
return
|
||||
}
|
||||
|
||||
// เก็บรูปภาพไว้ใน localStorage
|
||||
localStorage.setItem('photobooth-photos', JSON.stringify(uploadedPhotos.value))
|
||||
router.push('/printstep')
|
||||
try {
|
||||
// เคลียร์ cached images ที่เก่าออกก่อนเพื่อให้มีพื้นที่
|
||||
const keysToRemove = []
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i)
|
||||
if (key && (key.startsWith('photobooth-1x4-') || key.startsWith('photobooth-2x2-'))) {
|
||||
keysToRemove.push(key)
|
||||
}
|
||||
}
|
||||
keysToRemove.forEach(key => localStorage.removeItem(key))
|
||||
|
||||
// เก็บรูปภาพไว้ใน localStorage
|
||||
localStorage.setItem('photobooth-photos', JSON.stringify(uploadedPhotos.value))
|
||||
router.push('/printstep')
|
||||
} catch (error) {
|
||||
if (error instanceof DOMException && error.name === 'QuotaExceededError') {
|
||||
alert('รูปภาพมีขนาดใหญ่เกินไป กรุณาเลือกภาพขนาดเล็กลงหรือจำนวนน้อยลง')
|
||||
console.error('localStorage quota exceeded:', error)
|
||||
} else {
|
||||
console.error('Error saving photos:', error)
|
||||
alert('เกิดข้อผิดพลาดในการบันทึกภาพ')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
|
||||