diff --git a/client-v3/e2e/tests/06-show-config-characters.spec.ts b/client-v3/e2e/tests/06-show-config-characters.spec.ts index e6e957bcf..c2207ffb9 100644 --- a/client-v3/e2e/tests/06-show-config-characters.spec.ts +++ b/client-v3/e2e/tests/06-show-config-characters.spec.ts @@ -83,6 +83,62 @@ test('deletes a character', async () => { }); }); +// ── Rows per page selector ───────────────────────────────────────────────── + +test('rows per page selector is hidden when row count does not exceed minimum', async () => { + // Only Hamlet exists (1 row) — controls hidden when totalRows ≤ 10 (smallest option) + await expect(page.locator('select#character-table-per-page')).not.toBeVisible(); +}); + +test('creates enough characters for pagination controls to appear', async () => { + for (const name of [ + 'Alice', + 'Bob', + 'Carol', + 'Dave', + 'Eve', + 'Frank', + 'Grace', + 'Henry', + 'Iris', + 'Jack', + ]) { + await page.getByRole('button', { name: 'New Character', exact: true }).click(); + await waitForModal(page, 'New Character'); + await page.fill('.modal.show input[type="text"]', name); + await confirmModal(page); + await waitForModalClosed(page); + } + // 11 characters total (Hamlet + 10) — controls should now appear + await expect(page.locator('select#character-table-per-page')).toBeVisible(); +}); + +test('rows per page selector has correct default', async () => { + await expect(page.locator('label[for="character-table-per-page"]')).toBeVisible(); + await expect(page.locator('select#character-table-per-page')).toHaveValue('15'); +}); + +test('rows per page selector has all expected options', async () => { + const options = await page + .locator('select#character-table-per-page') + .locator('option') + .allTextContents(); + expect(options).toEqual(expect.arrayContaining(['10', '15', '25', '50', 'All'])); +}); + +test('pagination nav shows when rows exceed per-page and hides on All', async () => { + // 11 rows, default perPage=15: nav hidden (11 ≤ 15) + await expect(page.locator('button[aria-controls="character-table"]').first()).not.toBeVisible(); + // Change to 10 rows/page: nav appears (11 > 10) + await page.locator('select#character-table-per-page').selectOption('10'); + await expect(page.locator('button[aria-controls="character-table"]').first()).toBeVisible(); + // Select All: nav disappears + await page.locator('select#character-table-per-page').selectOption('0'); + await expect(page.locator('button[aria-controls="character-table"]').first()).not.toBeVisible(); + // Reset to default + await page.locator('select#character-table-per-page').selectOption('15'); +}); + // ── Character Merge ──────────────────────────────────────────────────────── test('Merge button is visible for each character row', async () => { diff --git a/client-v3/e2e/tests/10-show-config-script.spec.ts b/client-v3/e2e/tests/10-show-config-script.spec.ts index b32cf7608..e2b92d04f 100644 --- a/client-v3/e2e/tests/10-show-config-script.spec.ts +++ b/client-v3/e2e/tests/10-show-config-script.spec.ts @@ -120,6 +120,73 @@ test('saves a dialogue line to the script', async () => { ).toBeVisible({ timeout: 10_000 }); }); +// ── Cut mode ────────────────────────────────────────────────────────────── + +test('enters cut mode', async () => { + await page.goto(`${UI_BASE}/show-config/script`); + await waitForAppReady(page); + await page.click('button:has-text("Cuts")'); + await expect(page.locator('button:has-text("Stop Editing")')).toBeVisible({ timeout: 10_000 }); +}); + +test('dialogue line becomes clickable in cut mode', async () => { + await expect( + page.locator('a.viewable-line-cut').filter({ hasText: 'To be or not to be' }) + ).toBeVisible({ timeout: 5_000 }); +}); + +test('clicking a line toggles cut styling', async () => { + await page.locator('a.viewable-line-cut').filter({ hasText: 'To be or not to be' }).click(); + await expect( + page.locator('.cut-line-part').filter({ hasText: 'To be or not to be' }) + ).toBeVisible({ timeout: 3_000 }); +}); + +test('saves cuts', async () => { + await page.getByRole('button', { name: 'Save', exact: true }).click(); + await expect(page.locator('.v-toast__text').filter({ hasText: /saved/i })).toBeVisible({ + timeout: 5_000, + }); +}); + +test('stops cut mode without blank-page flash', async () => { + // In cut mode the line is an anchor (.viewable-line-cut); verify it exists before stopping + await expect( + page.locator('.viewable-line-cut').filter({ hasText: 'To be or not to be' }) + ).toBeVisible(); + + await page.click('button:has-text("Stop Editing")'); + + // Edit button reappears + await expect(page.getByRole('button', { name: 'Edit', exact: true })).toBeVisible({ + timeout: 10_000, + }); + // The saved line is still visible (no reload / blank flash) + await expect( + page.locator('.viewable-line').filter({ hasText: 'To be or not to be' }) + ).toBeVisible(); + // No edit controls are shown after stopping + await expect(page.locator('.script-edit-controls')).not.toBeVisible(); +}); + +test('cut styling persists after re-entering cut mode', async () => { + await page.click('button:has-text("Cuts")'); + await expect(page.locator('button:has-text("Stop Editing")')).toBeVisible({ timeout: 10_000 }); + await expect( + page.locator('.cut-line-part').filter({ hasText: 'To be or not to be' }) + ).toBeVisible({ timeout: 5_000 }); + // Clean up: un-cut the line so later tests start from a known state + await page.locator('a.viewable-line-cut').filter({ hasText: 'To be or not to be' }).click(); + await page.getByRole('button', { name: 'Save', exact: true }).click(); + await expect(page.locator('.v-toast__text').filter({ hasText: /saved/i })).toBeVisible({ + timeout: 5_000, + }); + await page.click('button:has-text("Stop Editing")'); + await expect(page.getByRole('button', { name: 'Edit', exact: true })).toBeVisible({ + timeout: 10_000, + }); +}); + // ── Stage Direction Styles ──────────────────────────────────────────────── test('switches to Stage Direction Styles sub-tab', async () => { diff --git a/client-v3/package-lock.json b/client-v3/package-lock.json index 08f2ce7b0..69ae23544 100644 --- a/client-v3/package-lock.json +++ b/client-v3/package-lock.json @@ -1,12 +1,12 @@ { "name": "client-v3", - "version": "0.31.1", + "version": "0.31.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "client-v3", - "version": "0.31.1", + "version": "0.31.2", "dependencies": { "@vuelidate/core": "^2.0.3", "@vuelidate/validators": "^2.0.4", diff --git a/client-v3/package.json b/client-v3/package.json index c231186b5..fcda5dfa1 100644 --- a/client-v3/package.json +++ b/client-v3/package.json @@ -1,6 +1,6 @@ { "name": "client-v3", - "version": "0.31.1", + "version": "0.31.2", "description": "DigiScript front end (Vue 3)", "author": "DreamTeamProd", "private": true, diff --git a/client-v3/src/components/config/ConfigShows.vue b/client-v3/src/components/config/ConfigShows.vue index 071be64ec..62ca7a9d9 100644 --- a/client-v3/src/components/config/ConfigShows.vue +++ b/client-v3/src/components/config/ConfigShows.vue @@ -7,7 +7,7 @@ id="shows-table" :items="availableShows" :fields="showFields" - :per-page="rowsPerPage" + :per-page="perPage" :current-page="currentPage" > - @@ -129,6 +127,7 @@ import { makeURL } from '@/js/utils'; import { useSystemStore } from '@/stores/system'; import { useShowStore } from '@/stores/show'; import { useConfirm } from '@/composables/useConfirm'; +import { usePagination } from '@/composables/usePagination'; import { toast } from '@/js/toast'; import type { Show } from '@/types/api/show'; @@ -144,8 +143,7 @@ const isSubmittingLoad = ref(false); const isSubmittingShow = ref(false); const isDeleting = ref(false); const deletingId = ref(null); -const currentPage = ref(1); -const rowsPerPage = 15; +const { perPage, currentPage } = usePagination(); const showFields = [ { key: 'id', label: 'ID' }, diff --git a/client-v3/src/components/config/ConfigSystem.vue b/client-v3/src/components/config/ConfigSystem.vue index ae5e74134..ecdb107a7 100644 --- a/client-v3/src/components/config/ConfigSystem.vue +++ b/client-v3/src/components/config/ConfigSystem.vue @@ -73,12 +73,11 @@ :current-page="currentPage" small /> - @@ -92,6 +91,7 @@ import { ref, computed, onMounted, onBeforeUnmount } from 'vue'; import { storeToRefs } from 'pinia'; import { BModal } from 'bootstrap-vue-next'; import { useSystemStore } from '@/stores/system'; +import { usePagination } from '@/composables/usePagination'; import { toast } from '@/js/toast'; const systemStore = useSystemStore(); @@ -99,8 +99,7 @@ const { connectedSessions, versionStatus, serverInfo } = storeToRefs(systemStore const loading = ref(true); const isCheckingVersion = ref(false); -const currentPage = ref(1); -const perPage = 5; +const { perPage, currentPage } = usePagination(); const clientsModal = ref>(); const clientFields = [ diff --git a/client-v3/src/components/shared/PaginationControls.vue b/client-v3/src/components/shared/PaginationControls.vue new file mode 100644 index 000000000..1f37e7ca6 --- /dev/null +++ b/client-v3/src/components/shared/PaginationControls.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/client-v3/src/components/show/config/acts_and_scenes/ConfigActs.vue b/client-v3/src/components/show/config/acts_and_scenes/ConfigActs.vue index 850df7cba..9bb104c4c 100644 --- a/client-v3/src/components/show/config/acts_and_scenes/ConfigActs.vue +++ b/client-v3/src/components/show/config/acts_and_scenes/ConfigActs.vue @@ -4,7 +4,7 @@ id="acts-table" :items="actTableItems" :fields="actFields" - :per-page="rowsPerPage" + :per-page="perPage" :current-page="currentPage" show-empty > @@ -46,13 +46,11 @@ - @@ -49,13 +49,11 @@ - @@ -200,6 +198,7 @@ import log from 'loglevel'; import { useSystemStore } from '@/stores/system'; import { useShowStore } from '@/stores/show'; import { useConfirm } from '@/composables/useConfirm'; +import { usePagination } from '@/composables/usePagination'; import type { Scene } from '@/types/api/show'; const systemStore = useSystemStore(); @@ -207,8 +206,7 @@ const showStore = useShowStore(); const { confirm } = useConfirm(); const loading = ref(true); -const rowsPerPage = 15; -const currentPage = ref(1); +const { perPage, currentPage } = usePagination(); const submittingNewScene = ref(false); const submittingEditScene = ref(false); const submittingFirstScene = ref(false); diff --git a/client-v3/src/components/show/config/characters/CharacterGroups.vue b/client-v3/src/components/show/config/characters/CharacterGroups.vue index 848d12611..68a1f1956 100644 --- a/client-v3/src/components/show/config/characters/CharacterGroups.vue +++ b/client-v3/src/components/show/config/characters/CharacterGroups.vue @@ -4,7 +4,7 @@ id="character-group-table" :items="showStore.characterGroupList" :fields="characterGroupFields" - :per-page="rowsPerPage" + :per-page="perPage" :current-page="currentPage" show-empty > @@ -48,13 +48,11 @@ - @@ -33,11 +33,10 @@ - @@ -94,6 +93,7 @@ import { required, helpers } from '@vuelidate/validators'; import { useShowStore } from '@/stores/show'; import { useSystemStore } from '@/stores/system'; import { useConfirm } from '@/composables/useConfirm'; +import { usePagination } from '@/composables/usePagination'; import { useFormValidation } from '@/composables/useFormValidation'; import type { Microphone } from '@/types/api/microphones'; import log from 'loglevel'; @@ -103,8 +103,7 @@ const systemStore = useSystemStore(); const { confirm } = useConfirm(); const { validationState } = useFormValidation(); -const rowsPerPage = 15; -const currentPage = ref(1); +const { perPage, currentPage } = usePagination(); const isSubmitting = ref(false); const newModal = ref>(); diff --git a/client-v3/src/components/show/config/script/ScriptEditor.vue b/client-v3/src/components/show/config/script/ScriptEditor.vue index 44d98c963..ce862e05c 100644 --- a/client-v3/src/components/show/config/script/ScriptEditor.vue +++ b/client-v3/src/components/show/config/script/ScriptEditor.vue @@ -257,7 +257,7 @@ const isEditor = computed( wsStore.internalUUID !== null && wsStore.internalUUID === scriptConfigStore.editStatus.currentEditor ); -const canEdit = computed(() => isEditor.value && !scriptConfigStore.cutMode); +const canEdit = computed(() => isEditor.value); const saveProgressVariant = computed(() => { if (savingInProgress.value) return 'primary'; @@ -336,13 +336,20 @@ async function stopEditing(): Promise { ); if (!ok) return; } + const wasCutMode = scriptConfigStore.cutMode; editingLines.value.clear(); exitBulkEditMode(); - scriptConfigStore.emptyScript(); linePartCuts.value = [...scriptStore.cuts]; - sendObj({ OP: 'STOP_SCRIPT_EDIT', DATA: {} }); scriptConfigStore.setCutMode(false); - await loadPage(currentPage.value); + scriptConfigStore.setEditStatus({ + canRequestEdit: scriptConfigStore.editStatus.canRequestEdit, + currentEditor: null, + }); + sendObj({ OP: 'STOP_SCRIPT_EDIT', DATA: {} }); + if (!wasCutMode) { + scriptConfigStore.emptyScript(); + await loadPage(currentPage.value); + } } async function loadPage(page: number): Promise { diff --git a/client-v3/src/components/show/config/script/ScriptLineViewer.vue b/client-v3/src/components/show/config/script/ScriptLineViewer.vue index 5c3c6c0e1..6bf13b507 100644 --- a/client-v3/src/components/show/config/script/ScriptLineViewer.vue +++ b/client-v3/src/components/show/config/script/ScriptLineViewer.vue @@ -1,5 +1,6 @@ - @@ -147,6 +146,7 @@ import log from 'loglevel'; import { useScriptStore } from '@/stores/script'; import { useSystemStore } from '@/stores/system'; import { useConfirm } from '@/composables/useConfirm'; +import { usePagination } from '@/composables/usePagination'; import { useFormValidation } from '@/composables/useFormValidation'; import type { StageDirectionStyle } from '@/types/api/script'; import { toast } from '@/js/toast'; @@ -156,8 +156,7 @@ const systemStore = useSystemStore(); const { confirm } = useConfirm(); const { validationState } = useFormValidation(); -const rowsPerPage = 15; -const currentPage = ref(1); +const { perPage, currentPage } = usePagination(); const isSubmittingNew = ref(false); const isSubmittingEdit = ref(false); const isDeleting = ref(false); diff --git a/client-v3/src/components/show/config/sessions/SessionTagList.vue b/client-v3/src/components/show/config/sessions/SessionTagList.vue index 40a43ad20..a0e6b34b6 100644 --- a/client-v3/src/components/show/config/sessions/SessionTagList.vue +++ b/client-v3/src/components/show/config/sessions/SessionTagList.vue @@ -4,7 +4,7 @@ id="session-tags-table" :items="showStore.sessionTags" :fields="tagFields" - :per-page="rowsPerPage" + :per-page="perPage" :current-page="currentPage" show-empty > @@ -39,13 +39,11 @@ - @@ -29,11 +29,10 @@ - @@ -102,6 +101,7 @@ import { required } from '@vuelidate/validators'; import { useStageStore } from '@/stores/stage'; import { useSystemStore } from '@/stores/system'; import { useConfirm } from '@/composables/useConfirm'; +import { usePagination } from '@/composables/usePagination'; import { useFormValidation } from '@/composables/useFormValidation'; import type { Crew } from '@/types/api/stage'; import log from 'loglevel'; @@ -111,8 +111,7 @@ const systemStore = useSystemStore(); const { confirm } = useConfirm(); const { validationState } = useFormValidation(); -const rowsPerPage = 15; -const currentPage = ref(1); +const { perPage, currentPage } = usePagination(); const isSubmitting = ref(false); const newModal = ref>(); diff --git a/client-v3/src/components/show/config/stage/PropsList.vue b/client-v3/src/components/show/config/stage/PropsList.vue index 3eec6db21..7e65e62a3 100644 --- a/client-v3/src/components/show/config/stage/PropsList.vue +++ b/client-v3/src/components/show/config/stage/PropsList.vue @@ -6,7 +6,7 @@ @@ -38,11 +38,10 @@ - @@ -50,7 +49,7 @@ @@ -81,11 +80,10 @@ - @@ -212,6 +210,7 @@ import { required, helpers } from '@vuelidate/validators'; import { useStageStore } from '@/stores/stage'; import { useSystemStore } from '@/stores/system'; import { useConfirm } from '@/composables/useConfirm'; +import { usePagination } from '@/composables/usePagination'; import { useFormValidation } from '@/composables/useFormValidation'; import type { PropType, Props } from '@/types/api/stage'; import log from 'loglevel'; @@ -221,9 +220,8 @@ const systemStore = useSystemStore(); const { confirm } = useConfirm(); const { validationState } = useFormValidation(); -const rowsPerPage = 15; -const currentPropTypesPage = ref(1); -const currentPropsPage = ref(1); +const { perPage: propTypesPerPage, currentPage: currentPropTypesPage } = usePagination(); +const { perPage: propsPerPage, currentPage: currentPropsPage } = usePagination(); const isSubmitting = ref(false); const newPropTypeModal = ref>(); diff --git a/client-v3/src/components/show/config/stage/SceneryList.vue b/client-v3/src/components/show/config/stage/SceneryList.vue index 272d6cf2f..bad527bb5 100644 --- a/client-v3/src/components/show/config/stage/SceneryList.vue +++ b/client-v3/src/components/show/config/stage/SceneryList.vue @@ -6,7 +6,7 @@ @@ -38,11 +38,10 @@ - @@ -50,7 +49,7 @@ @@ -85,11 +84,10 @@ - @@ -214,6 +212,7 @@ import { required, helpers } from '@vuelidate/validators'; import { useStageStore } from '@/stores/stage'; import { useSystemStore } from '@/stores/system'; import { useConfirm } from '@/composables/useConfirm'; +import { usePagination } from '@/composables/usePagination'; import { useFormValidation } from '@/composables/useFormValidation'; import type { SceneryType, Scenery } from '@/types/api/stage'; import log from 'loglevel'; @@ -223,9 +222,8 @@ const systemStore = useSystemStore(); const { confirm } = useConfirm(); const { validationState } = useFormValidation(); -const rowsPerPage = 15; -const currentSceneryTypesPage = ref(1); -const currentSceneryPage = ref(1); +const { perPage: sceneryTypesPerPage, currentPage: currentSceneryTypesPage } = usePagination(); +const { perPage: sceneryPerPage, currentPage: currentSceneryPage } = usePagination(); const isSubmitting = ref(false); const newSceneryTypeModal = ref>(); diff --git a/client-v3/src/components/show/config/stage/StageManager.vue b/client-v3/src/components/show/config/stage/StageManager.vue index d5e6f61a7..cbd298eda 100644 --- a/client-v3/src/components/show/config/stage/StageManager.vue +++ b/client-v3/src/components/show/config/stage/StageManager.vue @@ -65,7 +65,7 @@ - @@ -98,7 +97,7 @@ - @@ -433,6 +431,7 @@ import { required, helpers } from '@vuelidate/validators'; import { useStageStore } from '@/stores/stage'; import { useShowStore } from '@/stores/show'; import { useConfirm } from '@/composables/useConfirm'; +import { usePagination } from '@/composables/usePagination'; import { useFormValidation } from '@/composables/useFormValidation'; import { findOrphanedAssignments } from '@/js/blockOrphanUtils'; import type { SceneryAllocation, PropsAllocation, CrewAssignment, Crew } from '@/types/api/stage'; @@ -451,9 +450,8 @@ const setExpanded = ref(false); const strikeExpanded = ref(false); const savingAssignment = ref(false); const newCrewSelections = ref>({}); -const rowsPerPage = 10; -const currentSceneryAllocPage = ref(1); -const currentPropsAllocPage = ref(1); +const { perPage: sceneryAllocPerPage, currentPage: currentSceneryAllocPage } = usePagination(); +const { perPage: propsAllocPerPage, currentPage: currentPropsAllocPage } = usePagination(); const goToSceneModal = ref>(); const addSceneryModal = ref>(); diff --git a/client-v3/src/components/user/settings/CueColourPreferences.vue b/client-v3/src/components/user/settings/CueColourPreferences.vue index d53841c99..8b36b354c 100644 --- a/client-v3/src/components/user/settings/CueColourPreferences.vue +++ b/client-v3/src/components/user/settings/CueColourPreferences.vue @@ -5,7 +5,7 @@ id="cue-colour-table" :items="tableData" :fields="columns" - :per-page="rowsPerPage" + :per-page="perPage" :current-page="currentPage" show-empty > @@ -54,6 +54,12 @@ + No show loaded. @@ -169,6 +175,7 @@ import log from 'loglevel'; import { useUserStore } from '@/stores/user'; import { useSystemStore } from '@/stores/system'; import { useFormValidation } from '@/composables/useFormValidation'; +import { usePagination } from '@/composables/usePagination'; import type { CueType } from '@/types/api/cues'; const userStore = useUserStore(); @@ -180,8 +187,7 @@ const columns = [ { key: 'example', label: 'Example Cue Button' }, { key: 'btn', label: '' }, ]; -const rowsPerPage = 15; -const currentPage = ref(1); +const { perPage, currentPage } = usePagination(); const cueTypes = ref([]); const isSubmittingNew = ref(false); diff --git a/client-v3/src/components/user/settings/StageDirectionStyles.vue b/client-v3/src/components/user/settings/StageDirectionStyles.vue index 31d6ffbfb..a0bcc42ba 100644 --- a/client-v3/src/components/user/settings/StageDirectionStyles.vue +++ b/client-v3/src/components/user/settings/StageDirectionStyles.vue @@ -5,7 +5,7 @@ id="stage-directions-table" :items="tableData" :fields="columns" - :per-page="rowsPerPage" + :per-page="perPage" :current-page="currentPage" show-empty > @@ -54,6 +54,12 @@ + No show loaded. @@ -243,6 +249,7 @@ import { makeURL } from '@/js/utils'; import { useUserStore } from '@/stores/user'; import { useSystemStore } from '@/stores/system'; import { useFormValidation } from '@/composables/useFormValidation'; +import { usePagination } from '@/composables/usePagination'; import type { StageDirectionStyle } from '@/types/api/script'; interface StyleOption { @@ -266,8 +273,7 @@ const columns = [ { key: 'example', label: 'Example Stage Direction' }, { key: 'btn', label: '' }, ]; -const rowsPerPage = 15; -const currentPage = ref(1); +const { perPage, currentPage } = usePagination(); const stageDirectionStyles = ref([]); const isSubmittingNew = ref(false); diff --git a/client-v3/src/composables/usePagination.ts b/client-v3/src/composables/usePagination.ts new file mode 100644 index 000000000..4a6e1ca30 --- /dev/null +++ b/client-v3/src/composables/usePagination.ts @@ -0,0 +1,20 @@ +import { ref, watch } from 'vue'; + +export const PER_PAGE_OPTIONS = [ + { value: 10, text: '10' }, + { value: 15, text: '15' }, + { value: 25, text: '25' }, + { value: 50, text: '50' }, + { value: 0, text: 'All' }, +] as const; + +export function usePagination(defaultPerPage = 15) { + const perPage = ref(defaultPerPage); + const currentPage = ref(1); + + watch(perPage, () => { + currentPage.value = 1; + }); + + return { perPage, currentPage }; +} diff --git a/client-v3/src/views/show/config/ConfigCast.vue b/client-v3/src/views/show/config/ConfigCast.vue index accb669c4..d3bc61316 100644 --- a/client-v3/src/views/show/config/ConfigCast.vue +++ b/client-v3/src/views/show/config/ConfigCast.vue @@ -8,7 +8,7 @@ id="cast-table" :items="showStore.castList" :fields="castFields" - :per-page="rowsPerPage" + :per-page="perPage" :current-page="currentPage" show-empty > @@ -40,13 +40,11 @@ - @@ -126,6 +124,7 @@ import log from 'loglevel'; import { useSystemStore } from '@/stores/system'; import { useShowStore } from '@/stores/show'; import { useConfirm } from '@/composables/useConfirm'; +import { usePagination } from '@/composables/usePagination'; import CastLineStats from '@/components/show/config/cast/CastLineStats.vue'; import type { Cast } from '@/types/api/show'; @@ -133,8 +132,7 @@ const systemStore = useSystemStore(); const showStore = useShowStore(); const { confirm } = useConfirm(); -const rowsPerPage = 15; -const currentPage = ref(1); +const { perPage, currentPage } = usePagination(); const submittingNewCast = ref(false); const submittingEditCast = ref(false); const deletingCast = ref(false); diff --git a/client-v3/src/views/show/config/ConfigCharacters.vue b/client-v3/src/views/show/config/ConfigCharacters.vue index 41c9ddc00..b72a6b604 100644 --- a/client-v3/src/views/show/config/ConfigCharacters.vue +++ b/client-v3/src/views/show/config/ConfigCharacters.vue @@ -8,7 +8,7 @@ id="character-table" :items="showStore.characterList" :fields="characterFields" - :per-page="rowsPerPage" + :per-page="perPage" :current-page="currentPage" show-empty > @@ -55,13 +55,11 @@ - @@ -180,6 +178,7 @@ import 'vue-multiselect/dist/vue-multiselect.css'; import { useSystemStore } from '@/stores/system'; import { useShowStore } from '@/stores/show'; import { useConfirm } from '@/composables/useConfirm'; +import { usePagination } from '@/composables/usePagination'; import CharacterGroups from '@/components/show/config/characters/CharacterGroups.vue'; import CharacterLineStats from '@/components/show/config/characters/CharacterLineStats.vue'; import type { Character } from '@/types/api/show'; @@ -188,8 +187,7 @@ const systemStore = useSystemStore(); const showStore = useShowStore(); const { confirm } = useConfirm(); -const rowsPerPage = 15; -const currentPage = ref(1); +const { perPage, currentPage } = usePagination(); const submittingNewCharacter = ref(false); const submittingEditCharacter = ref(false); const deletingCharacter = ref(false); diff --git a/client-v3/src/views/show/config/ConfigCues.vue b/client-v3/src/views/show/config/ConfigCues.vue index 9326a1e2c..9958698f9 100644 --- a/client-v3/src/views/show/config/ConfigCues.vue +++ b/client-v3/src/views/show/config/ConfigCues.vue @@ -8,7 +8,7 @@ id="cue-types-table" :items="showStore.cueTypes" :fields="cueTypeFields" - :per-page="rowsPerPage" + :per-page="perPage" :current-page="currentPage" show-empty > @@ -50,13 +50,11 @@ - @@ -221,6 +219,7 @@ import log from 'loglevel'; import { useSystemStore } from '@/stores/system'; import { useShowStore } from '@/stores/show'; import { useConfirm } from '@/composables/useConfirm'; +import { usePagination } from '@/composables/usePagination'; import CueCountStats from '@/components/show/config/cues/CueCountStats.vue'; import CueEditor from '@/components/show/config/cues/CueEditor.vue'; import type { CueType } from '@/types/api/cues'; @@ -229,8 +228,7 @@ const systemStore = useSystemStore(); const showStore = useShowStore(); const { confirm } = useConfirm(); -const rowsPerPage = 15; -const currentPage = ref(1); +const { perPage, currentPage } = usePagination(); const submittingNewCueType = ref(false); const submittingEditCueType = ref(false); const deletingCueType = ref(false); diff --git a/client/package-lock.json b/client/package-lock.json index d5d4110f0..259c91dc2 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,12 +1,12 @@ { "name": "client", - "version": "0.31.1", + "version": "0.31.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "client", - "version": "0.31.1", + "version": "0.31.2", "dependencies": { "bootstrap": "4.6.2", "bootstrap-vue": "2.23.1", diff --git a/client/package.json b/client/package.json index 015d1bd52..2152dd630 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "client", - "version": "0.31.1", + "version": "0.31.2", "description": "DigiScript front end", "author": "DreamTeamProd", "private": true, diff --git a/client/src/main.ts b/client/src/main.ts index c322f39ad..a20913a7e 100644 --- a/client/src/main.ts +++ b/client/src/main.ts @@ -6,6 +6,7 @@ import Vuelidate from 'vuelidate'; import ToastPlugin from 'vue-toast-notification'; import Multiselect from 'vue-multiselect'; import { Splitpanes, Pane } from 'splitpanes'; +import PaginationControls from '@/vue_components/shared/PaginationControls.vue'; import store from '@/store/store'; import App from './App.vue'; @@ -30,6 +31,7 @@ Vue.use(IconsPlugin); Vue.component('MultiSelect', Multiselect); Vue.component('SplitPanes', Splitpanes); Vue.component('SplitPane', Pane); +Vue.component('PaginationControls', PaginationControls); Vue.use(Vuex); Vue.use(Vuelidate as any); // @types/vuelidate bundles its own Vue copy that conflicts with main package diff --git a/client/src/mixins/paginationMixin.ts b/client/src/mixins/paginationMixin.ts new file mode 100644 index 000000000..3ffd58a35 --- /dev/null +++ b/client/src/mixins/paginationMixin.ts @@ -0,0 +1,15 @@ +import { defineComponent } from 'vue'; + +export default defineComponent({ + data() { + return { + rowsPerPage: 15 as number, + currentPage: 1 as number, + }; + }, + watch: { + rowsPerPage(this: { currentPage: number }) { + this.currentPage = 1; + }, + }, +}); diff --git a/client/src/views/show/config/ConfigCast.vue b/client/src/views/show/config/ConfigCast.vue index b607f3616..e03a2fae6 100644 --- a/client/src/views/show/config/ConfigCast.vue +++ b/client/src/views/show/config/ConfigCast.vue @@ -36,13 +36,11 @@ - @@ -150,11 +148,12 @@ import { mapGetters, mapActions } from 'vuex'; import CastLineStats from '@/vue_components/show/config/cast/CastLineStats.vue'; import log from 'loglevel'; import formValidationMixin from '@/mixins/formValidationMixin'; +import paginationMixin from '@/mixins/paginationMixin'; export default defineComponent({ name: 'ConfigCast', components: { CastLineStats }, - mixins: [formValidationMixin], + mixins: [formValidationMixin, paginationMixin], data() { return { castFields: ['first_name', 'last_name', { key: 'btn', label: '' }], @@ -162,8 +161,6 @@ export default defineComponent({ firstName: '', lastName: '', }, - rowsPerPage: 15, - currentPage: 1, editFormState: { id: null as number | null, showID: null as number | null, diff --git a/client/src/views/show/config/ConfigCharacters.vue b/client/src/views/show/config/ConfigCharacters.vue index 5c949575d..0e846fa1a 100644 --- a/client/src/views/show/config/ConfigCharacters.vue +++ b/client/src/views/show/config/ConfigCharacters.vue @@ -51,13 +51,11 @@ - @@ -207,15 +205,14 @@ import CharacterTimeline from '@/vue_components/show/config/characters/Character import log from 'loglevel'; import CharacterGroups from '@/vue_components/show/config/characters/CharacterGroups.vue'; import formValidationMixin from '@/mixins/formValidationMixin'; +import paginationMixin from '@/mixins/paginationMixin'; export default defineComponent({ name: 'ConfigCharacters', components: { CharacterGroups, CharacterLineStats, CharacterTimeline }, - mixins: [formValidationMixin], + mixins: [formValidationMixin, paginationMixin], data() { return { - rowsPerPage: 15, - currentPage: 1, characterFields: [ 'name', 'description', diff --git a/client/src/views/show/config/ConfigCues.vue b/client/src/views/show/config/ConfigCues.vue index e7b3ccc09..1879e4ccc 100644 --- a/client/src/views/show/config/ConfigCues.vue +++ b/client/src/views/show/config/ConfigCues.vue @@ -49,13 +49,11 @@ - @@ -228,18 +226,17 @@ import { defineComponent } from 'vue'; import { required, maxLength } from 'vuelidate/lib/validators'; import { mapGetters, mapActions } from 'vuex'; import log from 'loglevel'; - +import paginationMixin from '@/mixins/paginationMixin'; import CueEditor from '@/vue_components/show/config/cues/CueEditor.vue'; import CueCountStats from '@/vue_components/show/config/cues/CueCountStats.vue'; export default defineComponent({ name: 'ConfigCues', components: { CueCountStats, CueEditor }, + mixins: [paginationMixin], data() { return { cueTypeFields: ['prefix', 'description', 'colour', { key: 'btn', label: '' }], - rowsPerPage: 15, - currentPage: 1, newCueTypeForm: { prefix: '', description: '', diff --git a/client/src/vue_components/config/ConfigShows.vue b/client/src/vue_components/config/ConfigShows.vue index fd787b518..775ee5135 100644 --- a/client/src/vue_components/config/ConfigShows.vue +++ b/client/src/vue_components/config/ConfigShows.vue @@ -42,13 +42,11 @@ - @@ -152,9 +150,11 @@ import { required, maxLength } from 'vuelidate/lib/validators'; import { mapGetters, mapActions } from 'vuex'; import { makeURL } from '@/js/utils'; import log from 'loglevel'; +import paginationMixin from '@/mixins/paginationMixin'; export default defineComponent({ name: 'ConfigShows', + mixins: [paginationMixin], data() { return { loaded: false, @@ -166,8 +166,6 @@ export default defineComponent({ 'created_at', { key: 'btn', label: '' }, ], - rowsPerPage: 15, - currentPage: 1, isSubmittingLoad: false, isSubmittingShow: false, isDeleting: false, diff --git a/client/src/vue_components/config/ConfigSystem.vue b/client/src/vue_components/config/ConfigSystem.vue index 9a139ef21..d99453293 100644 --- a/client/src/vue_components/config/ConfigSystem.vue +++ b/client/src/vue_components/config/ConfigSystem.vue @@ -81,16 +81,15 @@ id="connected-clients-table" :items="connectedClients" :fields="clientFields" - :per-page="perPage" - :current-page="currentPageClients" + :per-page="rowsPerPage" + :current-page="currentPage" small /> - @@ -103,6 +102,7 @@ import { defineComponent } from 'vue'; import log from 'loglevel'; import { makeURL } from '@/js/utils'; +import paginationMixin from '@/mixins/paginationMixin'; interface VersionStatus { current_version: string | null; @@ -115,11 +115,10 @@ interface VersionStatus { export default defineComponent({ name: 'ConfigSystem', + mixins: [paginationMixin], data() { return { - perPage: 5, connectedClients: [] as unknown[], - currentPageClients: 1, clientFields: [ { key: 'internal_id', label: 'UUID' }, { key: 'remote_ip', label: 'IP' }, diff --git a/client/src/vue_components/shared/PaginationControls.vue b/client/src/vue_components/shared/PaginationControls.vue new file mode 100644 index 000000000..76989e928 --- /dev/null +++ b/client/src/vue_components/shared/PaginationControls.vue @@ -0,0 +1,78 @@ + + + diff --git a/client/src/vue_components/show/config/acts_and_scenes/ConfigActs.vue b/client/src/vue_components/show/config/acts_and_scenes/ConfigActs.vue index 01f5e0126..cbe583405 100644 --- a/client/src/vue_components/show/config/acts_and_scenes/ConfigActs.vue +++ b/client/src/vue_components/show/config/acts_and_scenes/ConfigActs.vue @@ -48,13 +48,11 @@ - - @@ -217,15 +215,14 @@ import { required, integer } from 'vuelidate/lib/validators'; import { mapGetters, mapActions } from 'vuex'; import log from 'loglevel'; import formValidationMixin from '@/mixins/formValidationMixin'; +import paginationMixin from '@/mixins/paginationMixin'; export default defineComponent({ name: 'ConfigScenes', - mixins: [formValidationMixin], + mixins: [formValidationMixin, paginationMixin], data() { return { loading: true, - rowsPerPage: 15, - currentPage: 1, sceneFields: [ 'name', 'act', diff --git a/client/src/vue_components/show/config/characters/CharacterGroups.vue b/client/src/vue_components/show/config/characters/CharacterGroups.vue index 1a3cc3c6b..e21670363 100644 --- a/client/src/vue_components/show/config/characters/CharacterGroups.vue +++ b/client/src/vue_components/show/config/characters/CharacterGroups.vue @@ -43,13 +43,11 @@ - - diff --git a/client/src/vue_components/show/config/sessions/SessionTagList.vue b/client/src/vue_components/show/config/sessions/SessionTagList.vue index 75c3aafce..45c441a65 100644 --- a/client/src/vue_components/show/config/sessions/SessionTagList.vue +++ b/client/src/vue_components/show/config/sessions/SessionTagList.vue @@ -44,13 +44,11 @@ - - - @@ -57,13 +55,11 @@ - @@ -358,6 +354,12 @@ export default defineComponent({ }, ...mapGetters(['PROPS_LIST', 'PROP_TYPES', 'IS_SHOW_EDITOR', 'PROP_TYPE_BY_ID']), }, + watch: { + rowsPerPage() { + this.currentPropPage = 1; + this.currentPropTypePage = 1; + }, + }, async mounted(): Promise { await Promise.all([(this as any).GET_PROP_TYPES(), (this as any).GET_PROPS_LIST()]); }, diff --git a/client/src/vue_components/show/config/stage/SceneryList.vue b/client/src/vue_components/show/config/stage/SceneryList.vue index e559479b4..e3025a1c1 100644 --- a/client/src/vue_components/show/config/stage/SceneryList.vue +++ b/client/src/vue_components/show/config/stage/SceneryList.vue @@ -23,13 +23,11 @@ - @@ -57,13 +55,11 @@ - @@ -366,6 +362,12 @@ export default defineComponent({ }, ...mapGetters(['SCENERY_LIST', 'SCENERY_TYPES', 'IS_SHOW_EDITOR', 'SCENERY_TYPE_BY_ID']), }, + watch: { + rowsPerPage() { + this.currentSceneryPage = 1; + this.currentSceneryTypesPage = 1; + }, + }, async mounted(): Promise { await Promise.all([(this as any).GET_SCENERY_TYPES(), (this as any).GET_SCENERY_LIST()]); }, diff --git a/client/src/vue_components/show/config/stage/StageManager.vue b/client/src/vue_components/show/config/stage/StageManager.vue index 0e2b65012..bedfcd7c8 100644 --- a/client/src/vue_components/show/config/stage/StageManager.vue +++ b/client/src/vue_components/show/config/stage/StageManager.vue @@ -90,13 +90,11 @@ - @@ -122,13 +120,11 @@ - @@ -668,6 +664,12 @@ export default defineComponent({ 'CREW_ASSIGNMENTS_BY_SCENERY', ]), }, + watch: { + rowsPerPage() { + this.currentSceneryAllocPage = 1; + this.currentPropsAllocPage = 1; + }, + }, async mounted(): Promise { await Promise.all([ (this as any).GET_ACT_LIST(), diff --git a/electron/package-lock.json b/electron/package-lock.json index fe6e86a62..4a93c7d1b 100644 --- a/electron/package-lock.json +++ b/electron/package-lock.json @@ -1,12 +1,12 @@ { "name": "digiscript-electron", - "version": "0.31.1", + "version": "0.31.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "digiscript-electron", - "version": "0.31.1", + "version": "0.31.2", "license": "GPL-3.0", "dependencies": { "bonjour-service": "^1.4.0", diff --git a/electron/package.json b/electron/package.json index fd55c1cd1..a4f6cae3b 100644 --- a/electron/package.json +++ b/electron/package.json @@ -1,6 +1,6 @@ { "name": "digiscript-electron", - "version": "0.31.1", + "version": "0.31.2", "description": "DigiScript Electron Desktop Application", "author": "DreamTeamProd", "license": "GPL-3.0", diff --git a/server/pyproject.toml b/server/pyproject.toml index 90327a909..3e077ffc2 100644 --- a/server/pyproject.toml +++ b/server/pyproject.toml @@ -11,7 +11,7 @@ build-backend = "setuptools.build_meta" [project] name = "digiscript-server" -version = "0.31.1" +version = "0.31.2" description = "DigiScript server - Digital script management for theatrical shows" readme = "../README.md" requires-python = ">=3.13"