diff --git a/app/components/feature/CpSessionDaySelector.vue b/app/components/feature/CpSessionDaySelector.vue new file mode 100644 index 0000000..4c1ce2e --- /dev/null +++ b/app/components/feature/CpSessionDaySelector.vue @@ -0,0 +1,34 @@ + + + diff --git a/app/components/feature/CpSessionItem.vue b/app/components/feature/CpSessionItem.vue new file mode 100644 index 0000000..5525986 --- /dev/null +++ b/app/components/feature/CpSessionItem.vue @@ -0,0 +1,33 @@ + + + diff --git a/app/components/feature/CpSessionTable.vue b/app/components/feature/CpSessionTable.vue new file mode 100644 index 0000000..cc1a7ee --- /dev/null +++ b/app/components/feature/CpSessionTable.vue @@ -0,0 +1,172 @@ + + + diff --git a/app/components/shared/CpBadge.vue b/app/components/shared/CpBadge.vue new file mode 100644 index 0000000..9abebbf --- /dev/null +++ b/app/components/shared/CpBadge.vue @@ -0,0 +1,12 @@ + + + diff --git a/app/components/shared/CpPopup.vue b/app/components/shared/CpPopup.vue index 53ff643..838b430 100644 --- a/app/components/shared/CpPopup.vue +++ b/app/components/shared/CpPopup.vue @@ -1,10 +1,13 @@ @@ -18,7 +21,7 @@ onClickOutside(target, close) class="cursor-pointer" :is-open="isOpen" name="trigger" - @click="toggle" + @click="onTriggerClick" /> diff --git a/app/composables/useDragScroll.ts b/app/composables/useDragScroll.ts new file mode 100644 index 0000000..3ee3ea8 --- /dev/null +++ b/app/composables/useDragScroll.ts @@ -0,0 +1,112 @@ +interface UseDragScrollOptions { + horizontal?: boolean + vertical?: boolean +} + +export function useDragScroll({ horizontal = true, vertical = true }: UseDragScrollOptions = {}) { + const containerRef = ref() + const isDragging = ref(false) + const suppressClick = ref(false) + + const dragState = reactive({ + pointerId: -1, + startX: 0, + startY: 0, + scrollLeft: 0, + scrollTop: 0, + }) + + function handlePointerDown(event: PointerEvent) { + if (event.button !== 0 || !containerRef.value) { + return + } + + dragState.pointerId = event.pointerId + dragState.startX = event.clientX + dragState.startY = event.clientY + dragState.scrollLeft = containerRef.value.scrollLeft + dragState.scrollTop = containerRef.value.scrollTop + isDragging.value = true + suppressClick.value = false + + window.addEventListener('pointermove', handlePointerMove) + window.addEventListener('pointerup', stopDragging) + window.addEventListener('pointercancel', stopDragging) + } + + function handlePointerMove(event: PointerEvent) { + if (!isDragging.value || !containerRef.value) { + return + } + + event.preventDefault() + + const deltaX = event.clientX - dragState.startX + const deltaY = event.clientY - dragState.startY + + const moved = (horizontal && Math.abs(deltaX) > 4) || (vertical && Math.abs(deltaY) > 4) + if (moved) { + suppressClick.value = true + } + + if (horizontal) { + containerRef.value.scrollLeft = dragState.scrollLeft - deltaX + } + if (vertical) { + containerRef.value.scrollTop = dragState.scrollTop - deltaY + } + } + + function stopDragging(event?: PointerEvent) { + if (event && event.pointerId !== dragState.pointerId) { + return + } + + dragState.pointerId = -1 + isDragging.value = false + + window.removeEventListener('pointermove', handlePointerMove) + window.removeEventListener('pointerup', stopDragging) + window.removeEventListener('pointercancel', stopDragging) + } + + function handleClickCapture(event: MouseEvent) { + if (!suppressClick.value) { + return + } + + event.preventDefault() + event.stopPropagation() + suppressClick.value = false + } + + function handleDragStart(event: DragEvent) { + event.preventDefault() + } + + watchEffect((onCleanup) => { + const el = containerRef.value + if (!el) { + return + } + + el.addEventListener('pointerdown', handlePointerDown) + el.addEventListener('click', handleClickCapture, { capture: true }) + el.addEventListener('dragstart', handleDragStart) + + onCleanup(() => { + el.removeEventListener('pointerdown', handlePointerDown) + el.removeEventListener('click', handleClickCapture, { capture: true }) + el.removeEventListener('dragstart', handleDragStart) + // Clean up window listeners in case drag was in progress + window.removeEventListener('pointermove', handlePointerMove) + window.removeEventListener('pointerup', stopDragging) + window.removeEventListener('pointercancel', stopDragging) + }) + }) + + return { + containerRef, + isDragging, + } +} diff --git a/app/composables/useToggle.ts b/app/composables/useToggle.ts deleted file mode 100644 index 0b5c57b..0000000 --- a/app/composables/useToggle.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ref } from 'vue' - -export default function useToggle(defaultStatus: boolean = false) { - const isOpen = ref(defaultStatus) - - function open() { - isOpen.value = true - } - - function close() { - isOpen.value = false - } - - function toggle() { - isOpen.value = !isOpen.value - } - - return { isOpen, open, close, toggle } -} diff --git a/app/pages/session/index.vue b/app/pages/session/index.vue new file mode 100644 index 0000000..07a14d5 --- /dev/null +++ b/app/pages/session/index.vue @@ -0,0 +1,45 @@ + + + + + + en: + noSession: 'Not announced yet, stay tuned.' + zh: + noSession: '尚未公布,敬請期待。' +