795aec9578
* feat(extension): add PluginSortControl reusable component for sorting * i18n: add i18n keys for plugin sorting and filtering features * feat(extension): add sorting and status filtering for installed plugins Backend changes (plugin.py): - Add _resolve_plugin_dir method to resolve plugin directory path - Add _get_plugin_installed_at method to get installation time from file mtime - Add installed_at field to plugin API response Frontend changes (InstalledPluginsTab.vue): - Import PluginSortControl component - Add status filter toggle (all/enabled/disabled) using v-btn-toggle - Integrate PluginSortControl for sorting options - Add toolbar layout with actions and controls sections Frontend changes (MarketPluginsTab.vue): - Import PluginSortControl component - Replace v-select + v-btn combination with unified PluginSortControl Frontend changes (useExtensionPage.js): - Add installedStatusFilter, installedSortBy, installedSortOrder refs - Add installedSortItems and installedSortUsesOrder computed properties - Add sortInstalledPlugins function with multi-criteria support - Support sorting by install time, name, author, and update status - Add status filtering in filteredPlugins computed property - Disable default table sorting by setting sortable: false * test: add tests for installed_at field in plugin API - Assert all plugins have installed_at field in get_plugins response - Assert installed_at is not null after plugin installation * fix(extension): add explicit fallbacks for installed plugin sort comparisons * i18n(extension): rename install time label to last modified * fix(extension): cache installed_at parsing and validate timestamp format in tests * test(dashboard): strengthen installed_at coverage for plugin API
98 lines
1.9 KiB
Vue
98 lines
1.9 KiB
Vue
<script setup>
|
|
const props = defineProps({
|
|
modelValue: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
items: {
|
|
type: Array,
|
|
required: true,
|
|
},
|
|
label: {
|
|
type: String,
|
|
required: true,
|
|
},
|
|
order: {
|
|
type: String,
|
|
default: "desc",
|
|
},
|
|
ascendingLabel: {
|
|
type: String,
|
|
default: "Ascending",
|
|
},
|
|
descendingLabel: {
|
|
type: String,
|
|
default: "Descending",
|
|
},
|
|
showOrder: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
});
|
|
|
|
const emit = defineEmits(["update:modelValue", "update:order"]);
|
|
|
|
const updateSortBy = (value) => {
|
|
emit("update:modelValue", value);
|
|
};
|
|
|
|
const toggleOrder = () => {
|
|
emit("update:order", props.order === "desc" ? "asc" : "desc");
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="plugin-sort-control">
|
|
<v-select
|
|
:model-value="modelValue"
|
|
:items="items"
|
|
density="compact"
|
|
variant="outlined"
|
|
hide-details
|
|
:label="label"
|
|
class="plugin-sort-control__select"
|
|
@update:model-value="updateSortBy"
|
|
>
|
|
<template #prepend-inner>
|
|
<v-icon size="small">mdi-sort</v-icon>
|
|
</template>
|
|
</v-select>
|
|
|
|
<v-btn
|
|
v-if="showOrder"
|
|
icon
|
|
variant="text"
|
|
density="compact"
|
|
@click="toggleOrder"
|
|
>
|
|
<v-icon>{{
|
|
order === "desc" ? "mdi-arrow-down-thin" : "mdi-arrow-up-thin"
|
|
}}</v-icon>
|
|
<v-tooltip activator="parent" location="top">
|
|
{{ order === "desc" ? descendingLabel : ascendingLabel }}
|
|
</v-tooltip>
|
|
</v-btn>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.plugin-sort-control {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.plugin-sort-control__select {
|
|
min-width: 180px;
|
|
max-width: 220px;
|
|
}
|
|
|
|
.plugin-sort-control__select :deep(.v-field__input),
|
|
.plugin-sort-control__select :deep(.v-field-label),
|
|
.plugin-sort-control__select :deep(.v-select__selection-text),
|
|
.plugin-sort-control__select :deep(.v-field__prepend-inner) {
|
|
font-size: 0.875rem;
|
|
}
|
|
</style>
|