Skip to content

Commit 3dc5078

Browse files
committed
refactor: split catalog presentation and crud table composables
Signed-off-by: Vitor Mattos <[email protected]>
1 parent 0114f5d commit 3dc5078

7 files changed

Lines changed: 342 additions & 241 deletions

File tree

src/tests/views/Settings/SettingsPolicyWorkbench.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ vi.mock('../../../store/policies', () => ({
5151
}),
5252
}))
5353

54-
import RealPolicyWorkbench from '../../../views/Settings/PolicyWorkbench/RealPolicyWorkbench.vue'
54+
import RealPolicyWorkbench from '../../../views/Settings/PolicyWorkbench/Catalog/Catalog.vue'
5555

5656
function mountWorkbench() {
5757
return mount(RealPolicyWorkbench, {

src/views/Policies/Policies.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</template>
1111

1212
<script setup lang="ts">
13-
import RealPolicyWorkbench from '../Settings/PolicyWorkbench/RealPolicyWorkbench.vue'
13+
import RealPolicyWorkbench from '../Settings/PolicyWorkbench/Catalog/Catalog.vue'
1414
1515
defineOptions({
1616
name: 'Policies',

src/views/Settings/PolicyWorkbench/Catalog/Catalog.vue

Lines changed: 32 additions & 222 deletions
Original file line numberDiff line numberDiff line change
@@ -547,10 +547,10 @@ import NcTextField from '@nextcloud/vue/components/NcTextField'
547547
548548
import { usePoliciesStore } from '../../../../store/policies'
549549
import { useUserConfigStore } from '../../../../store/userconfig.js'
550-
import { realDefinitions } from '../settings/realDefinitions'
551-
import type { RealPolicySettingCategory } from '../settings/realTypes'
552550
import PolicyRuleEditorPanel from '../PolicyRuleEditorPanel.vue'
553551
import { createRealPolicyWorkbenchState } from '../useRealPolicyWorkbench'
552+
import { useCatalogCrudTable } from './composables/useCatalogCrudTable'
553+
import { useCatalogPresentation } from './composables/useCatalogPresentation'
554554
import { useCatalogState } from './composables/useCatalogState'
555555
import { useCatalogInteractions } from './composables/useCatalogInteractions'
556556
import { useNavigation } from './composables/useNavigation'
@@ -573,10 +573,6 @@ const isRemovingRule = ref(false)
573573
const removalFeedback = ref<string | null>(null)
574574
const removalFeedbackTimeout = ref<number | null>(null)
575575
const openRuleActionsKey = ref<string | null>(null)
576-
const crudSearch = ref('')
577-
const crudScopeFilter = ref<'all' | 'system' | 'group' | 'user'>('all')
578-
const crudPage = ref(1)
579-
const scopeFilterOpen = ref(false)
580576
const isRtl = ref(false)
581577
582578
// Initialize catalog state composable
@@ -593,87 +589,39 @@ const {
593589
getFilter: () => catalogState.settingsFilter.value,
594590
onOpenSetting: (key) => state.openSetting(key as never),
595591
})
596-
597-
const CRUD_PAGE_SIZE = 20
598-
const REMOVAL_FEEDBACK_DURATION_MS = 6000
599-
600-
const CATEGORY_ORDER: RealPolicySettingCategory[] = [
601-
'who-can-sign',
602-
'how-signing-works',
603-
'signer-experience',
604-
'what-gets-recorded',
605-
'time-and-limits',
606-
'trust-and-verification',
607-
'system-behavior',
608-
]
609-
610-
const categoryLabel = (category: RealPolicySettingCategory): string => {
611-
switch (category) {
612-
case 'who-can-sign':
613-
return t('libresign', 'Who can sign documents')
614-
case 'how-signing-works':
615-
return t('libresign', 'How signing works')
616-
case 'signer-experience':
617-
return t('libresign', 'What the signer sees')
618-
case 'what-gets-recorded':
619-
return t('libresign', 'What gets recorded')
620-
case 'time-and-limits':
621-
return t('libresign', 'Time and limits')
622-
case 'trust-and-verification':
623-
return t('libresign', 'Trust and verification')
624-
case 'system-behavior':
625-
return t('libresign', 'System behavior')
626-
default:
627-
return t('libresign', 'Other')
628-
}
629-
}
630-
631-
const categoryBySettingKey = computed<Record<string, RealPolicySettingCategory>>(() => {
632-
const map: Record<string, RealPolicySettingCategory> = {}
633-
for (const definition of Object.values(realDefinitions)) {
634-
map[definition.key] = definition.category ?? 'system-behavior'
635-
}
636-
637-
return map
592+
const {
593+
filteredSettingSummaries,
594+
visibleCategorySections,
595+
effectiveCatalogLayout,
596+
hasActiveFilter,
597+
hasVisibleCategorySections,
598+
showCategoryNavigation,
599+
catalogViewButtonLabel,
600+
catalogCollapseButtonLabel,
601+
} = useCatalogPresentation({
602+
visibleSettingSummaries: computed(() => state.visibleSettingSummaries),
603+
settingsFilter: catalogState.settingsFilter,
604+
catalogLayout: catalogState.catalogLayout,
605+
isCatalogCollapsed: catalogState.isCatalogCollapsed,
606+
isSmallViewport,
638607
})
639-
640-
const filteredSettingSummaries = computed(() => {
641-
const normalized = catalogState.settingsFilter.value.trim().toLowerCase()
642-
if (!normalized) {
643-
return state.visibleSettingSummaries
644-
}
645-
646-
return state.visibleSettingSummaries.filter((summary) => {
647-
return [summary.title, summary.context ?? '', summary.description, summary.defaultSummary]
648-
.some((value) => value.toLowerCase().includes(normalized))
649-
})
608+
const {
609+
crudSearch,
610+
crudScopeFilter,
611+
crudPage,
612+
scopeFilterOpen,
613+
crudPageCount,
614+
pagedCrudRows,
615+
activeScopeFilterChip,
616+
crudScopeLabel,
617+
onCrudSearchChange,
618+
setCrudScopeFilter,
619+
} = useCatalogCrudTable({
620+
state,
621+
summarizeRuleValue,
650622
})
651623
652-
const visibleCategorySections = computed<Array<{
653-
key: RealPolicySettingCategory,
654-
id: string,
655-
label: string,
656-
summaries: typeof filteredSettingSummaries.value,
657-
}>>(() => {
658-
const grouped = new Map<RealPolicySettingCategory, typeof filteredSettingSummaries.value>()
659-
for (const category of CATEGORY_ORDER) {
660-
grouped.set(category, [])
661-
}
662-
663-
for (const summary of filteredSettingSummaries.value) {
664-
const category = categoryBySettingKey.value[summary.key] ?? 'system-behavior'
665-
grouped.get(category)?.push(summary)
666-
}
667-
668-
return CATEGORY_ORDER
669-
.map((category) => ({
670-
key: category,
671-
id: `policy-category-${category}`,
672-
label: categoryLabel(category),
673-
summaries: grouped.get(category) ?? [],
674-
}))
675-
.filter((category) => category.summaries.length > 0)
676-
})
624+
const REMOVAL_FEEDBACK_DURATION_MS = 6000
677625
678626
const navigation = useNavigation(visibleCategorySections)
679627
@@ -688,20 +636,6 @@ const activeEditorProps = computed<Record<string, unknown>>(() => {
688636
689637
return state.activeDefinition.resolveEditorProps?.(activePolicy, baseEditorProps) ?? baseEditorProps
690638
})
691-
const effectiveCatalogLayout = computed(() => isSmallViewport.value ? 'cards' : catalogState.catalogLayout.value)
692-
const hasActiveFilter = computed(() => catalogState.settingsFilter.value.trim().length > 0)
693-
const hasVisibleCategorySections = computed(() => visibleCategorySections.value.length > 0)
694-
const showCategoryNavigation = computed(() => hasVisibleCategorySections.value)
695-
const catalogViewButtonLabel = computed(() => {
696-
return effectiveCatalogLayout.value === 'cards'
697-
? t('libresign', 'Switch to compact view')
698-
: t('libresign', 'Switch to card view')
699-
})
700-
const catalogCollapseButtonLabel = computed(() => {
701-
return catalogState.isCatalogCollapsed.value
702-
? t('libresign', 'Expand settings categories')
703-
: t('libresign', 'Collapse settings categories')
704-
})
705639
706640
const selectedTargetOptions = computed(() => {
707641
if (!state.editorDraft) {
@@ -711,93 +645,6 @@ const selectedTargetOptions = computed(() => {
711645
return state.availableTargets.filter((option) => state.editorDraft?.targetIds.includes(option.id))
712646
})
713647
714-
type CrudScope = 'system' | 'group' | 'user'
715-
type CrudRow = {
716-
key: string,
717-
ruleId: string | null,
718-
scope: CrudScope,
719-
targetLabel: string,
720-
valueLabel: string,
721-
canRemove: boolean,
722-
}
723-
724-
const filteredCrudRows = computed<CrudRow[]>(() => {
725-
const rows: CrudRow[] = []
726-
const systemRule = state.inheritedSystemRule
727-
if (systemRule && state.hasGlobalDefault) {
728-
rows.push({
729-
key: systemRule.id,
730-
ruleId: systemRule.id,
731-
scope: 'system',
732-
targetLabel: t('libresign', 'Default (everyone)'),
733-
valueLabel: state.summary?.currentBaseValue ?? t('libresign', 'Not configured'),
734-
canRemove: Boolean(systemRule.id),
735-
})
736-
}
737-
738-
for (const rule of state.visibleGroupRules) {
739-
rows.push({
740-
key: rule.id,
741-
ruleId: rule.id,
742-
scope: 'group',
743-
targetLabel: state.resolveTargetLabel('group', rule.targetId || ''),
744-
valueLabel: summarizeRuleValue(rule.value),
745-
canRemove: true,
746-
})
747-
}
748-
749-
for (const rule of state.visibleUserRules) {
750-
rows.push({
751-
key: rule.id,
752-
ruleId: rule.id,
753-
scope: 'user',
754-
targetLabel: state.resolveTargetLabel('user', rule.targetId || ''),
755-
valueLabel: summarizeRuleValue(rule.value),
756-
canRemove: true,
757-
})
758-
}
759-
760-
const scopePriority: Record<CrudScope, number> = {
761-
user: 0,
762-
group: 1,
763-
system: 2,
764-
}
765-
766-
rows.sort((left, right) => {
767-
const priorityDiff = scopePriority[left.scope] - scopePriority[right.scope]
768-
if (priorityDiff !== 0) {
769-
return priorityDiff
770-
}
771-
772-
return left.targetLabel.localeCompare(right.targetLabel)
773-
})
774-
775-
const normalized = crudSearch.value.trim().toLowerCase()
776-
777-
return rows.filter((row) => {
778-
if (crudScopeFilter.value !== 'all' && row.scope !== crudScopeFilter.value) {
779-
return false
780-
}
781-
782-
if (!normalized) {
783-
return true
784-
}
785-
786-
const scope = crudScopeLabel(row.scope).toLowerCase()
787-
return [scope, row.targetLabel.toLowerCase(), row.valueLabel.toLowerCase()]
788-
.some((value) => value.includes(normalized))
789-
})
790-
})
791-
792-
const crudPageCount = computed(() => Math.max(1, Math.ceil(filteredCrudRows.value.length / CRUD_PAGE_SIZE)))
793-
const pagedCrudRows = computed(() => {
794-
if (crudPage.value > crudPageCount.value) {
795-
crudPage.value = crudPageCount.value
796-
}
797-
798-
const start = (crudPage.value - 1) * CRUD_PAGE_SIZE
799-
return filteredCrudRows.value.slice(start, start + CRUD_PAGE_SIZE)
800-
})
801648
802649
const ruleDialogTitle = computed(() => {
803650
if (!state.editorDraft) {
@@ -964,16 +811,6 @@ const createScopeOptions = computed<Array<{
964811
})
965812
})
966813
967-
const activeScopeFilterChip = computed(() => {
968-
if (crudScopeFilter.value === 'all') {
969-
return ''
970-
}
971-
972-
return t('libresign', 'Scope: {scope}', {
973-
scope: crudScopeLabel(crudScopeFilter.value),
974-
})
975-
})
976-
977814
const defaultSourceLabel = computed(() => {
978815
return state.hasGlobalDefault
979816
? t('libresign', 'custom')
@@ -1134,33 +971,6 @@ function resolveSignatureFlowMode(value: unknown): 'parallel' | 'ordered_numeric
1134971
return null
1135972
}
1136973
1137-
function crudScopeLabel(scope: CrudScope) {
1138-
if (scope === 'system') {
1139-
return t('libresign', 'Everyone')
1140-
}
1141-
1142-
if (scope === 'group') {
1143-
return t('libresign', 'Group')
1144-
}
1145-
1146-
return t('libresign', 'User')
1147-
}
1148-
1149-
function onCrudSearchChange(value: string | number) {
1150-
crudSearch.value = String(value ?? '')
1151-
crudPage.value = 1
1152-
}
1153-
1154-
function setCrudScopeFilter(value: 'all' | 'system' | 'group' | 'user', selected: boolean) {
1155-
if (!selected) {
1156-
return
1157-
}
1158-
1159-
crudScopeFilter.value = value
1160-
crudPage.value = 1
1161-
scopeFilterOpen.value = false
1162-
}
1163-
1164974
function requestCreateRule() {
1165975
if (!hasCreatableScope.value || saveStatus.value === 'saving') {
1166976
return

0 commit comments

Comments
 (0)