Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion dashboard/src/i18n/locales/en-US/features/extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@
"updated": "Last Updated",
"updateStatus": "Update Status",
"ascending": "Ascending",
"descending": "Descending"
"descending": "Descending",
"pinUpdatesOnTop": "Pin Updates on Top"
},
"tags": {
"danger": "Danger"
Expand Down
5 changes: 3 additions & 2 deletions dashboard/src/i18n/locales/ru-RU/features/extension.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
{
"title": "Плагины",
"subtitle": "Управление и настройка расширений системы",
"tabs": {
Expand Down Expand Up @@ -143,7 +143,8 @@
"updated": "Дате обновления",
"updateStatus": "Статусу обновления",
"ascending": "По возрастанию",
"descending": "По убыванию"
"descending": "По убыванию",
"pinUpdatesOnTop": "Обновления сверху"
},
"tags": {
"danger": "Опасно"
Expand Down
3 changes: 2 additions & 1 deletion dashboard/src/i18n/locales/zh-CN/features/extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,8 @@
"updated": "更新时间",
"updateStatus": "更新状态",
"ascending": "升序",
"descending": "降序"
"descending": "降序",
"pinUpdatesOnTop": "有更新置顶"
},
"tags": {
"danger": "危险"
Expand Down
10 changes: 10 additions & 0 deletions dashboard/src/views/extension/InstalledPluginsTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const {
installedStatusFilter,
installedSortBy,
installedSortOrder,
pinUpdatesOnTop,
loading_,
currentPage,
dangerConfirmDialog,
Expand Down Expand Up @@ -354,6 +355,15 @@ const pinnedPlugins = computed(() => {
:show-order="installedSortUsesOrder"
@update:order="installedSortOrder = $event"
/>
<v-switch
v-model="pinUpdatesOnTop"
:label="tm('sort.pinUpdatesOnTop')"
color="primary"
density="compact"
hide-details
class="ml-4"
style="max-width: 200px"
/>
</div>
</div>
</v-col>
Expand Down
84 changes: 84 additions & 0 deletions dashboard/src/views/extension/extensionPreferenceStorage.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
export const SHOW_RESERVED_PLUGINS_STORAGE_KEY = "showReservedPlugins";
export const PLUGIN_LIST_VIEW_MODE_STORAGE_KEY = "pluginListViewMode";
export const PIN_UPDATES_ON_TOP_STORAGE_KEY = "pinUpdatesOnTop";

/**
* Resolve the storage backend for reading preferences.
* Pass `null` to explicitly disable storage access in callers/tests.
*/
const getStorageForRead = (storageOverride) => {
Comment thread
Blueteemo marked this conversation as resolved.
if (storageOverride === null) {
return null;
}
if (storageOverride !== undefined) {
return typeof storageOverride?.getItem === "function"
? storageOverride
: null;
}
if (typeof window === "undefined") {
return null;
}
try {
const localStorage = window.localStorage ?? null;
return typeof localStorage?.getItem === "function" ? localStorage : null;
} catch {
return null;
}
};

/**
* Resolve the storage backend for writing preferences.
* Pass `null` to explicitly disable storage access in callers/tests.
*/
const getStorageForWrite = (storageOverride) => {
if (storageOverride === null) {
return null;
}
if (storageOverride !== undefined) {
return typeof storageOverride?.setItem === "function"
? storageOverride
: null;
}
if (typeof window === "undefined") {
return null;
}
try {
const localStorage = window.localStorage ?? null;
return typeof localStorage?.setItem === "function" ? localStorage : null;
} catch {
return null;
}
};

export const readBooleanPreference = (key, fallback, storage) => {
const targetStorage = getStorageForRead(storage);
if (!targetStorage) {
return fallback;
}

try {
const saved = targetStorage.getItem(key);
if (saved === "true") {
return true;
}
if (saved === "false") {
return false;
}
return fallback;
} catch {
return fallback;
}
};

export const writeBooleanPreference = (key, value, storage) => {
const targetStorage = getStorageForWrite(storage);
if (!targetStorage) {
return;
}

try {
targetStorage.setItem(key, String(value));
} catch {
// Ignore restricted storage environments.
}
};
84 changes: 53 additions & 31 deletions dashboard/src/views/extension/useExtensionPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ import {
getValidHashTab,
replaceTabRoute,
} from "@/utils/hashRouteTabs.mjs";
import {
PIN_UPDATES_ON_TOP_STORAGE_KEY,
PLUGIN_LIST_VIEW_MODE_STORAGE_KEY,
SHOW_RESERVED_PLUGINS_STORAGE_KEY,
readBooleanPreference,
writeBooleanPreference,
} from "./extensionPreferenceStorage.mjs";
import { ref, computed, onMounted, onUnmounted, reactive, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useDisplay } from "vuetify";
Expand Down Expand Up @@ -124,11 +131,7 @@ export const useExtensionPage = () => {

// 从 localStorage 恢复显示系统插件的状态,默认为 false(隐藏)
const getInitialShowReserved = () => {
if (typeof window !== "undefined" && window.localStorage) {
const saved = localStorage.getItem("showReservedPlugins");
return saved === "true";
}
return false;
return readBooleanPreference(SHOW_RESERVED_PLUGINS_STORAGE_KEY, false);
};
const showReserved = ref(getInitialShowReserved());
const snack_message = ref("");
Expand Down Expand Up @@ -178,16 +181,20 @@ export const useExtensionPage = () => {
// 新增变量支持列表视图
// 从 localStorage 恢复显示模式,默认为 false(卡片视图)
const getInitialListViewMode = () => {
if (typeof window !== "undefined" && window.localStorage) {
return localStorage.getItem("pluginListViewMode") === "true";
}
return false;
return readBooleanPreference(PLUGIN_LIST_VIEW_MODE_STORAGE_KEY, false);
};
const isListView = ref(getInitialListViewMode());
const pluginSearch = ref("");
const installedStatusFilter = ref("all");
const installedSortBy = ref("default");
const installedSortOrder = ref("desc");
const getInitialPinUpdatesOnTop = () => {
return readBooleanPreference(PIN_UPDATES_ON_TOP_STORAGE_KEY, true);
};
const pinUpdatesOnTop = ref(getInitialPinUpdatesOnTop());
watch(pinUpdatesOnTop, (val) => {
writeBooleanPreference(PIN_UPDATES_ON_TOP_STORAGE_KEY, val);
});
const loading_ = ref(false);

// 分页相关
Expand Down Expand Up @@ -426,6 +433,17 @@ export const useExtensionPage = () => {
return Number.isFinite(parsed) ? parsed : null;
};

const compareInstalledFallback = (left, right) => {
const nameCompare = compareInstalledPluginNames(left.plugin, right.plugin);
return nameCompare !== 0 ? nameCompare : left.index - right.index;
};

const compareInstalledUpdatePinning = (left, right) => {
const leftHasUpdate = left.plugin?.has_update ? 1 : 0;
const rightHasUpdate = right.plugin?.has_update ? 1 : 0;
return rightHasUpdate - leftHasUpdate;
};

const sortInstalledPlugins = (plugins) => {
return plugins
.map((plugin, index) => ({
Expand All @@ -434,19 +452,24 @@ export const useExtensionPage = () => {
installedAtTimestamp: getInstalledAtTimestamp(plugin),
}))
.sort((left, right) => {
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
const fallbackNameCompare = compareInstalledPluginNames(
left.plugin,
right.plugin,
);
const fallbackResult =
fallbackNameCompare !== 0 ? fallbackNameCompare : left.index - right.index;
if (
pinUpdatesOnTop.value &&
installedSortBy.value !== "update_status"
) {
// Pinning updates is a primary grouping; the selected sort order still
// applies within the "has update" and "no update" groups below.
const pinCompare = compareInstalledUpdatePinning(left, right);
if (pinCompare !== 0) {
return pinCompare;
}
}

if (installedSortBy.value === "install_time") {
const leftTimestamp = left.installedAtTimestamp;
const rightTimestamp = right.installedAtTimestamp;

if (leftTimestamp == null && rightTimestamp == null) {
return fallbackResult;
return compareInstalledFallback(left, right);
}
if (leftTimestamp == null) {
return 1;
Expand All @@ -459,7 +482,9 @@ export const useExtensionPage = () => {
installedSortOrder.value === "desc"
? rightTimestamp - leftTimestamp
: leftTimestamp - rightTimestamp;
return timeDiff !== 0 ? timeDiff : fallbackResult;
return timeDiff !== 0
? timeDiff
: compareInstalledFallback(left, right);
}

if (installedSortBy.value === "name") {
Expand All @@ -469,7 +494,7 @@ export const useExtensionPage = () => {
? -nameCompare
: nameCompare;
}
return left.index - right.index;
return compareInstalledFallback(left, right);
}

if (installedSortBy.value === "author") {
Expand All @@ -482,20 +507,20 @@ export const useExtensionPage = () => {
? -authorCompare
: authorCompare;
}
return fallbackResult;
return compareInstalledFallback(left, right);
}

if (installedSortBy.value === "update_status") {
const leftHasUpdate = left.plugin?.has_update ? 1 : 0;
const rightHasUpdate = right.plugin?.has_update ? 1 : 0;
const updateDiff =
installedSortOrder.value === "desc"
? rightHasUpdate - leftHasUpdate
: leftHasUpdate - rightHasUpdate;
return updateDiff !== 0 ? updateDiff : fallbackResult;
? compareInstalledUpdatePinning(left, right)
: compareInstalledUpdatePinning(right, left);
return updateDiff !== 0
? updateDiff
: compareInstalledFallback(left, right);
}

return fallbackResult;
return compareInstalledFallback(left, right);
})
.map((item) => item.plugin);
};
Expand Down Expand Up @@ -636,9 +661,7 @@ export const useExtensionPage = () => {
const toggleShowReserved = () => {
showReserved.value = !showReserved.value;
// 保存到 localStorage
if (typeof window !== "undefined" && window.localStorage) {
localStorage.setItem("showReservedPlugins", showReserved.value.toString());
}
writeBooleanPreference(SHOW_RESERVED_PLUGINS_STORAGE_KEY, showReserved.value);
};

const toast = (message, success) => {
Expand Down Expand Up @@ -1603,9 +1626,7 @@ export const useExtensionPage = () => {

// 监听显示模式变化并保存到 localStorage
watch(isListView, (newVal) => {
if (typeof window !== "undefined" && window.localStorage) {
localStorage.setItem("pluginListViewMode", String(newVal));
}
writeBooleanPreference(PLUGIN_LIST_VIEW_MODE_STORAGE_KEY, newVal);
});

watch(
Expand Down Expand Up @@ -1695,6 +1716,7 @@ export const useExtensionPage = () => {
installedStatusFilter,
installedSortBy,
installedSortOrder,
pinUpdatesOnTop,
loading_,
currentPage,
marketCategoryFilter,
Expand Down
Loading
Loading