diff --git a/resources/js/components/projects/ProjectPageHeader.vue b/resources/js/components/projects/ProjectPageHeader.vue
new file mode 100644
index 0000000..6e275dc
--- /dev/null
+++ b/resources/js/components/projects/ProjectPageHeader.vue
@@ -0,0 +1,34 @@
+
+
+
+
+
diff --git a/resources/js/components/projects/ProjectTabs.vue b/resources/js/components/projects/ProjectTabs.vue
index a5fd6b6..9308b9c 100644
--- a/resources/js/components/projects/ProjectTabs.vue
+++ b/resources/js/components/projects/ProjectTabs.vue
@@ -14,24 +14,6 @@ defineProps();
aria-label="Project sections"
class="border-outline-variant/70 flex min-w-max items-center gap-1 border-b"
>
-
-
- Board
-
-
-
();
/>
+
+
+ Board
+
+
+
import { Form, Head, Link } from '@inertiajs/vue3';
+import { Plus } from 'lucide-vue-next';
import ProjectBoardController from '@/actions/App/Http/Controllers/ProjectBoardController';
-import Heading from '@/components/Heading.vue';
+import ProjectPageHeader from '@/components/projects/ProjectPageHeader.vue';
import ProjectTabs from '@/components/projects/ProjectTabs.vue';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
@@ -72,34 +73,25 @@ const allowedTargetStatuses = (ticket: TicketModel): TicketStatusModel[] => {
-
-
-
- {{ project.key }}
-
-
-
-
-
-
-
-
+
+
import { Head, Link, router } from '@inertiajs/vue3';
import { CircleUserRound, GripVertical, Plus } from 'lucide-vue-next';
-import { onBeforeUnmount, ref } from 'vue';
+import { onBeforeUnmount, onMounted, ref } from 'vue';
import ProjectBoardController from '@/actions/App/Http/Controllers/ProjectBoardController';
+import ProjectPageHeader from '@/components/projects/ProjectPageHeader.vue';
import ProjectTabs from '@/components/projects/ProjectTabs.vue';
import UserAvatar from '@/components/ui/avatar/UserAvatar.vue';
-import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import AppLayout from '@/layouts/AppLayout.vue';
import { index as projectIndex } from '@/routes/projects';
@@ -53,6 +53,7 @@ const pointerX = ref(0);
const pointerY = ref(0);
let autoScrollFrame: number | undefined;
+let initialScrollFrame: number | undefined;
let longPressTimer: number | undefined;
let suppressClickTicketId: number | null = null;
@@ -96,6 +97,9 @@ const isActiveDropTarget = (
);
};
+const dropZoneRows = (ticket: TicketModel, boardColumnId: number): string =>
+ `repeat(${allowedStatusesForColumn(ticket, boardColumnId).length}, minmax(0, 1fr))`;
+
const clearLongPressTimer = (): void => {
if (longPressTimer !== undefined) {
window.clearTimeout(longPressTimer);
@@ -221,7 +225,11 @@ const handlePointerDown = (
startY: event.clientY,
};
- (event.currentTarget as HTMLElement).setPointerCapture(event.pointerId);
+ window.addEventListener('pointermove', handlePointerMove, {
+ passive: false,
+ });
+ window.addEventListener('pointerup', finishPointer);
+ window.addEventListener('pointercancel', cancelPointer);
if (event.pointerType === 'touch') {
longPressTimer = window.setTimeout(startDragging, 240);
@@ -268,6 +276,9 @@ const handlePointerMove = (event: PointerEvent): void => {
const resetDragState = (): void => {
clearLongPressTimer();
stopAutoScroll();
+ window.removeEventListener('pointermove', handlePointerMove);
+ window.removeEventListener('pointerup', finishPointer);
+ window.removeEventListener('pointercancel', cancelPointer);
pendingDragTicket.value = null;
draggedTicket.value = null;
dragPointer.value = null;
@@ -311,11 +322,6 @@ const finishPointer = (event: PointerEvent): void => {
return;
}
- const targetElement = event.currentTarget as HTMLElement;
- if (targetElement.hasPointerCapture(event.pointerId)) {
- targetElement.releasePointerCapture(event.pointerId);
- }
-
resetDragState();
if (drag !== null) {
@@ -347,7 +353,21 @@ const handleTicketClick = (event: MouseEvent, ticketId: number): void => {
event.stopPropagation();
};
+onMounted(() => {
+ initialScrollFrame = window.requestAnimationFrame(() => {
+ if (boardScroller.value !== null) {
+ boardScroller.value.scrollLeft = 0;
+ }
+
+ initialScrollFrame = undefined;
+ });
+});
+
onBeforeUnmount(() => {
+ if (initialScrollFrame !== undefined) {
+ window.cancelAnimationFrame(initialScrollFrame);
+ }
+
resetDragState();
});
@@ -357,23 +377,13 @@ onBeforeUnmount(() => {
-
+
Drop as {{ ticketStatus.name }}
-
-
{
? 'pointer-events-none animate-pulse opacity-55'
: '',
]"
- @pointerdown="
- handlePointerDown(
- $event,
- ticket,
- boardColumn,
- )
- "
- @pointermove="handlePointerMove"
- @pointerup="finishPointer"
- @pointercancel="cancelPointer"
>
{
+
+
diff --git a/resources/js/pages/projects/Settings.vue b/resources/js/pages/projects/Settings.vue
index 46e54e7..10bc04e 100644
--- a/resources/js/pages/projects/Settings.vue
+++ b/resources/js/pages/projects/Settings.vue
@@ -10,8 +10,8 @@ import {
Tags,
} from 'lucide-vue-next';
import { computed } from 'vue';
+import ProjectPageHeader from '@/components/projects/ProjectPageHeader.vue';
import ProjectTabs from '@/components/projects/ProjectTabs.vue';
-import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import AppLayout from '@/layouts/AppLayout.vue';
import { index as projectsIndex } from '@/routes/projects';
@@ -134,29 +134,18 @@ const sectionHref = (section: ProjectSettingsSection) =>
-
-
-
- {{ project.key }}
-
-
-
- {{ project.name }}
-
-
- Project configuration and access controls.
-
-
-
-
-
+
= Omit<
T,
'embedded' | 'project' | 'status'