phase 6
This commit is contained in:
@@ -16,8 +16,10 @@ import { usePipelineStore } from './stores/pipeline'
|
||||
import { useSSEConnection } from './composables/useSSEConnection'
|
||||
import { useCheckpointLoader } from './composables/useCheckpointLoader'
|
||||
import { useEditorState } from './composables/useEditorState'
|
||||
import { useHashRouter } from './composables/useHashRouter'
|
||||
|
||||
const pipeline = usePipelineStore()
|
||||
useHashRouter()
|
||||
const logPanel = ref<{ clear: () => void } | null>(null)
|
||||
|
||||
// SSE connection + pipeline status
|
||||
|
||||
70
ui/detection-app/src/composables/useHashRouter.ts
Normal file
70
ui/detection-app/src/composables/useHashRouter.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { watch, onUnmounted } from 'vue'
|
||||
import { usePipelineStore } from '../stores/pipeline'
|
||||
|
||||
/**
|
||||
* Bidirectional sync between URL hash and pipeline store layout state.
|
||||
*
|
||||
* Hash format:
|
||||
* #/ → normal dashboard
|
||||
* #/editor/<stage> → bbox/region editor for that stage
|
||||
* #/config/<stage> → stage config editor
|
||||
* #/source → source selector
|
||||
*
|
||||
* Call once in App.vue. Handles popstate (back/forward) and
|
||||
* updates the hash when store state changes.
|
||||
*/
|
||||
export function useHashRouter() {
|
||||
const pipeline = usePipelineStore()
|
||||
|
||||
function parseHash(hash: string) {
|
||||
const path = hash.replace(/^#\/?/, '')
|
||||
if (!path || path === 'dashboard') {
|
||||
return { mode: 'normal', stage: null as string | null }
|
||||
}
|
||||
if (path === 'source') {
|
||||
return { mode: 'source_selector', stage: null as string | null }
|
||||
}
|
||||
const editorMatch = path.match(/^editor\/(.+)$/)
|
||||
if (editorMatch) {
|
||||
return { mode: 'bbox_editor', stage: editorMatch[1] }
|
||||
}
|
||||
const configMatch = path.match(/^config\/(.+)$/)
|
||||
if (configMatch) {
|
||||
return { mode: 'stage_editor', stage: configMatch[1] }
|
||||
}
|
||||
return { mode: 'normal', stage: null as string | null }
|
||||
}
|
||||
|
||||
function applyHash() {
|
||||
const { mode, stage } = parseHash(window.location.hash)
|
||||
pipeline.layoutMode = mode
|
||||
pipeline.editorStage = stage
|
||||
}
|
||||
|
||||
function updateHash() {
|
||||
let hash = '#/'
|
||||
if (pipeline.layoutMode === 'bbox_editor' && pipeline.editorStage) {
|
||||
hash = `#/editor/${pipeline.editorStage}`
|
||||
} else if (pipeline.layoutMode === 'stage_editor' && pipeline.editorStage) {
|
||||
hash = `#/config/${pipeline.editorStage}`
|
||||
} else if (pipeline.layoutMode === 'source_selector') {
|
||||
hash = '#/source'
|
||||
}
|
||||
if (window.location.hash !== hash) {
|
||||
window.history.pushState(null, '', hash)
|
||||
}
|
||||
}
|
||||
|
||||
// Sync hash → state on load
|
||||
applyHash()
|
||||
|
||||
// Sync hash → state on back/forward
|
||||
window.addEventListener('popstate', applyHash)
|
||||
onUnmounted(() => window.removeEventListener('popstate', applyHash))
|
||||
|
||||
// Sync state → hash when layout changes
|
||||
watch(
|
||||
() => [pipeline.layoutMode, pipeline.editorStage],
|
||||
updateHash,
|
||||
)
|
||||
}
|
||||
@@ -1,17 +1,12 @@
|
||||
/**
|
||||
* Pipeline store — run state, transport controls, checkpoint status.
|
||||
*
|
||||
* Layout is driven by URL hash:
|
||||
* #/ → normal dashboard
|
||||
* #/editor/<stage> → bbox/region editor for that stage
|
||||
* #/config/<stage> → stage config editor
|
||||
* #/source → source selector
|
||||
*
|
||||
* State shape defined in types/store-state.ts.
|
||||
* Hash routing is handled by useHashRouter composable, not here.
|
||||
*/
|
||||
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { ref, computed } from 'vue'
|
||||
import type { NodeState } from '../types/store-state'
|
||||
import type { CheckpointInfo } from '../types/sse-contract'
|
||||
|
||||
@@ -26,7 +21,6 @@ export const usePipelineStore = defineStore('pipeline', () => {
|
||||
const checkpoints = ref<CheckpointInfo[]>([])
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
// Layout mode — synced with URL hash
|
||||
const layoutMode = ref<string>('normal')
|
||||
const editorStage = ref<string | null>(null)
|
||||
|
||||
@@ -35,56 +29,6 @@ export const usePipelineStore = defineStore('pipeline', () => {
|
||||
const canReplay = computed(() => checkpoints.value.length > 0)
|
||||
const isEditing = computed(() => layoutMode.value !== 'normal')
|
||||
|
||||
// --- Hash routing ---
|
||||
|
||||
function parseHash(hash: string) {
|
||||
const path = hash.replace(/^#\/?/, '')
|
||||
if (!path || path === 'dashboard') {
|
||||
return { mode: 'normal', stage: null }
|
||||
}
|
||||
if (path === 'source') {
|
||||
return { mode: 'source_selector', stage: null }
|
||||
}
|
||||
const editorMatch = path.match(/^editor\/(.+)$/)
|
||||
if (editorMatch) {
|
||||
return { mode: 'bbox_editor', stage: editorMatch[1] }
|
||||
}
|
||||
const configMatch = path.match(/^config\/(.+)$/)
|
||||
if (configMatch) {
|
||||
return { mode: 'stage_editor', stage: configMatch[1] }
|
||||
}
|
||||
return { mode: 'normal', stage: null }
|
||||
}
|
||||
|
||||
function applyHash() {
|
||||
const { mode, stage } = parseHash(window.location.hash)
|
||||
layoutMode.value = mode
|
||||
editorStage.value = stage
|
||||
}
|
||||
|
||||
function updateHash() {
|
||||
let hash = '#/'
|
||||
if (layoutMode.value === 'bbox_editor' && editorStage.value) {
|
||||
hash = `#/editor/${editorStage.value}`
|
||||
} else if (layoutMode.value === 'stage_editor' && editorStage.value) {
|
||||
hash = `#/config/${editorStage.value}`
|
||||
} else if (layoutMode.value === 'source_selector') {
|
||||
hash = '#/source'
|
||||
}
|
||||
if (window.location.hash !== hash) {
|
||||
window.history.pushState(null, '', hash)
|
||||
}
|
||||
}
|
||||
|
||||
// Sync hash → state on load and popstate (back/forward)
|
||||
applyHash()
|
||||
window.addEventListener('popstate', applyHash)
|
||||
|
||||
// Sync state → hash when layout changes
|
||||
watch([layoutMode, editorStage], updateHash)
|
||||
|
||||
// --- Actions ---
|
||||
|
||||
function setJob(id: string) {
|
||||
jobId.value = id
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user