Skip to content

Commit 28bfd45

Browse files
committed
feat: 支持配置图标填充模式; 新增非官方图标集 来自 theSVG https://github.com/xream/unofficial-thesvg-iconset
1 parent 71ae848 commit 28bfd45

25 files changed

Lines changed: 463 additions & 23 deletions

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sub-store-front-end",
3-
"version": "2.27.3",
3+
"version": "2.28.0",
44
"private": true,
55
"packageManager": "pnpm@11.0.9",
66
"scripts": {

src/assets/styles/overwritten_css_var.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,16 @@ img.auto-reverse {
218218
}
219219
}
220220
}
221+
222+
.image-fit-tips-dialog {
223+
> .nut-dialog {
224+
> .nut-dialog__content {
225+
color: var(--primary-text-color);
226+
line-height: 1.5;
227+
text-align: left !important;
228+
}
229+
}
230+
}
221231
// nut-popup round popup-bottom nut-cascader__popup
222232
// form
223233
.nut-cascader__popup,

src/components/ArchiveListItem.vue

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
:style="{
1919
opacity: isIconColor ? 1 : 0.8,
2020
filter: isIconColor ? 'none' : 'brightness(var(--img-brightness))',
21+
objectFit: itemIconFit,
2122
}"
2223
>
2324
</div>
@@ -119,6 +120,7 @@ import { useSettingsStore } from '@/store/settings';
119120
import { useSubsStore } from '@/store/subs';
120121
import { resolveArtifactIcon } from '@/utils/artifactIcon';
121122
import { createGithubProxyUrlRewriter } from '@/utils/githubProxy';
123+
import { resolveImageFit } from '@/utils/iconFit';
122124
import { isShareType, resolveShareDisplayIconState } from '@/utils/share';
123125
import {
124126
formatArchiveTime,
@@ -296,6 +298,13 @@ const isIconColor = computed(() => {
296298
297299
return props.data?.snapshot?.isIconColor !== false;
298300
});
301+
const itemIconFit = computed(() => {
302+
if (itemType.value === 'share') {
303+
return resolveImageFit(shareIconState.value.iconFit, appearanceSetting.value.iconFit);
304+
}
305+
306+
return resolveImageFit(props.data?.snapshot?.iconFit, appearanceSetting.value.iconFit);
307+
});
299308
300309
const detailParts = computed(() => {
301310
if (itemType.value === 'artifact') {
@@ -428,7 +437,7 @@ const nonSimpleSecondLine = computed(() => {
428437
img {
429438
width: 100%;
430439
height: 100%;
431-
object-fit: contain;
440+
object-fit: cover;
432441
border-radius: 10px;
433442
}
434443
}

src/components/ArtifactPanel.vue

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
<nut-switch v-model="isIconColor" />
3636
</div>
3737
</nut-form-item>
38+
<ImageFitPicker v-model="editPanelData.iconFit" :fallback-value="appearanceSetting.iconFit" />
3839
<nut-form-item
3940
:label="$t(`syncPage.addArtForm.name.label`)"
4041
prop="name"
@@ -158,13 +159,19 @@
158159

159160
<script lang="ts" setup>
160161
import { useArtifactsStore } from '@/store/artifacts';
162+
import { useSettingsStore } from '@/store/settings';
161163
import { useSubsStore } from '@/store/subs';
164+
import ImageFitPicker from '@/components/ImageFitPicker.vue';
162165
import IconPopup from '@/views/icon/IconPopup.vue';
166+
import { normalizeOptionalImageFit } from '@/utils/iconFit';
163167
import { Dialog, Toast } from '@nutui/nutui';
168+
import { storeToRefs } from 'pinia';
164169
import { computed, ref, toRaw, watchEffect } from 'vue';
165170
import { useI18n } from 'vue-i18n';
166171
const { t } = useI18n();
167172
const artifactsStore = useArtifactsStore();
173+
const settingsStore = useSettingsStore();
174+
const { appearanceSetting } = storeToRefs(settingsStore);
168175
const isInit = ref(false);
169176
const isEditMode = ref(false);
170177
const ruleForm = ref();
@@ -192,6 +199,7 @@
192199
displayName: '',
193200
icon: '',
194201
isIconColor: true,
202+
iconFit: undefined,
195203
source: '',
196204
type: 'file',
197205
platform: 'Stash',
@@ -303,7 +311,15 @@
303311
return;
304312
}
305313
306-
const data = toRaw(editPanelData.value);
314+
const data = { ...toRaw(editPanelData.value) };
315+
const iconFit = normalizeOptionalImageFit(editPanelData.value.iconFit);
316+
if (iconFit) {
317+
data.iconFit = iconFit;
318+
} else if (isEditMode.value) {
319+
data.iconFit = null;
320+
} else {
321+
delete data.iconFit;
322+
}
307323
Toast.loading(t('syncPage.addArtForm.submitLoading'), {
308324
cover: true,
309325
id: 'add-artifact-loading',
@@ -370,6 +386,7 @@
370386
if (!isInit.value && name) {
371387
const artifact = artifactsStore.artifacts.find(art => art.name === name);
372388
editPanelData.value = JSON.parse(JSON.stringify(toRaw(artifact)));
389+
editPanelData.value.iconFit = normalizeOptionalImageFit(editPanelData.value.iconFit);
373390
sourceModel.value.push(
374391
editPanelData.value.type,
375392
editPanelData.value.source

src/components/ArtifactsListItem.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<div
1111
class="sub-item-wrapper"
1212
:class="{ 'is-dual-column': props.isDualColumn }"
13-
:style="{ padding: itemPadding }"
13+
:style="{ padding: itemPadding, '--icon-fit': iconFit }"
1414
@click="handleContentClick"
1515
>
1616
<div v-if="appearanceSetting.isShowIcon" class="sub-img-wrappers" @click.stop="openUrl">
@@ -212,6 +212,7 @@ import { useSubsStore } from "@/store/subs";
212212
import { butifyDate } from "@/utils/butifyDate";
213213
import { resolveArtifactIcon } from "@/utils/artifactIcon";
214214
import { createGithubProxyUrlRewriter } from "@/utils/githubProxy";
215+
import { resolveImageFit } from "@/utils/iconFit";
215216
import { isMobile } from "@/utils/isMobile";
216217
import { Dialog, Toast } from "@nutui/nutui";
217218
import { useClipboard } from "@vueuse/core";
@@ -313,6 +314,9 @@ const sourceSub = computed(() => {
313314
const isIconColor = computed(() => {
314315
return artifact.value.isIconColor !== false;
315316
});
317+
const iconFit = computed(() => {
318+
return resolveImageFit(artifact.value?.iconFit, appearanceSetting.value.iconFit);
319+
});
316320
317321
const icon = computed(() => {
318322
return resolveArtifactIcon({
@@ -652,7 +656,7 @@ watch(isSyncOpen, async () => {
652656
border-radius: 12px;
653657
654658
img {
655-
object-fit: contain;
659+
object-fit: var(--icon-fit, cover);
656660
border-radius: 10px;
657661
}
658662
}

src/components/FileListItem.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
<div
1212
class="sub-item-wrapper"
1313
:class="{ 'is-dual-column': props.isDualColumn }"
14-
:style="{ padding: itemPadding }"
14+
:style="{ padding: itemPadding, '--icon-fit': iconFit }"
1515
@click="handleContentClick"
1616
>
1717
<div
@@ -255,6 +255,7 @@
255255
import { useSubsStore } from '@/store/subs';
256256
import { getString } from '@/utils/flowTransfer';
257257
import { createGithubProxyUrlRewriter } from '@/utils/githubProxy';
258+
import { resolveImageFit } from '@/utils/iconFit';
258259
import { isMobile } from '@/utils/isMobile';
259260
import { openManagedDeleteDialog } from '@/utils/archive';
260261
import FilePreview from '@/views/FilePreview.vue';
@@ -373,6 +374,9 @@
373374
const isIconColor = computed(() => {
374375
return props.file.isIconColor !== false;
375376
});
377+
const iconFit = computed(() => {
378+
return resolveImageFit(props.file.iconFit, appearanceSetting.value.iconFit);
379+
});
376380
377381
const collectionDetail = computed(() => {
378382
const nameList = props?.collection.subscriptions || [];
@@ -673,7 +677,7 @@
673677
border-radius: 12px;
674678
675679
img {
676-
object-fit: contain;
680+
object-fit: var(--icon-fit, cover);
677681
border-radius: 10px;
678682
}
679683
}

src/components/ImageFitPicker.vue

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<template>
2+
<nut-form-item :prop="prop">
3+
<template #label>
4+
<span class="image-fit-label">
5+
{{ labelText }}
6+
<nut-icon
7+
name="tips"
8+
size="14"
9+
role="button"
10+
:aria-label="$t(`imageFit.tips.title`)"
11+
@click.stop="openTips"
12+
/>
13+
</span>
14+
</template>
15+
<nut-input
16+
:model-value="currentText"
17+
class="nut-input-text"
18+
:border="false"
19+
input-align="right"
20+
readonly
21+
right-icon="rect-right"
22+
@click="openPicker"
23+
@click-right-icon="openPicker"
24+
/>
25+
</nut-form-item>
26+
<DesktopPicker
27+
v-model="pickerValue"
28+
v-model:visible="showPicker"
29+
:columns="columns"
30+
:title="labelText"
31+
:cancel-text="$t(`editorPage.subConfig.sourceNamePicker.cancel`)"
32+
:ok-text="$t(`editorPage.subConfig.sourceNamePicker.confirm`)"
33+
@confirm="confirm"
34+
/>
35+
</template>
36+
37+
<script setup lang="ts">
38+
import { Dialog } from "@nutui/nutui";
39+
import { computed, ref, watch } from "vue";
40+
import { useI18n } from "vue-i18n";
41+
42+
import DesktopPicker from "@/components/DesktopPicker.vue";
43+
import { IMAGE_FIT_OPTIONS, isImageFit, type ImageFit } from "@/utils/iconFit";
44+
45+
const INHERIT_IMAGE_FIT = "__inherit__";
46+
type ImageFitPickerValue = ImageFit | typeof INHERIT_IMAGE_FIT;
47+
48+
const props = withDefaults(
49+
defineProps<{
50+
modelValue?: ImageFit | string | null;
51+
fallbackValue?: ImageFit | string | null;
52+
label?: string;
53+
prop?: string;
54+
}>(),
55+
{
56+
label: "",
57+
prop: "iconFit",
58+
},
59+
);
60+
61+
const emit = defineEmits<{
62+
(event: "update:modelValue", value: ImageFit | undefined): void;
63+
(event: "change", value: ImageFit | undefined): void;
64+
}>();
65+
66+
const { t } = useI18n();
67+
const showPicker = ref(false);
68+
const labelText = computed(() => props.label || t("editorPage.subConfig.basic.iconFit.label"));
69+
const currentPickerValue = computed<ImageFitPickerValue>(() => {
70+
return isImageFit(props.modelValue) ? props.modelValue : INHERIT_IMAGE_FIT;
71+
});
72+
const currentText = computed(() => {
73+
return currentPickerValue.value === INHERIT_IMAGE_FIT
74+
? t("imageFit.inherit")
75+
: t(`imageFit.${currentPickerValue.value}`);
76+
});
77+
const columns = computed(() => {
78+
return [
79+
{
80+
text: t("imageFit.inherit"),
81+
value: INHERIT_IMAGE_FIT,
82+
},
83+
...IMAGE_FIT_OPTIONS.map((value) => ({
84+
text: t(`imageFit.${value}`),
85+
value,
86+
})),
87+
];
88+
});
89+
const pickerValue = ref<ImageFitPickerValue[]>([currentPickerValue.value]);
90+
watch(currentPickerValue, (value) => {
91+
if (!showPicker.value) pickerValue.value = [value];
92+
});
93+
watch(showPicker, (visible) => {
94+
if (visible) pickerValue.value = [currentPickerValue.value];
95+
});
96+
const openPicker = () => {
97+
pickerValue.value = [currentPickerValue.value];
98+
showPicker.value = true;
99+
};
100+
const confirm = ({ selectedValue }: { selectedValue?: unknown[] }) => {
101+
const selected = selectedValue?.[0];
102+
const next = isImageFit(selected) ? selected : undefined;
103+
emit("update:modelValue", next);
104+
emit("change", next);
105+
};
106+
const openTips = () => {
107+
Dialog({
108+
title: t("imageFit.tips.title"),
109+
content: t("imageFit.tips.content"),
110+
popClass: "auto-dialog image-fit-tips-dialog",
111+
noCancelBtn: true,
112+
okText: t("imageFit.tips.close"),
113+
closeOnClickOverlay: true,
114+
closeOnPopstate: true,
115+
});
116+
};
117+
</script>
118+
119+
<style scoped lang="scss">
120+
.image-fit-label {
121+
display: inline-flex;
122+
align-items: center;
123+
gap: 4px;
124+
125+
:deep(.nut-icon-tips) {
126+
color: var(--second-text-color);
127+
}
128+
}
129+
</style>

src/components/ShareListItem.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<div
33
class="sub-item-wrapper"
44
:class="{ disabled: props.disabled, 'is-dual-column': props.isDualColumn }"
5-
:style="{ padding: itemPadding }"
5+
:style="{ padding: itemPadding, '--icon-fit': shareIconFit }"
66
data-testid="link-card"
77
@click="onClickPreviews"
88
>
@@ -130,6 +130,7 @@ import { useSettingsStore } from "@/store/settings";
130130
import { useSubsStore } from "@/store/subs";
131131
import { openManagedDeleteDialog } from "@/utils/archive";
132132
import { createGithubProxyUrlRewriter } from "@/utils/githubProxy";
133+
import { resolveImageFit } from "@/utils/iconFit";
133134
import {
134135
formatShareTimestamp,
135136
getShareEditPath,
@@ -275,6 +276,9 @@ const shareIcon = computed(() => {
275276
const isIconColor = computed(() => {
276277
return shareIconState.value.isIconColor;
277278
});
279+
const shareIconFit = computed(() => {
280+
return resolveImageFit(shareIconState.value.iconFit, appearanceSetting.value.iconFit);
281+
});
278282
279283
const onDeleteConfirm = async (mode: DeleteMode = "permanent") => {
280284
await subsStore.deleteShare(token.value, type.value, name.value, mode);
@@ -454,7 +458,7 @@ const onClickPreviews = () => {
454458
border-radius: 12px;
455459
456460
img {
457-
object-fit: contain;
461+
object-fit: var(--icon-fit, cover);
458462
border-radius: 10px;
459463
}
460464
}

src/components/SubListItem.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<div
1111
class="sub-item-wrapper"
1212
:class="{ 'is-dual-column': props.isDualColumn }"
13-
:style="{ padding: itemPadding }"
13+
:style="{ padding: itemPadding, '--icon-fit': iconFit }"
1414
@click="handleContentClick"
1515
>
1616
<div
@@ -342,6 +342,7 @@ import { useSettingsStore } from "@/store/settings";
342342
import { useSubsStore } from "@/store/subs";
343343
import { getString } from "@/utils/flowTransfer";
344344
import { createGithubProxyUrlRewriter } from "@/utils/githubProxy";
345+
import { resolveImageFit } from "@/utils/iconFit";
345346
import { isMobile } from "@/utils/isMobile";
346347
import { openManagedDeleteDialog } from "@/utils/archive";
347348
import CompareTable from "@/views/CompareTable.vue";
@@ -446,6 +447,9 @@ const rewriteGithubUrl = (url?: string | null) => {
446447
const isIconColor = computed(() => {
447448
return props[props.type].isIconColor !== false;
448449
});
450+
const iconFit = computed(() => {
451+
return resolveImageFit(props[props.type].iconFit, appearanceSetting.value.iconFit);
452+
});
449453
450454
const collectionDetail = computed(() => {
451455
const nameList = props?.collection.subscriptions || [];
@@ -1035,7 +1039,7 @@ const refreshSubFlowsIfNeeded = async () => {
10351039
border-radius: 12px;
10361040
10371041
img {
1038-
object-fit: contain;
1042+
object-fit: var(--icon-fit, cover);
10391043
border-radius: 10px;
10401044
}
10411045
}

0 commit comments

Comments
 (0)