Feature(custom): rewrite tray page for macos

This commit is contained in:
Kuingsmile
2025-08-08 16:47:05 +08:00
parent fd9dac735a
commit 1442724fbe
7 changed files with 376 additions and 222 deletions

View File

@@ -3,7 +3,7 @@
<div class="title-bar"> <div class="title-bar">
<div class="app-title"> <div class="app-title">
<div class="app-text"> <div class="app-text">
{{ $t('app.title') }} {{ t('app.title') }}
</div> </div>
<div class="app-version"> <div class="app-version">
v{{ version }} v{{ version }}
@@ -40,7 +40,7 @@
<div class="nav-icon-container"> <div class="nav-icon-container">
<DatabaseIcon :size="18" /> <DatabaseIcon :size="18" />
</div> </div>
<span class="nav-label">{{ $t('navigation.picbed') }}</span> <span class="nav-label">{{ t('navigation.picbed') }}</span>
<ChevronDownIcon <ChevronDownIcon
:size="16" :size="16"
class="submenu-arrow" class="submenu-arrow"
@@ -62,7 +62,7 @@
<div class="sidebar-footer"> <div class="sidebar-footer">
<button <button
class="footer-button" class="footer-button"
:title="$t('navigation.moreOptions')" :title="t('navigation.moreOptions')"
@click="openMenu" @click="openMenu"
> >
<Info :size="20" /> <Info :size="20" />
@@ -103,12 +103,12 @@
> >
<DialogPanel class="dialog-panel"> <DialogPanel class="dialog-panel">
<DialogTitle class="dialog-title"> <DialogTitle class="dialog-title">
{{ $t('navigation.picBedQrCode') }} {{ t('navigation.picBedQrCode') }}
</DialogTitle> </DialogTitle>
<div class="dialog-content"> <div class="dialog-content">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('navigation.choosePicBed') }}</label> <label class="form-label">{{ t('navigation.choosePicBed') }}</label>
<Listbox <Listbox
v-model="choosedPicBedForQRCode" v-model="choosedPicBedForQRCode"
multiple multiple
@@ -119,13 +119,13 @@
v-if="choosedPicBedForQRCode.length === 0" v-if="choosedPicBedForQRCode.length === 0"
class="placeholder" class="placeholder"
> >
{{ $t('navigation.selectPicBeds') }} {{ t('navigation.selectPicBeds') }}
</span> </span>
<span <span
v-else v-else
class="selected-count" class="selected-count"
> >
{{ choosedPicBedForQRCode.length }} {{ $t('navigation.selected') }} {{ choosedPicBedForQRCode.length }} {{ t('navigation.selected') }}
</span> </span>
<ChevronDownIcon <ChevronDownIcon
:size="16" :size="16"
@@ -167,7 +167,7 @@
@click="handleCopyPicBedConfig" @click="handleCopyPicBedConfig"
> >
<CopyIcon :size="16" /> <CopyIcon :size="16" />
{{ $t('navigation.copyPicBedConfig') }} {{ t('navigation.copyPicBedConfig') }}
</button> </button>
</div> </div>
@@ -264,7 +264,7 @@ function openMenu () {
function handleCopyPicBedConfig () { function handleCopyPicBedConfig () {
window.electron.clipboard.writeText(picBedConfigString.value) window.electron.clipboard.writeText(picBedConfigString.value)
$message.success(t('COPY_PICBED_CONFIG_SUCCEED')) $message.success(t('navigation.copySuccess'))
} }
const navigationItems = computed(() => [ const navigationItems = computed(() => [

View File

@@ -15,7 +15,8 @@
"moreOptions": "More Options", "moreOptions": "More Options",
"picBedQrCode": "PicBed QR Code", "picBedQrCode": "PicBed QR Code",
"choosePicBed": "Choose PicBed", "choosePicBed": "Choose PicBed",
"selectPicBeds": "Select PicBeds" "selectPicBeds": "Select PicBeds",
"copySuccess": "Copy Success"
}, },
"settings": { "settings": {
"theme": { "theme": {
@@ -347,6 +348,7 @@
"pluginList": "Plugin List", "pluginList": "Plugin List",
"notGuiImplement": "This plugin does not have a GUI implementation, continue?", "notGuiImplement": "This plugin does not have a GUI implementation, continue?",
"updateSuccess": "Update Success", "updateSuccess": "Update Success",
"setResult": "Set Result",
"setSuccess": "Set Success" "setSuccess": "Set Success"
}, },
"inputBox": { "inputBox": {
@@ -476,35 +478,14 @@
"inputRegexTip": "Please enter the matching string", "inputRegexTip": "Please enter the matching string",
"noMatch": "No matching items found", "noMatch": "No matching items found",
"noItemsNeedRename": "No items need to be renamed" "noItemsNeedRename": "No items need to be renamed"
},
"tray": {
"openMainWindow": "Open Main Window",
"waitForUpload": "Waiting for Upload",
"uploaded": "Uploaded",
"copySuccess": "Copy Success"
} }
}, },
"OPEN_MAIN_WINDOW": "Open Main Window",
"OPERATION_SUCCEED": "Operation Succeed",
"REFRESH": "Refresh",
"CHOOSE_PICBED": "Choose Picbed",
"COPY_PICBED_CONFIG": "Copy Picbed Config",
"COPY_PICBED_CONFIG_SUCCEED": "Copy Picbed Config Succeed",
"INPUT": "Input",
"CANCEL": "Cancel",
"CONFIRM": "Confirm",
"RESET_PICBED_CONFIG": "Reset",
"CHOOSE_SHOWED_PICBED": "Choose Showed Picbed",
"CHOOSE_PASTE_FORMAT": "Choose Paste Format",
"COPY": "Copy",
"DELETE": "Delete",
"SELECT_ALL": "Select All",
"COPY_LINK_SUCCEED": "Copy Link Succeed",
"SETTINGS": "Settings",
"SETTINGS_OPEN": "Open",
"SETTINGS_CLOSE": "Close",
"SETTINGS_RESULT": "Result",
"UPLOADER_CONFIG_PLACEHOLDER": "Please Enter Configuration Name",
"SELECTED_SETTING_HINT": "Selected",
"WAIT_TO_UPLOAD": "Wait to Upload",
"ALREADY_UPLOAD": "Already Uploaded",
"TIPS_DRAG_VALID_PICTURE_OR_URL": "Drag valid picture or url to here",
"TIPS_SET_SUCCEED": "Set successfully",
"TIPS_RESET_SUCCEED": "Reset successfully",
"MANAGE_SETTING_TITLE": "Manage Setting", "MANAGE_SETTING_TITLE": "Manage Setting",
"MANAGE_SETTING_ISAUTOREFRESH_TITLE": "Auto refresh file list when entering new directory", "MANAGE_SETTING_ISAUTOREFRESH_TITLE": "Auto refresh file list when entering new directory",
"MANAGE_SETTING_ISAUTOREFRESH_TIPS": "Only applies to non-paginated mode, data is cached to indexdb to speed up loading speed", "MANAGE_SETTING_ISAUTOREFRESH_TIPS": "Only applies to non-paginated mode, data is cached to indexdb to speed up loading speed",

View File

@@ -15,7 +15,8 @@
"moreOptions": "更多选项", "moreOptions": "更多选项",
"picBedQrCode": "图床配置二维码", "picBedQrCode": "图床配置二维码",
"choosePicBed": "选择图床", "choosePicBed": "选择图床",
"selectPicBeds": "请选择图床" "selectPicBeds": "请选择图床",
"copySuccess": "复制成功"
}, },
"settings": { "settings": {
"theme": { "theme": {
@@ -342,6 +343,7 @@
"pluginList": "插件列表", "pluginList": "插件列表",
"notGuiImplement": "该插件未对可视化界面进行优化, 是否继续安装?", "notGuiImplement": "该插件未对可视化界面进行优化, 是否继续安装?",
"updateSuccess": "插件更新成功", "updateSuccess": "插件更新成功",
"setResult": "设置结果",
"setSuccess": "设置成功" "setSuccess": "设置成功"
}, },
"inputBox": { "inputBox": {
@@ -471,35 +473,14 @@
"inputRegexTip": "请输入匹配字符串", "inputRegexTip": "请输入匹配字符串",
"noMatch": "未找到匹配项", "noMatch": "未找到匹配项",
"noItemsNeedRename": "没有需要重命名的项目" "noItemsNeedRename": "没有需要重命名的项目"
},
"tray": {
"openMainWindow": "打开主窗口",
"waitForUpload": "等待上传",
"uploaded": "已上传",
"copySuccess": "复制成功"
} }
}, },
"OPEN_MAIN_WINDOW": "打开主窗口",
"OPERATION_SUCCEED": "操作成功",
"REFRESH": "刷新",
"CHOOSE_PICBED": "选择图床",
"COPY_PICBED_CONFIG": "复制图床配置",
"COPY_PICBED_CONFIG_SUCCEED": "复制图床配置成功",
"INPUT": "输入框",
"CANCEL": "取消",
"CONFIRM": "确定",
"RESET_PICBED_CONFIG": "重置",
"CHOOSE_SHOWED_PICBED": "请选择显示的图床",
"CHOOSE_PASTE_FORMAT": "请选择粘贴的格式",
"COPY": "复制",
"DELETE": "删除",
"SELECT_ALL": "全选",
"COPY_LINK_SUCCEED": "复制链接成功",
"SETTINGS": "设置",
"SETTINGS_OPEN": "开",
"SETTINGS_CLOSE": "关",
"SETTINGS_RESULT": "设置结果",
"UPLOADER_CONFIG_PLACEHOLDER": "请输入配置名称",
"SELECTED_SETTING_HINT": "已选中",
"WAIT_TO_UPLOAD": "等待上传",
"ALREADY_UPLOAD": "已上传",
"TIPS_DRAG_VALID_PICTURE_OR_URL": "请拖入合法的图片文件或者图片URL地址",
"TIPS_SET_SUCCEED": "设置成功",
"TIPS_RESET_SUCCEED": "重置成功",
"MANAGE_SETTING_TITLE": "管理页面设置", "MANAGE_SETTING_TITLE": "管理页面设置",
"MANAGE_SETTING_ISAUTOREFRESH_TITLE": "每次进入新目录时,是否自动刷新文件列表", "MANAGE_SETTING_ISAUTOREFRESH_TITLE": "每次进入新目录时,是否自动刷新文件列表",
"MANAGE_SETTING_ISAUTOREFRESH_TIPS": "仅对不分页模式有效,默认在加载过一次后自动缓存到数据库来加快下次加载速度", "MANAGE_SETTING_ISAUTOREFRESH_TIPS": "仅对不分页模式有效,默认在加载过一次后自动缓存到数据库来加快下次加载速度",

View File

@@ -15,7 +15,8 @@
"moreOptions": "更多選項", "moreOptions": "更多選項",
"picBedQrCode": "圖床配置 QRCODE", "picBedQrCode": "圖床配置 QRCODE",
"choosePicBed": "選擇圖床", "choosePicBed": "選擇圖床",
"selectPicBeds": "請選擇圖床" "selectPicBeds": "請選擇圖床",
"copySuccess": "複製成功"
}, },
"settings": { "settings": {
"theme": { "theme": {
@@ -342,6 +343,7 @@
"pluginList": "插件列表", "pluginList": "插件列表",
"notGuiImplement": "該插件未針對圖形介面進行優化,是否繼續安裝?", "notGuiImplement": "該插件未針對圖形介面進行優化,是否繼續安裝?",
"updateSuccess": "插件更新成功", "updateSuccess": "插件更新成功",
"setResult": "設定結果",
"setSuccess": "設定成功" "setSuccess": "設定成功"
}, },
"inputBox": { "inputBox": {
@@ -468,38 +470,17 @@
"listView": "列表", "listView": "列表",
"gridView": "網格", "gridView": "網格",
"isAlwaysForceReload": "無快取", "isAlwaysForceReload": "無快取",
"inputRegexTip": "请输入匹配字串", "inputRegexTip": "請輸入匹配字串",
"noMatch": "未找到匹配", "noMatch": "未找到匹配",
"noItemsNeedRename": "有需要重命名的目" "noItemsNeedRename": "有需要重命名的目"
},
"tray": {
"openMainWindow": "打開主窗口",
"waitForUpload": "等待上傳",
"uploaded": "已上傳",
"copySuccess": "複製成功"
} }
}, },
"OPEN_MAIN_WINDOW": "打開主視窗",
"OPERATION_SUCCEED": "操作成功",
"REFRESH": "刷新",
"CHOOSE_PICBED": "選擇圖床",
"COPY_PICBED_CONFIG": "複製圖床設定",
"COPY_PICBED_CONFIG_SUCCEED": "複製圖床設定成功",
"INPUT": "輸入框",
"CANCEL": "取消",
"CONFIRM": "確定",
"RESET_PICBED_CONFIG": "重置",
"CHOOSE_SHOWED_PICBED": "請選擇顯示的圖床",
"CHOOSE_PASTE_FORMAT": "請選擇貼上的格式",
"COPY": "複製",
"DELETE": "刪除",
"SELECT_ALL": "全選",
"COPY_LINK_SUCCEED": "複製連結成功",
"SETTINGS": "設定",
"SETTINGS_OPEN": "開",
"SETTINGS_CLOSE": "關",
"SETTINGS_RESULT": "設定結果",
"UPLOADER_CONFIG_PLACEHOLDER": "請輸入配置名稱",
"SELECTED_SETTING_HINT": "已選中",
"WAIT_TO_UPLOAD": "等待上傳",
"ALREADY_UPLOAD": "已上傳",
"TIPS_DRAG_VALID_PICTURE_OR_URL": "請拖入合法的圖片檔案或者圖片URL地址",
"TIPS_SET_SUCCEED": "設定成功",
"TIPS_RESET_SUCCEED": "重置成功",
"MANAGE_SETTING_TITLE": "管理設定", "MANAGE_SETTING_TITLE": "管理設定",
"MANAGE_SETTING_ISAUTOREFRESH_TITLE": "每次進入新目錄時,是否自動重新整理檔案列表", "MANAGE_SETTING_ISAUTOREFRESH_TITLE": "每次進入新目錄時,是否自動重新整理檔案列表",
"MANAGE_SETTING_ISAUTOREFRESH_TIPS": "僅對不分頁模式有效,預設會在載入後自動快取至資料庫以提升下次載入速度", "MANAGE_SETTING_ISAUTOREFRESH_TIPS": "僅對不分頁模式有效,預設會在載入後自動快取至資料庫以提升下次載入速度",

View File

@@ -36,10 +36,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { IpcRendererEvent } from 'electron' import type { IpcRendererEvent } from 'electron'
import { ElMessage } from 'element-plus'
import type { IConfig } from 'piclist' import type { IConfig } from 'piclist'
import { onBeforeMount, onBeforeUnmount, ref, watch } from 'vue' import { onBeforeMount, onBeforeUnmount, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { isUrl } from '@/utils/common' import { isUrl } from '@/utils/common'
import { getConfig } from '@/utils/dataSender' import { getConfig } from '@/utils/dataSender'
@@ -47,7 +45,6 @@ import { IRPCActionType } from '@/utils/enum'
import { osGlobal } from '@/utils/global' import { osGlobal } from '@/utils/global'
import type { IFileWithPath } from '#/types/types' import type { IFileWithPath } from '#/types/types'
const { t } = useI18n()
const logoPath = ref('') const logoPath = ref('')
const dragover = ref(false) const dragover = ref(false)
const progress = ref(0) const progress = ref(0)
@@ -116,8 +113,6 @@ function onDrop (e: DragEvent) {
const str = e.dataTransfer!.getData(items[0].type) const str = e.dataTransfer!.getData(items[0].type)
if (isUrl(str)) { if (isUrl(str)) {
window.electron.sendRPC(IRPCActionType.UPLOAD_CHOOSED_FILES, [{ path: str }]) window.electron.sendRPC(IRPCActionType.UPLOAD_CHOOSED_FILES, [{ path: str }])
} else {
ElMessage.error(t('TIPS_DRAG_VALID_PICTURE_OR_URL'))
} }
} }
} }
@@ -134,8 +129,6 @@ function handleURLDrag (items: DataTransferItemList, dataTransfer: DataTransfer)
path: urlMatch[1] path: urlMatch[1]
} }
]) ])
} else {
ElMessage.error(t('TIPS_DRAG_VALID_PICTURE_OR_URL'))
} }
} }

View File

@@ -555,7 +555,7 @@ async function handleConfirmConfig () {
break break
} }
if ('Notification' in window) { if ('Notification' in window) {
const successNotification = new Notification(t('SETTINGS_RESULT'), { const successNotification = new Notification(t('pages.plugin.setResult'), {
body: t('pages.plugin.setSuccess') body: t('pages.plugin.setSuccess')
}) })
successNotification.onclick = () => { successNotification.onclick = () => {

View File

@@ -1,58 +1,117 @@
<template> <template>
<div id="tray-page"> <div id="tray-page">
<!-- Header -->
<div <div
class="open-main-window" class="tray-header"
@click="openSettingWindow" @click="openSettingWindow"
> >
{{ $t('OPEN_MAIN_WINDOW') }} <div class="header-content">
<span class="header-text">
{{ t('pages.tray.openMainWindow') }}
</span>
</div>
<div class="header-arrow">
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="m9 18 6-6-6-6" />
</svg>
</div>
</div> </div>
<div class="content">
<!-- Content -->
<div class="tray-content">
<!-- Clipboard Files Section -->
<div <div
v-if="clipboardFiles.length > 0" v-if="clipboardFiles.length > 0"
class="wait-upload-img" class="section"
> >
<div class="list-title"> <div class="section-header">
{{ $t('WAIT_TO_UPLOAD') }} <div class="section-title">
{{ t('pages.tray.waitForUpload') }}
</div>
<div class="section-badge">
{{ clipboardFiles.length }}
</div>
</div> </div>
<div <div class="image-grid">
v-for="(item, index) in clipboardFiles"
:key="index"
class="img-list"
>
<div <div
class="upload-img__container" v-for="(item, index) in clipboardFiles"
:class="{ upload: uploadFlag }" :key="index"
class="image-item"
:class="{ uploading: uploadFlag }"
@click="uploadClipboardFiles" @click="uploadClipboardFiles"
> >
<img <div class="image-container">
:src="item.imgUrl" <img
class="upload-img" :src="item.imgUrl"
> class="image"
@error="onImageError"
>
<div
v-if="uploadFlag"
class="upload-overlay"
>
<div class="spinner" />
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="uploaded-img">
<div class="list-title"> <!-- Uploaded Files Section -->
{{ $t('ALREADY_UPLOAD') }} <div class="section">
<div class="section-header">
<div class="section-title">
{{ t('pages.tray.uploaded') }}
</div>
</div> </div>
<div <div class="image-grid">
v-for="item in files"
:key="item.imgUrl"
class="img-list"
>
<div <div
class="upload-img__container" v-for="item in files"
:key="item.imgUrl"
class="image-item"
@click="copyTheLink(item)" @click="copyTheLink(item)"
> >
<img <div class="image-container">
v-lazy="item.imgUrl" <img
class="upload-img" v-lazy="item.imgUrl"
> class="image"
<div @error="onImageError"
class="upload-img__title" >
:title="item.fileName" <div class="image-overlay">
> <div
{{ item.fileName }} class="image-title"
:title="item.fileName"
>
{{ item.fileName }}
</div>
<div class="copy-indicator">
<svg
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<rect
x="9"
y="9"
width="13"
height="13"
rx="2"
ry="2"
/>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
</svg>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -82,7 +141,7 @@ type IResult<T> = T & {
} }
const files = ref<IResult<ImgInfo>[]>([]) const files = ref<IResult<ImgInfo>[]>([])
const notification = reactive({ const notification = reactive({
title: t('COPY_LINK_SUCCEED'), title: t('pages.tray.copySuccess'),
body: '' body: ''
}) })
@@ -94,7 +153,7 @@ function openSettingWindow () {
} }
async function getData () { async function getData () {
files.value = (await $$db.get<ImgInfo>({ orderBy: 'desc', limit: 5 }))!.data files.value = (await $$db.get<ImgInfo>({ orderBy: 'desc', limit: 10 }))!.data
} }
const formatCustomLink = (customLink: string, item: ImgInfo) => { const formatCustomLink = (customLink: string, item: ImgInfo) => {
@@ -181,6 +240,11 @@ function uploadClipboardFiles () {
window.electron.sendRPC(IRPCActionType.TRAY_UPLOAD_CLIPBOARD_FILES) window.electron.sendRPC(IRPCActionType.TRAY_UPLOAD_CLIPBOARD_FILES)
} }
function onImageError (event: Event) {
const img = event.target as HTMLImageElement
img.style.display = 'none'
}
const dragFilesHandler = async (_: IpcRendererEvent, _files: string[]) => { const dragFilesHandler = async (_: IpcRendererEvent, _files: string[]) => {
for (const file of _files) { for (const file of _files) {
await $$db.insert(file) await $$db.insert(file)
@@ -207,9 +271,9 @@ const updateFilesHandler = () => {
getData() getData()
} }
onBeforeMount(() => { onBeforeMount(async () => {
disableDragFile() disableDragFile()
getData() await getData()
window.electron.ipcRendererOn('dragFiles', dragFilesHandler) window.electron.ipcRendererOn('dragFiles', dragFilesHandler)
window.electron.ipcRendererOn('clipboardFiles', clipboardFilesHandler) window.electron.ipcRendererOn('clipboardFiles', clipboardFilesHandler)
window.electron.ipcRendererOn('uploadFiles', uploadFilesHandler) window.electron.ipcRendererOn('uploadFiles', uploadFilesHandler)
@@ -230,88 +294,242 @@ export default {
} }
</script> </script>
<style lang="stylus"> <style lang="stylus" scoped>
/* Global styles */
body::-webkit-scrollbar body::-webkit-scrollbar
width 0px width 0px
#tray-page #tray-page
background-color transparent width 196px
.open-main-window height 350px
background #000 background rgba(255, 255, 255, 0.95)
height 20px backdrop-filter blur(20px)
line-height 20px display flex
text-align center flex-direction column
color #858585 overflow hidden
font-size 12px font-family -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif
cursor pointer
transition all .2s ease-in-out /* Header */
.tray-header
background var(--color-text-tertiary)
padding 8px 12px
cursor pointer
transition all 0.2s ease
display flex
align-items center
justify-content space-between
min-height 32px
border-radius 0 0 8px 8px
&:hover
transform translateY(-1px)
box-shadow 0 4px 6px rgba(102, 126, 234, 0.3)
.header-content
display flex
align-items center
gap 8px
flex 1
.header-text
color white
font-size 11px
font-weight 500
opacity 0.95
.header-arrow
color white
opacity 0.8
transition transform 0.2s ease
display flex
align-items center
.tray-header:hover .header-arrow
transform translateX(2px)
/* Content */
.tray-content
flex 1
padding 8px
overflow-y auto
overflow-x hidden
.tray-content::-webkit-scrollbar
width 4px
.tray-content::-webkit-scrollbar-track
background rgba(0, 0, 0, 0.05)
border-radius 2px
.tray-content::-webkit-scrollbar-thumb
background rgba(0, 0, 0, 0.2)
border-radius 2px
&:hover
background rgba(0, 0, 0, 0.3)
/* Section */
.section
margin-bottom 12px
&:last-child
margin-bottom 0
.section-header
display flex
align-items center
justify-content space-between
margin-bottom 6px
padding 0 2px
.section-title
font-size 10px
font-weight 600
color #666
text-transform uppercase
letter-spacing 0.5px
.section-badge
background rgba(102, 126, 234, 0.1)
color #667eea
font-size 9px
font-weight 600
padding 2px 6px
border-radius 8px
min-width 16px
text-align center
/* Image Grid */
.image-grid
display grid
grid-template-columns repeat(2, 1fr)
gap 6px
.image-item
position relative
cursor pointer
border-radius 6px
overflow hidden
transition all 0.2s ease
background rgba(255, 255, 255, 0.8)
border 1px solid rgba(0, 0, 0, 0.08)
&:hover
transform translateY(-2px)
box-shadow 0 4px 12px rgba(0, 0, 0, 0.15)
border-color rgba(102, 126, 234, 0.3)
&.uploading
cursor not-allowed
opacity 0.7
.image-container
position relative
width 100%
height 60px
overflow hidden
.image
width 100%
height 100%
object-fit cover
transition transform 0.2s ease
.image-item:hover .image
transform scale(1.05)
/* Upload Overlay */
.upload-overlay
position absolute
top 0
left 0
right 0
bottom 0
background rgba(255, 255, 255, 0.9)
display flex
align-items center
justify-content center
.spinner
width 16px
height 16px
border 2px solid rgba(102, 126, 234, 0.2)
border-left-color #667eea
border-radius 50%
animation spin 1s linear infinite
@keyframes spin
0%
transform rotate(0deg)
100%
transform rotate(360deg)
/* Image Overlay */
.image-overlay
position absolute
bottom 0
left 0
right 0
background linear-gradient(transparent, rgba(0, 0, 0, 0.7))
padding 6px 4px 4px
display flex
align-items flex-end
justify-content space-between
transform translateY(100%)
transition transform 0.2s ease
.image-item:hover .image-overlay
transform translateY(0)
.image-title
color white
font-size 9px
font-weight 500
max-width 60px
overflow hidden
text-overflow ellipsis
white-space nowrap
line-height 1.2
.copy-indicator
color white
opacity 0.8
display flex
align-items center
/* Responsive adjustments for very small content */
@media (max-width: 200px)
.image-grid
grid-template-columns 1fr
.header-text
font-size 10px
.section-title
font-size 9px
/* Dark theme support */
@media (prefers-color-scheme: dark)
#tray-page
background rgba(30, 30, 30, 0.95)
color #fff
.section-title
color #999
.image-item
background rgba(40, 40, 40, 0.8)
border-color rgba(255, 255, 255, 0.1)
&:hover &:hover
color: #fff; border-color rgba(102, 126, 234, 0.5)
background #49B1F5
.list-title .tray-content::-webkit-scrollbar-track
text-align center background rgba(255, 255, 255, 0.05)
color #858585
font-size 12px .tray-content::-webkit-scrollbar-thumb
padding 6px 0 background rgba(255, 255, 255, 0.2)
position relative
&:before
content ''
position absolute
height 1px
width calc(100% - 36px)
bottom 0
left 18px
background #858585
// .header-arrow
// position absolute
// top 12px
// left 50%
// margin-left -10px
// width: 0;
// height: 0;
// border-left: 10px solid transparent
// border-right: 10px solid transparent
// border-bottom: 10px solid rgba(255,255,255, 1)
.content
position absolute
top 20px
width 100%
.img-list
padding 4px 8px
display flex
justify-content space-between
align-items center
// height 45px
cursor pointer
transition all .2s ease-in-out
&:hover &:hover
background #49B1F5 background rgba(255, 255, 255, 0.3)
.upload-img__index
color #fff
.upload-img__container
display flex
flex-direction column
justify-content center
align-items center
.upload-img
max-width 100%
object-fit scale-down
margin 0 auto
&__container
display flex
flex-direction column
justify-content center
align-items center
width 100%
padding 8px 8px 4px
height 100%
&.upload
cursor not-allowed
&__title
text-align center
overflow hidden
text-overflow ellipsis
white-space nowrap
color #ddd
font-size 14px
margin-top 4px
</style> </style>