mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-05-06 20:42:57 +08:00
✨ Feature(custom): allow user to add up to 6 favorite picbes in upload page, and quick select
This commit is contained in:
@@ -958,6 +958,7 @@
|
||||
"updateReady": "Update Ready to Install"
|
||||
},
|
||||
"upload": {
|
||||
"addToFavorites": "Add current picbed to quick switch",
|
||||
"changePicBed": "Change PicBed",
|
||||
"clickToUpload": "Click to Upload",
|
||||
"clipboardPicture": "Clipboard",
|
||||
@@ -970,9 +971,13 @@
|
||||
"inputValidUrl": "Please enter a valid URL",
|
||||
"invalidUrlsFound": "Invalid URLs found: {urls}",
|
||||
"linkFormat": "Link Format",
|
||||
"longPressToRemoveFromFavorites": "Long press to remove",
|
||||
"multipleUrlsHint": "Each URL on a separate line for multiple uploads",
|
||||
"outputFormat": "Output Format",
|
||||
"picbedAddedToFavorites": "Added to quick switch",
|
||||
"picbedSwitched": "Switched to {name}",
|
||||
"quickUpload": "Quick Upload",
|
||||
"removeFromFavorites": "Remove from quick switch",
|
||||
"taskQueue": {
|
||||
"addFiles": "Add Files",
|
||||
"allCancelled": "All tasks cancelled",
|
||||
|
||||
@@ -953,6 +953,7 @@
|
||||
"updateReady": "更新已准备好"
|
||||
},
|
||||
"upload": {
|
||||
"addToFavorites": "添加当前图床到快速切换",
|
||||
"changePicBed": "切换图床",
|
||||
"clickToUpload": "点击上传",
|
||||
"clipboardPicture": "剪贴板图片",
|
||||
@@ -965,9 +966,13 @@
|
||||
"inputValidUrl": "请输入合法的URL",
|
||||
"invalidUrlsFound": "发现无效的URL: {urls}",
|
||||
"linkFormat": "链接格式",
|
||||
"longPressToRemoveFromFavorites": "长按移除",
|
||||
"multipleUrlsHint": "多个URL请分行输入",
|
||||
"outputFormat": "输出格式",
|
||||
"picbedAddedToFavorites": "已添加到快速切换",
|
||||
"picbedSwitched": "已切换到 {name}",
|
||||
"quickUpload": "快捷上传",
|
||||
"removeFromFavorites": "从快速切换中移除",
|
||||
"taskQueue": {
|
||||
"addFiles": "添加文件",
|
||||
"allCancelled": "所有任务已取消",
|
||||
|
||||
@@ -953,6 +953,7 @@
|
||||
"updateReady": "更新已準備好"
|
||||
},
|
||||
"upload": {
|
||||
"addToFavorites": "添加當前圖床到快速切換",
|
||||
"changePicBed": "切換圖床",
|
||||
"clickToUpload": "點擊上傳",
|
||||
"clipboardPicture": "剪貼板圖片",
|
||||
@@ -965,9 +966,13 @@
|
||||
"inputValidUrl": "請輸入合法的URL",
|
||||
"invalidUrlsFound": "發現無效的URL: {urls}",
|
||||
"linkFormat": "連結格式",
|
||||
"longPressToRemoveFromFavorites": "長按移除",
|
||||
"multipleUrlsHint": "多個URL請分行輸入",
|
||||
"outputFormat": "輸出格式",
|
||||
"picbedAddedToFavorites": "已添加到快速切換",
|
||||
"picbedSwitched": "已切換到 {name}",
|
||||
"quickUpload": "快捷上傳",
|
||||
"removeFromFavorites": "從快速切換中移除",
|
||||
"taskQueue": {
|
||||
"addFiles": "添加文件",
|
||||
"allCancelled": "所有任務已取消",
|
||||
|
||||
@@ -15,6 +15,45 @@
|
||||
</div>
|
||||
<EditIcon :size="16" class="provider-arrow" />
|
||||
</button>
|
||||
<div
|
||||
class="add-favorite-button"
|
||||
:title="t('pages.upload.addToFavorites')"
|
||||
:class="{ disabled: favoritePicbeds.length >= MAX_FAVORITE_PICBEDS || isCurrentPicBedInFavorites }"
|
||||
@click="addCurrentPicbedToFavorites"
|
||||
>
|
||||
<PlusIcon :size="12" />
|
||||
</div>
|
||||
<transition-group
|
||||
name="badges-slide"
|
||||
tag="div"
|
||||
class="favorite-picbeds-container"
|
||||
:class="{ 'has-many': favoritePicbeds.length >= 4 }"
|
||||
>
|
||||
<button
|
||||
v-for="picbedType in favoritePicbeds"
|
||||
:key="picbedType.id"
|
||||
class="picbed-badge"
|
||||
:class="{ 'is-active': isCurrentPicbed(picbedType), 'show-delete': longPressedBadge === picbedType.id }"
|
||||
:title="t('pages.upload.longPressToRemoveFromFavorites') + getPicbedName(picbedType)"
|
||||
@click="handleBadgeClick(picbedType)"
|
||||
@mousedown="handleBadgeMouseDown(picbedType)"
|
||||
@mouseup="handleBadgeMouseUp"
|
||||
@mouseleave="handleBadgeMouseUp"
|
||||
@touchstart="handleBadgeTouchStart(picbedType, $event)"
|
||||
@touchend="handleBadgeTouchEnd"
|
||||
@touchcancel="handleBadgeTouchEnd"
|
||||
>
|
||||
<span class="badge-name">{{ getAbbreviatedName(picbedType) }}</span>
|
||||
<button
|
||||
v-if="longPressedBadge === picbedType.id"
|
||||
class="badge-remove"
|
||||
:title="t('pages.upload.removeFromFavorites')"
|
||||
@click.stop="removePicbedFromFavorites(picbedType)"
|
||||
>
|
||||
<XIcon :size="12" />
|
||||
</button>
|
||||
</button>
|
||||
</transition-group>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<div class="segmented-button-group">
|
||||
@@ -623,6 +662,16 @@ const PicBedId = ref('')
|
||||
const fileInput = useTemplateRef('fileInput')
|
||||
const uploadInterval = ref(1000)
|
||||
|
||||
const favoritePicbeds = useStorage<IFavoritePicbedItem[]>('favorite-picbeds', [])
|
||||
const MAX_FAVORITE_PICBEDS = 6
|
||||
const longPressedBadge = ref<string | null>(null)
|
||||
let longPressTimer: NodeJS.Timeout | null = null
|
||||
const LONG_PRESS_DURATION = 500
|
||||
const isCurrentPicBedInFavorites = computed(() => {
|
||||
const result = favoritePicbeds.value.some(item => item.id === defaultIdG.value)
|
||||
return result
|
||||
})
|
||||
|
||||
// New task queue settings
|
||||
const showTaskSettings = useStorage('upload-task-queue-show-settings', true)
|
||||
const taskSearchQuery = ref('')
|
||||
@@ -833,6 +882,135 @@ function updateUrlType(shortUrl: boolean) {
|
||||
})
|
||||
}
|
||||
|
||||
async function valideFavoritePicbeds() {
|
||||
if (!favoritePicbeds.value.length) return
|
||||
const allUploaders = (await getConfig<IStringKeyMap>(configPaths.uploader)) || {}
|
||||
|
||||
const availableFavorites = favoritePicbeds.value.filter(item => {
|
||||
return (
|
||||
Object.keys(allUploaders).includes(item.type) &&
|
||||
allUploaders[item.type]?.configList.some((cfg: any) => cfg._id === item.id && cfg._configName === item.configName)
|
||||
)
|
||||
})
|
||||
if (JSON.stringify(availableFavorites) !== JSON.stringify(favoritePicbeds.value)) {
|
||||
favoritePicbeds.value = availableFavorites
|
||||
}
|
||||
}
|
||||
|
||||
watch(favoritePicbeds, valideFavoritePicbeds, { immediate: true })
|
||||
|
||||
function addCurrentPicbedToFavorites() {
|
||||
favoritePicbeds.value.push({
|
||||
id: defaultIdG.value,
|
||||
type: defaultPicBedG.value,
|
||||
configName: defaultConfigNameG.value,
|
||||
})
|
||||
message.success(t('pages.upload.picbedAddedToFavorites'))
|
||||
}
|
||||
|
||||
function removePicbedFromFavorites(picbedType: IFavoritePicbedItem) {
|
||||
const index = favoritePicbeds.value.findIndex(
|
||||
item => item.type === picbedType.type && item.id === picbedType.id && item.configName === picbedType.configName,
|
||||
)
|
||||
if (index === -1) return
|
||||
favoritePicbeds.value.splice(index, 1)
|
||||
}
|
||||
|
||||
async function switchToPicbed(picbedType: IFavoritePicbedItem) {
|
||||
if (!picbedType.id || !picbedType.type || !picbedType.configName) {
|
||||
return
|
||||
}
|
||||
const uploaders = (await getConfig<IStringKeyMap>(`uploader.${picbedType.type}`)) || {}
|
||||
const targetConfig = uploaders?.configList.find(
|
||||
(cfg: any) => cfg._id === picbedType.id && cfg._configName === picbedType.configName,
|
||||
)
|
||||
if (!targetConfig) {
|
||||
return
|
||||
}
|
||||
saveConfig({
|
||||
[`uploader.${picbedType.type}.defaultId`]: picbedType.id,
|
||||
[`picBed.${picbedType.type}`]: targetConfig,
|
||||
[configPaths.picBed.current]: picbedType.type,
|
||||
[configPaths.picBed.uploader]: picbedType.type,
|
||||
})
|
||||
await updatePicBeds()
|
||||
const name = getPicbedName(picbedType).split('-')[0]
|
||||
window.electron.sendRPC(IRPCActionType.TRAY_SET_TOOL_TIP, `${name} ${targetConfig._configName}`)
|
||||
message.success(t('pages.upload.picbedSwitched', { name: getPicbedName(picbedType) }))
|
||||
}
|
||||
|
||||
function getPicbedName(picbedType: IFavoritePicbedItem): string {
|
||||
if (!picBedG.value || picBedG.value.length === 0) {
|
||||
return picbedType.configName
|
||||
}
|
||||
const target = picBedG.value.find(item => item.type === picbedType.type)
|
||||
return `${target ? target.name : picbedType.type}-${picbedType.configName}`
|
||||
}
|
||||
|
||||
const truncatePart = (part: string): string => {
|
||||
let partCount = 0
|
||||
let res = ''
|
||||
for (const char of part) {
|
||||
const isDoubleByte = char.charCodeAt(0) > 127
|
||||
const nextCount = partCount + (isDoubleByte ? 2 : 1)
|
||||
if (nextCount > 4) {
|
||||
return res
|
||||
}
|
||||
res += char
|
||||
partCount = nextCount
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function getAbbreviatedName(picbedType: IFavoritePicbedItem): string {
|
||||
const name = getPicbedName(picbedType)
|
||||
return name.split('-').map(truncatePart).join('-')
|
||||
}
|
||||
|
||||
function isCurrentPicbed(picbedType: IFavoritePicbedItem): boolean {
|
||||
return defaultIdG.value === picbedType.id
|
||||
}
|
||||
|
||||
function handleBadgeClick(picbedType: IFavoritePicbedItem) {
|
||||
if (longPressedBadge.value === picbedType.id) {
|
||||
return
|
||||
}
|
||||
switchToPicbed(picbedType)
|
||||
}
|
||||
|
||||
function handleBadgeMouseDown(picbedType: IFavoritePicbedItem) {
|
||||
longPressTimer = setTimeout(() => {
|
||||
longPressedBadge.value = picbedType.id
|
||||
}, LONG_PRESS_DURATION)
|
||||
}
|
||||
|
||||
function handleBadgeMouseUp() {
|
||||
if (longPressTimer) {
|
||||
clearTimeout(longPressTimer)
|
||||
longPressTimer = null
|
||||
}
|
||||
setTimeout(() => {
|
||||
longPressedBadge.value = null
|
||||
}, 10000)
|
||||
}
|
||||
|
||||
function handleBadgeTouchStart(picbedType: IFavoritePicbedItem, event: TouchEvent) {
|
||||
longPressTimer = setTimeout(() => {
|
||||
longPressedBadge.value = picbedType.id
|
||||
event.preventDefault()
|
||||
}, LONG_PRESS_DURATION)
|
||||
}
|
||||
|
||||
function handleBadgeTouchEnd() {
|
||||
if (longPressTimer) {
|
||||
clearTimeout(longPressTimer)
|
||||
longPressTimer = null
|
||||
}
|
||||
setTimeout(() => {
|
||||
longPressedBadge.value = null
|
||||
}, 10000)
|
||||
}
|
||||
|
||||
function uploadClipboardFiles() {
|
||||
window.electron.sendRPC(IRPCActionType.UPLOAD_CLIPBOARD_FILES_FROM_UPLOAD_PAGE)
|
||||
}
|
||||
@@ -1072,14 +1250,12 @@ onBeforeUnmount(() => {
|
||||
removeTaskQueueUpdateListenerCallback()
|
||||
})
|
||||
|
||||
onBeforeMount(() => {
|
||||
onBeforeMount(async () => {
|
||||
removeUploadProgressListenerCallback = window.electron.ipcRendererOn('uploadProgress', uploadProgressHandler)
|
||||
removeSyncPicBedListenerCallback = window.electron.ipcRendererOn('syncPicBed', syncPicBedHandler)
|
||||
removeTaskQueueUpdateListenerCallback = window.electron.ipcRendererOn('uploadTaskQueueUpdate', taskQueueUpdateHandler)
|
||||
$bus.on(SHOW_INPUT_BOX_RESPONSE, handleInputBoxValue)
|
||||
getUseShortUrl()
|
||||
getPasteStyle()
|
||||
refreshTaskStatus()
|
||||
await Promise.all([getUseShortUrl(), getPasteStyle(), refreshTaskStatus()])
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -54,6 +54,11 @@ html, body {
|
||||
|
||||
.provider-section {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
max-width: calc(100% - 300px); /* Leave space for header-actions */
|
||||
}
|
||||
|
||||
.provider-button {
|
||||
@@ -79,6 +84,159 @@ html, body {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.add-favorite-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
font-family: inherit;
|
||||
color: var(--color-text-secondary);
|
||||
background: var(--color-surface-elevated);
|
||||
transition: var(--transition-fast);
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.add-favorite-button.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.add-favorite-button:hover:not(:disabled) {
|
||||
border-color: var(--color-accent);
|
||||
color: var(--color-accent);
|
||||
background: var(--color-surface);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.add-favorite-button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.favorite-picbeds-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.2rem;
|
||||
flex-wrap: wrap;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.favorite-picbeds-container .picbed-badge {
|
||||
width: 85px;
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
|
||||
.favorite-picbeds-container.has-many {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.picbed-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 0.375rem 0.5rem 0.375rem 0.75rem;
|
||||
width: 85px;
|
||||
font-family: inherit;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-secondary);
|
||||
background: var(--color-surface-elevated);
|
||||
transition: all var(--transition-fast);
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.picbed-badge:hover {
|
||||
border-color: var(--color-accent);
|
||||
color: var(--color-accent);
|
||||
background: var(--color-surface);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.picbed-badge.is-active {
|
||||
border-color: var(--color-accent);
|
||||
color: var(--color-accent);
|
||||
background: var(--color-accent-soft, rgb(59 130 246 / 10%));
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.picbed-badge.show-delete {
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.badge-name {
|
||||
line-height: 1;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.badge-remove {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
padding: 0.125rem;
|
||||
color: inherit;
|
||||
background: transparent;
|
||||
transition: var(--transition-fast);
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
animation: fade-in 0.2s ease-in;
|
||||
}
|
||||
|
||||
.badge-remove:hover {
|
||||
color: var(--color-error, #ef4444);
|
||||
background: var(--color-error-soft, rgb(239 68 68 / 10%));
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Badges slide animation */
|
||||
.badges-slide-enter-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.badges-slide-leave-active {
|
||||
transition: all 0.3s ease;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.badges-slide-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateX(-10px) scale(0.9);
|
||||
}
|
||||
|
||||
.badges-slide-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(10px) scale(0.9);
|
||||
}
|
||||
|
||||
.badges-slide-move {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.provider-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
6
src/universal/types/types.d.ts
vendored
6
src/universal/types/types.d.ts
vendored
@@ -521,3 +521,9 @@ interface IBuildInListItem {
|
||||
// settings.rename
|
||||
manualRename?: boolean
|
||||
}
|
||||
|
||||
interface IFavoritePicbedItem {
|
||||
id: string
|
||||
type: string
|
||||
configName: string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user