From 6fc3df4a0d1fdaae5f0319e9af48d656be9c5edd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Steinhagen?= <88777268+bjoernsteinhagen@users.noreply.github.com> Date: Mon, 2 Mar 2026 15:34:36 +0200 Subject: [PATCH] refactor: centralized filter validation and generalized 'empty selection' checks --- components/model/Sender.vue | 3 +- lib/validation.ts | 57 +++++++++++++++++++++++++++++++++++++ store/hostApp.ts | 45 +++++------------------------ 3 files changed, 66 insertions(+), 39 deletions(-) diff --git a/components/model/Sender.vue b/components/model/Sender.vue index 6c39dab..aecf82a 100644 --- a/components/model/Sender.vue +++ b/components/model/Sender.vue @@ -217,7 +217,8 @@ const updateFilter = (filter: ISendFilter) => { } const isSaveDisabled = computed(() => { - return !store.validateSendFilter(newFilter.value || props.modelCard.sendFilter).valid + const filterToCheck = newFilter.value || props.modelCard.sendFilter + return !store.validateSendFilter(filterToCheck).valid }) const saveFilter = async () => { diff --git a/lib/validation.ts b/lib/validation.ts index 491b4d4..ebd419a 100644 --- a/lib/validation.ts +++ b/lib/validation.ts @@ -1,6 +1,24 @@ +import { computed } from 'vue' +import type { + ISendFilter, + SendFilterSelect, + RevitCategoriesSendFilter, + RevitViewsSendFilter +} from '~/lib/models/card/send' import { ValidationHelpers } from '@speckle/ui-components' import type { GenericValidateFunction } from 'vee-validate' +export const isSelectFilter = (f: ISendFilter): f is SendFilterSelect => + f.type === 'Select' || 'selectedItems' in f + +export const isRevitCategoriesFilter = ( + f: ISendFilter +): f is RevitCategoriesSendFilter => + f.id === 'revitCategories' || f.id === 'archicadLayers' + +export const isRevitViewsFilter = (f: ISendFilter): f is RevitViewsSendFilter => + f.id === 'revitViews' + export const isEmail = ValidationHelpers.isEmail export const isOneOrMultipleEmails = ValidationHelpers.isOneOrMultipleEmails @@ -42,3 +60,42 @@ export function useModelNameValidationRules() { isValidModelName ]) } + +export type FilterValidationResult = { valid: boolean; reason?: string } + +export function validateFilter( + filter: ISendFilter | undefined, + context: { selectionCount: number } +): FilterValidationResult { + if (!filter) return { valid: false, reason: 'No filter selected' } + + // Selection Filter check + if (filter.name === 'Selection' || filter.id === 'selection') { + return context.selectionCount > 0 + ? { valid: true } + : { valid: false, reason: 'No objects selected to publish' } + } + + // List-based filters (Rhino Layers, etc.) + if (isSelectFilter(filter)) { + return (filter.selectedItems?.length ?? 0) > 0 + ? { valid: true } + : { valid: false, reason: 'No items selected to publish' } + } + + // Category-based filters + if (isRevitCategoriesFilter(filter)) { + return (filter.selectedCategories?.length ?? 0) > 0 + ? { valid: true } + : { valid: false, reason: 'No categories selected to publish' } + } + + // View-based filters + if (isRevitViewsFilter(filter)) { + return filter.selectedView?.trim() + ? { valid: true } + : { valid: false, reason: 'No view selected to publish' } + } + + return { valid: true } +} diff --git a/store/hostApp.ts b/store/hostApp.ts index 9f40b98..235e954 100644 --- a/store/hostApp.ts +++ b/store/hostApp.ts @@ -11,9 +11,10 @@ import type { ISendFilterSelectItem, ISenderModelCard, RevitViewsSendFilter, - RevitCategoriesSendFilter, SendFilterSelect } from '~/lib/models/card/send' +import { useSelectionStore } from '~/store/selection' +import { validateFilter } from '~/lib/validation' import type { ToastNotification } from '@speckle/ui-components' import { ToastNotificationType } from '@speckle/ui-components' import type { Nullable } from '@speckle/shared' @@ -23,7 +24,6 @@ import { defineStore } from 'pinia' import type { CardSetting } from '~/lib/models/card/setting' import type { DUIAccount } from '~/store/accounts' import { useAccountStore } from '~/store/accounts' -import { useSelectionStore } from '~/store/selection' import { useUpdateConnector, type Version @@ -370,43 +370,12 @@ export const useHostAppStore = defineStore('hostAppStore', () => { */ app.$sendBinding?.on('refreshSendFilters', () => void refreshSendFilters()) - const validateSendFilter = ( - filter?: ISendFilter - ): { valid: boolean; reason?: string } => { - if (!filter) return { valid: false, reason: 'No filter selected' } + const validateSendFilter = (filter?: ISendFilter) => { + const selectionStore = useSelectionStore() - if (filter.name === 'Selection' || filter.id === 'selection') { - const selectionStore = useSelectionStore() - if ( - !selectionStore.selectionInfo.selectedObjectIds || - selectionStore.selectionInfo.selectedObjectIds.length === 0 - ) { - return { valid: false, reason: 'No objects selected to publish' } - } - } - - if (filter.type === 'Select' || filter.id === 'navisworksSavedSets') { - const f = filter as SendFilterSelect - if (!f.selectedItems || f.selectedItems.length === 0) { - return { valid: false, reason: 'No items selected to publish' } - } - } - - if (filter.id === 'revitCategories' || filter.id === 'archicadLayers') { - const f = filter as RevitCategoriesSendFilter - if (!f.selectedCategories || f.selectedCategories.length === 0) { - return { valid: false, reason: 'No categories selected to publish' } - } - } - - if (filter.id === 'revitViews') { - const f = filter as RevitViewsSendFilter - if (!f.selectedView || f.selectedView.trim() === '') { - return { valid: false, reason: 'No view selected to publish' } - } - } - - return { valid: true } + return validateFilter(filter, { + selectionCount: selectionStore.selectionInfo.selectedObjectIds?.length ?? 0 + }) } /**