chore(frontend): introduce prettier + eslint and prune unused code
- Add Prettier 3 with prettier-plugin-organize-imports (sorts/removes unused imports) - Add ESLint 10 flat config with typescript-eslint + eslint-plugin-vue + eslint-config-prettier - Add root scripts: format, format:check, lint, lint:fix - Reformat 257 files across admin-web, ops-web, desktop-client, packages - Remove unused locals/exports flagged by --noUnusedLocals/--noUnusedParameters - Fix duplicate localTabLabel key, surrogate-pair regex u-flag, NBSP literal in regex - Skip server/ (Go) and apps/browser-extension/ (deprecated per ADR) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,81 +1,89 @@
|
||||
<script setup lang="ts">
|
||||
import { SearchOutlined } from "@ant-design/icons-vue";
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { imagesApi } from "@/lib/api";
|
||||
import { formatBytes, formatPercentage } from "@/lib/display";
|
||||
import type { ImageAssetItem } from "@geo/shared-types";
|
||||
import { imagesApi } from '@/lib/api'
|
||||
import { formatBytes, formatPercentage } from '@/lib/display'
|
||||
import type { ImageAssetItem } from '@geo/shared-types'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
singleSelect?: boolean;
|
||||
}>(), {
|
||||
singleSelect: true,
|
||||
});
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
singleSelect?: boolean
|
||||
}>(),
|
||||
{
|
||||
singleSelect: true,
|
||||
},
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
select: [image: ImageAssetItem];
|
||||
}>();
|
||||
select: [image: ImageAssetItem]
|
||||
}>()
|
||||
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n()
|
||||
|
||||
// State
|
||||
const selectedFolderId = ref<number | null>(null);
|
||||
const searchKeyword = ref("");
|
||||
const debouncedSearch = ref("");
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(12);
|
||||
const selectedId = ref<number | null>(null);
|
||||
const selectedFolderId = ref<number | null>(null)
|
||||
const searchKeyword = ref('')
|
||||
const debouncedSearch = ref('')
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(12)
|
||||
const selectedId = ref<number | null>(null)
|
||||
|
||||
// Queries
|
||||
const foldersQuery = useQuery({
|
||||
queryKey: ["images", "folders"],
|
||||
queryKey: ['images', 'folders'],
|
||||
queryFn: () => imagesApi.listFolders(),
|
||||
});
|
||||
})
|
||||
|
||||
const imagesQuery = useQuery({
|
||||
queryKey: computed(() => ["images", "list", "picker", {
|
||||
folder_id: selectedFolderId.value,
|
||||
q: debouncedSearch.value,
|
||||
page: currentPage.value,
|
||||
page_size: pageSize.value,
|
||||
}]),
|
||||
queryFn: () => imagesApi.list({
|
||||
folder_id: selectedFolderId.value ?? undefined,
|
||||
q: debouncedSearch.value,
|
||||
page: currentPage.value,
|
||||
page_size: pageSize.value,
|
||||
}),
|
||||
});
|
||||
queryKey: computed(() => [
|
||||
'images',
|
||||
'list',
|
||||
'picker',
|
||||
{
|
||||
folder_id: selectedFolderId.value,
|
||||
q: debouncedSearch.value,
|
||||
page: currentPage.value,
|
||||
page_size: pageSize.value,
|
||||
},
|
||||
]),
|
||||
queryFn: () =>
|
||||
imagesApi.list({
|
||||
folder_id: selectedFolderId.value ?? undefined,
|
||||
q: debouncedSearch.value,
|
||||
page: currentPage.value,
|
||||
page_size: pageSize.value,
|
||||
}),
|
||||
})
|
||||
|
||||
const storageQuery = useQuery({
|
||||
queryKey: ["images", "storage-usage", "picker"],
|
||||
queryKey: ['images', 'storage-usage', 'picker'],
|
||||
queryFn: () => imagesApi.storageUsage(),
|
||||
});
|
||||
})
|
||||
|
||||
// Computed
|
||||
const folders = computed(() => foldersQuery.data.value ?? []);
|
||||
const images = computed(() => imagesQuery.data.value?.items ?? []);
|
||||
const totalImages = computed(() => imagesQuery.data.value?.total ?? 0);
|
||||
const storageUsage = computed(() => storageQuery.data.value);
|
||||
const folders = computed(() => foldersQuery.data.value ?? [])
|
||||
const images = computed(() => imagesQuery.data.value?.items ?? [])
|
||||
const totalImages = computed(() => imagesQuery.data.value?.total ?? 0)
|
||||
const storageUsage = computed(() => storageQuery.data.value)
|
||||
|
||||
// Actions
|
||||
let searchTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let searchTimer: ReturnType<typeof setTimeout> | null = null
|
||||
watch(searchKeyword, () => {
|
||||
if (searchTimer) clearTimeout(searchTimer);
|
||||
if (searchTimer) clearTimeout(searchTimer)
|
||||
searchTimer = setTimeout(() => {
|
||||
debouncedSearch.value = searchKeyword.value;
|
||||
currentPage.value = 1;
|
||||
}, 300);
|
||||
});
|
||||
debouncedSearch.value = searchKeyword.value
|
||||
currentPage.value = 1
|
||||
}, 300)
|
||||
})
|
||||
|
||||
function handleSelect(image: ImageAssetItem) {
|
||||
selectedId.value = image.id;
|
||||
emit("select", image);
|
||||
selectedId.value = image.id
|
||||
emit('select', image)
|
||||
}
|
||||
|
||||
function getSelectPopupContainer(triggerNode: HTMLElement) {
|
||||
return triggerNode.parentElement ?? triggerNode;
|
||||
return triggerNode.parentElement ?? triggerNode
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -90,7 +98,7 @@ function getSelectPopupContainer(triggerNode: HTMLElement) {
|
||||
:get-popup-container="getSelectPopupContainer"
|
||||
@change="currentPage = 1"
|
||||
>
|
||||
<a-select-option :value="null">{{ t("images.allFolders") }}</a-select-option>
|
||||
<a-select-option :value="null">{{ t('images.allFolders') }}</a-select-option>
|
||||
<a-select-option v-for="f in folders" :key="f.id" :value="f.id">
|
||||
{{ f.name }}
|
||||
</a-select-option>
|
||||
@@ -136,15 +144,20 @@ function getSelectPopupContainer(triggerNode: HTMLElement) {
|
||||
|
||||
<div v-if="storageUsage" class="storage-usage">
|
||||
<div class="storage-usage__info">
|
||||
<span>{{ t("images.storageUsage") }}: {{ t("images.storageUsageDetail", {
|
||||
used: formatBytes(storageUsage.used_bytes),
|
||||
total: formatBytes(storageUsage.quota_bytes),
|
||||
percent: formatPercentage(storageUsage.used_pct)
|
||||
}) }}</span>
|
||||
<span>
|
||||
{{ t('images.storageUsage') }}:
|
||||
{{
|
||||
t('images.storageUsageDetail', {
|
||||
used: formatBytes(storageUsage.used_bytes),
|
||||
total: formatBytes(storageUsage.quota_bytes),
|
||||
percent: formatPercentage(storageUsage.used_pct),
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
<a-progress
|
||||
:percent="storageUsage.used_pct"
|
||||
:show-info="false"
|
||||
<a-progress
|
||||
:percent="storageUsage.used_pct"
|
||||
:show-info="false"
|
||||
stroke-color="#1677ff"
|
||||
size="small"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user