From bcb9569b268de2924f0c8b3592329804ae7f0ffd Mon Sep 17 00:00:00 2001 From: vikingowl Date: Mon, 14 Apr 2025 21:43:18 +0200 Subject: [PATCH] Refactor package filtering and display components Replaced inline package filtering and table logic with dedicated reusable components: `PackageFilters` and `PackageTable`. Introduced a composable (`usePackageFilters`) for managing filtering logic and state, and optimized display logic with a new `usePackageDisplay` composable. This improves maintainability, readability, and separation of concerns. --- frontend/src/components/Packages.vue | 397 +----------------- .../components/Packages/PackageFilters.vue | 66 +++ .../src/components/Packages/PackageTable.vue | 115 +++++ .../composables/Packages/usePackageDisplay.ts | 98 +++++ .../composables/Packages/usePackageFilters.ts | 171 ++++++++ frontend/src/config/constants.ts | 28 ++ 6 files changed, 489 insertions(+), 386 deletions(-) create mode 100644 frontend/src/components/Packages/PackageFilters.vue create mode 100644 frontend/src/components/Packages/PackageTable.vue create mode 100644 frontend/src/composables/Packages/usePackageDisplay.ts create mode 100644 frontend/src/composables/Packages/usePackageFilters.ts create mode 100644 frontend/src/config/constants.ts diff --git a/frontend/src/components/Packages.vue b/frontend/src/components/Packages.vue index 60a0b52..20ab911 100644 --- a/frontend/src/components/Packages.vue +++ b/frontend/src/components/Packages.vue @@ -1,398 +1,23 @@ diff --git a/frontend/src/components/Packages/PackageFilters.vue b/frontend/src/components/Packages/PackageFilters.vue new file mode 100644 index 0000000..9f9cf52 --- /dev/null +++ b/frontend/src/components/Packages/PackageFilters.vue @@ -0,0 +1,66 @@ + + + diff --git a/frontend/src/components/Packages/PackageTable.vue b/frontend/src/components/Packages/PackageTable.vue new file mode 100644 index 0000000..eb3b650 --- /dev/null +++ b/frontend/src/components/Packages/PackageTable.vue @@ -0,0 +1,115 @@ + + + diff --git a/frontend/src/composables/Packages/usePackageDisplay.ts b/frontend/src/composables/Packages/usePackageDisplay.ts new file mode 100644 index 0000000..e5d90e2 --- /dev/null +++ b/frontend/src/composables/Packages/usePackageDisplay.ts @@ -0,0 +1,98 @@ +import { type Package } from '@/types/Package' + +export function usePackageDisplay() { + const repoName = (repo: string) => repo.split('-')[0] + const repoVersion = (repo: string) => repo.split('-')[repo.split('-').length - 1] + + const getVersionColor = (version: string) => { + switch (version) { + case 'v2': + return '#3498db' + case 'v3': + return '#f39c12' + case 'v4': + return '#2ecc71' + default: + return 'grey' + } + } + + const getStatusColor = (status: Package['status']) => { + switch (status) { + case 'skipped': + return '#373737' + case 'queued': + return '#5d2f03' + case 'latest': + return '' + case 'failed': + return '#4f140f' + case 'signing': + return '#093372' + case 'building': + return '#084f46' + case 'unknown': + return '#191919' + default: + return '' + } + } + + const getLto = (lto: Package['lto']) => { + switch (lto) { + case 'enabled': + return { + title: 'built with LTO', + class: 'fa fa-check fa-lg text-success' + } + case 'unknown': + return { + title: 'not built with LTO yet', + class: 'fa fa-hourglass-o fa-lg text-grey' + } + case 'disabled': + return { + title: 'LTO explicitly disabled', + class: 'fa fa-times fa-lg text-red' + } + case 'auto_disabled': + return { + title: 'LTO automatically disabled', + class: 'fa fa-times-circle-o fa-lg text-amber' + } + default: + return { title: '', class: '' } + } + } + + const getDs = (ds: Package['debug_symbols']) => { + switch (ds) { + case 'available': + return { + title: 'Debug symbols available', + class: 'fa fa-check fa-lg text-success' + } + case 'unknown': + return { + title: 'Not built yet', + class: 'fa fa-hourglass-o fa-lg text-grey' + } + case 'not_available': + return { + title: 'Not built with debug symbols', + class: 'fa fa-times fa-lg text-red' + } + default: + return { title: '', class: '' } + } + } + + return { + repoName, + repoVersion, + getVersionColor, + getStatusColor, + getLto, + getDs + } +} diff --git a/frontend/src/composables/Packages/usePackageFilters.ts b/frontend/src/composables/Packages/usePackageFilters.ts new file mode 100644 index 0000000..b5a3bd4 --- /dev/null +++ b/frontend/src/composables/Packages/usePackageFilters.ts @@ -0,0 +1,171 @@ +import { ref, watch } from 'vue' +import { type Package } from '@/types/Package' +import { useDataStore } from '@/stores/dataStore' + +const OFFSET = 50 + +export interface FilterOptions { + pkgbaseSearch: string + repo?: { title: string; value: string } + statuses: Array<{ title: string; value: string }> + exactSearch: boolean + page: number +} + +export interface FilterResult { + packages: Array + totalPages: number + noResults: boolean +} + +export function usePackageFilters() { + const dataStore = useDataStore() + + const filterOptions = ref({ + pkgbaseSearch: '', + repo: undefined, + statuses: [], + exactSearch: false, + page: 1 + }) + + const filterResult = ref({ + packages: [], + totalPages: 1, + noResults: false + }) + + const applyFilters = () => { + const { pkgbaseSearch, repo, statuses, exactSearch, page } = filterOptions.value + const offset = OFFSET * (page - 1) + + let filteredPackages: Package[] = [] + + if (statuses.length === 1) { + const statusValue = statuses[0].value as Package['status'] + const result = dataStore.getPackagesByStatus(statusValue) + filteredPackages = result.packages + } else { + const allPackagesResult = dataStore.getAllPackages() + filteredPackages = allPackagesResult.packages + + if (statuses.length > 0) { + const statusValues = statuses.map((status) => status.value) + filteredPackages = filteredPackages.filter((pkg) => statusValues.includes(pkg.status)) + } + } + + if (repo) { + filteredPackages = filteredPackages.filter((pkg) => pkg.repo === repo.value) + } + + if (pkgbaseSearch) { + const searchTerm = pkgbaseSearch.toLowerCase() + filteredPackages = filteredPackages.filter((pkg) => + exactSearch + ? pkg.pkgbase.toLowerCase() === searchTerm + : pkg.pkgbase.toLowerCase().includes(searchTerm) + ) + } + + if (filteredPackages.length > 0) { + const startIndex = offset + const endIndex = startIndex + OFFSET + + filterResult.value = { + packages: filteredPackages.slice(startIndex, endIndex), + totalPages: Math.ceil(filteredPackages.length / OFFSET), + noResults: false + } + } else { + filterResult.value = { + packages: [], + totalPages: 1, + noResults: true + } + } + } + + const initFromUrl = () => { + const urlParams = new URLSearchParams(window.location.search) + + filterOptions.value = { + pkgbaseSearch: urlParams.get('pkgbase') || '', + page: urlParams.has('page') ? parseInt(urlParams.get('page') || '1') : 1, + exactSearch: urlParams.has('exact'), + statuses: urlParams.getAll('status').map((status) => ({ + title: status.toUpperCase(), + value: status + })), + repo: undefined + } + + const repoValue = urlParams.get('repo') + if (repoValue) { + filterOptions.value.repo = { title: repoValue, value: repoValue } + } + + applyFilters() + } + + const updateUrlParams = () => { + const { pkgbaseSearch, repo, statuses, exactSearch, page } = filterOptions.value + const params = new URLSearchParams() + + // Only add page parameter if it's not the first page + if (page > 1) { + params.set('page', page.toString()) + } + + if (pkgbaseSearch) { + params.set('pkgbase', pkgbaseSearch.toLowerCase()) + } + + if (repo) { + params.set('repo', repo.value) + } + + if (statuses.length > 0) { + statuses.forEach((status) => { + params.append('status', status.value) + }) + } + + if (exactSearch) { + params.set('exact', '') + } + + const paramsString = params.toString() + if (paramsString) { + window.history.pushState(null, '', `${window.location.pathname}?${paramsString}`) + } else { + window.history.pushState(null, '', window.location.pathname) + } + } + + watch( + filterOptions, + () => { + updateUrlParams() + applyFilters() + }, + { deep: true } + ) + + watch( + () => dataStore.data.value, + () => { + if (dataStore.data.value) { + applyFilters() + } + }, + { deep: true } + ) + + return { + filterOptions, + filterResult, + initFromUrl, + applyFilters + } +} diff --git a/frontend/src/config/constants.ts b/frontend/src/config/constants.ts new file mode 100644 index 0000000..35ee3b6 --- /dev/null +++ b/frontend/src/config/constants.ts @@ -0,0 +1,28 @@ +// UI Constants +export const TRANSPARENT_COLOR = 'transparent' +export const ROW_HEIGHT = 60 + +// Select Items Constants +export const REPO_ITEMS = [ + { title: 'core-x86-64-v2', value: 'core-x86-64-v2' }, + { title: 'core-x86-64-v3', value: 'core-x86-64-v3' }, + { title: 'core-x86-64-v4', value: 'core-x86-64-v4' }, + { title: 'extra-x86-64-v2', value: 'extra-x86-64-v2' }, + { title: 'extra-x86-64-v3', value: 'extra-x86-64-v3' }, + { title: 'extra-x86-64-v4', value: 'extra-x86-64-v4' }, + { title: 'multilib-x86-64-v2', value: 'multilib-x86-64-v2' }, + { title: 'multilib-x86-64-v3', value: 'multilib-x86-64-v3' }, + { title: 'multilib-x86-64-v4', value: 'multilib-x86-64-v4' } +] + +export const STATUS_ITEMS = [ + { title: 'Latest', value: 'latest' }, + { title: 'Built', value: 'built' }, + { title: 'Failed', value: 'failed' }, + { title: 'Skipped', value: 'skipped' }, + { title: 'Delayed', value: 'delayed' }, + { title: 'Queued', value: 'queued' }, + { title: 'Building', value: 'building' }, + { title: 'Signing', value: 'signing' }, + { title: 'Unknown', value: 'unknown' } +]