feat: Implement initial e-learning platform frontend structure including dashboard, course management, authentication, and common UI components.
This commit is contained in:
parent
aceeb80d9a
commit
ad11c6b7c5
44 changed files with 720 additions and 578 deletions
|
|
@ -1,22 +1,27 @@
|
|||
/**
|
||||
* @composable useMediaPrefs
|
||||
* @description จัดการตั้งค่าระดับเสียงและสถานะปิดเสียง (Mute) ของวิดีโอ/สื่อ
|
||||
* โดยจะเก็บค่าแยกตามบัญชีผู้ใช้งาน และสั่งประยุกต์ใช้กับ <video> ให้อัตโนมัติ
|
||||
*/
|
||||
export const useMediaPrefs = () => {
|
||||
// 1. Global State
|
||||
// ใช้ useState เพื่อแชร์ค่าเดียวกันทั่วทั้ง App (เช่น เปลี่ยนหน้าแล้วเสียงยังเท่าเดิม)
|
||||
// 1. สถานะส่วนกลาง (Global State)
|
||||
// ใช้ useState เพื่อแชร์ค่าเดียวกันทั่วหน้าเว็บ (เช่น เปลี่ยนหน้าแล้วระดับเสียงยังคงที่)
|
||||
const volume = useState<number>('media_prefs_volume', () => 1)
|
||||
const muted = useState<boolean>('media_prefs_muted', () => false)
|
||||
|
||||
const { user } = useAuth()
|
||||
|
||||
// 2. Storage Key Helper (User Specific)
|
||||
// 2. ฟังก์ชันช่วยสร้าง Key สำหรับ Storage (เก็บแยกตาม User)
|
||||
const getStorageKey = () => {
|
||||
const userId = user.value?.id || 'guest'
|
||||
return `media:prefs:v1:${userId}`
|
||||
}
|
||||
|
||||
// 3. Save Logic (Throttled)
|
||||
// 3. ระบบบันทึกการตั้งค่าลงเบราว์เซอร์ (Throttled เพื่อไม่ให้บันทึกถี่เกินไป)
|
||||
let saveTimeout: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const save = () => {
|
||||
if (import.meta.server) return
|
||||
if (import.meta.server) return // เลี่ยงไม่ได้ต้องทำงานบนฝั่ง Client เท่านั้น
|
||||
|
||||
if (saveTimeout) clearTimeout(saveTimeout)
|
||||
saveTimeout = setTimeout(() => {
|
||||
|
|
@ -29,12 +34,12 @@ export const useMediaPrefs = () => {
|
|||
}
|
||||
localStorage.setItem(key, JSON.stringify(data))
|
||||
} catch (e) {
|
||||
console.error('Failed to save media prefs', e)
|
||||
console.error('ไม่สามารถบันทึกการตั้งค่าสื่อได้', e)
|
||||
}
|
||||
}, 500) // Throttle 500ms
|
||||
}, 500) // หน่วงเวลา 500ms
|
||||
}
|
||||
|
||||
// 4. Load Logic
|
||||
// 4. ระบบโหลดการตั้งค่าเก่าขึ้นมา (Load Logic)
|
||||
const load = () => {
|
||||
if (import.meta.server) return
|
||||
|
||||
|
|
@ -51,20 +56,20 @@ export const useMediaPrefs = () => {
|
|||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load media prefs', e)
|
||||
console.error('ไม่สามารถโหลดการตั้งค่าสื่อได้', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Setters (With Logic)
|
||||
// 5. ฟังก์ชันสำหรับอัปเดตและสั่งบันทึกการตั้งค่า (Setters)
|
||||
const setVolume = (val: number) => {
|
||||
const clamped = Math.max(0, Math.min(1, val))
|
||||
volume.value = clamped
|
||||
|
||||
// Auto unmute if volume increased from 0
|
||||
// ยกเลิกปิดเสียงอัตโนมัติ ถ้าระดับเสียงเพิ่มขึ้นจาก 0
|
||||
if (clamped > 0 && muted.value) {
|
||||
muted.value = false
|
||||
}
|
||||
// Auto mute if volume set to 0
|
||||
// ปิดเสียงอัตโนมัติ ถ้าระดับเสียงกลายเป็น 0
|
||||
if (clamped === 0 && !muted.value) {
|
||||
muted.value = true
|
||||
}
|
||||
|
|
@ -75,7 +80,7 @@ export const useMediaPrefs = () => {
|
|||
const setMuted = (val: boolean) => {
|
||||
muted.value = val
|
||||
|
||||
// Logic: Unmuting should restore volume if it was 0
|
||||
// หากผู้ใช้กดยกเลิกการปิดเสียงขณะที่ระดับเสียงเคยเป็น 0 ควรตั้งค่าเริ่มต้นให้เป็น 1
|
||||
if (!val && volume.value === 0) {
|
||||
volume.value = 1
|
||||
}
|
||||
|
|
@ -83,15 +88,15 @@ export const useMediaPrefs = () => {
|
|||
save()
|
||||
}
|
||||
|
||||
// 6. Apply & Bind to Element (The Magic)
|
||||
// 6. ฟังก์ชันจับคู่ใช้กับการเล่นสื่อ (ตย. <video ref="videoEl"> -> applyTo(videoEl.value))
|
||||
const applyTo = (el: HTMLMediaElement | null | undefined) => {
|
||||
if (!el) return () => {}
|
||||
|
||||
// Initial Apply
|
||||
// ใส่ค่าตั้งต้นให้กับออบเจ็กต์สื่อ
|
||||
el.volume = volume.value
|
||||
el.muted = muted.value
|
||||
|
||||
// A. Watch State -> Update Element
|
||||
// A. สังเกตการเปลี่ยนแปลงจาก State -> เพื่อส่งไปอัปเดต Element สื่อ
|
||||
const stopVolWatch = watch(volume, (v) => {
|
||||
if (Math.abs(el.volume - v) > 0.01) el.volume = v
|
||||
})
|
||||
|
|
@ -99,9 +104,9 @@ export const useMediaPrefs = () => {
|
|||
if (el.muted !== m) el.muted = m
|
||||
})
|
||||
|
||||
// B. Listen Element -> Update State (e.g. Native Controls)
|
||||
// B. สังเกตการเปลี่ยนแปลงจาก Element (เช่น ผู้ใช้กดปุ่มเร่งเสียงในวิดีโอตรงๆ) -> เพื่อเอาค่ามาอัปเดต State
|
||||
const onVolumeChange = () => {
|
||||
// Update state only if diff allows (prevent loop)
|
||||
// อัปเดตเฉพาะเมื่อมีความแตกต่างเพื่อหลีกเลี่ยง Loop อนันต์
|
||||
if (Math.abs(el.volume - volume.value) > 0.01) {
|
||||
volume.value = el.volume
|
||||
save()
|
||||
|
|
@ -113,7 +118,7 @@ export const useMediaPrefs = () => {
|
|||
}
|
||||
el.addEventListener('volumechange', onVolumeChange)
|
||||
|
||||
// Cleanup function
|
||||
// ฟังก์ชันล้างค่าเพื่อเลิกติดตาม (Cleanup แบบส่งกลับ (Return))
|
||||
return () => {
|
||||
stopVolWatch()
|
||||
stopMutedWatch()
|
||||
|
|
@ -121,11 +126,11 @@ export const useMediaPrefs = () => {
|
|||
}
|
||||
}
|
||||
|
||||
// 7. Lifecycle & Sync
|
||||
// 7. จังหวะวงจรชีวิตตอนโหลดเสร็จและระบบ Sync
|
||||
if (import.meta.client) {
|
||||
onMounted(() => {
|
||||
load()
|
||||
// Cross-tab sync
|
||||
// ระบบ Sync กับแท็บหรือหน้าต่างเดียวกันหากถูกเปิดไว้
|
||||
window.addEventListener('storage', (e) => {
|
||||
if (e.key === getStorageKey()) {
|
||||
load()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue