mirror of
https://github.com/Kuingsmile/PicList.git
synced 2026-05-06 20:42:57 +08:00
@@ -84,7 +84,7 @@
|
||||
<button
|
||||
class="action-button primary"
|
||||
:class="{ 'action-button': selectedItems.length === 0 }"
|
||||
@click="handleBatchCopyLink(manageStore.config.settings.pasteFormat)"
|
||||
@click="copyDropdownOpen = !copyDropdownOpen"
|
||||
>
|
||||
<CopyIcon class="action-icon" />
|
||||
</button>
|
||||
@@ -417,27 +417,34 @@
|
||||
</button>
|
||||
|
||||
<!-- Copy Link Dropdown -->
|
||||
<div class="file-actions-dropdown">
|
||||
<button class="file-action-button" @click.stop="toggleCopyDropdown(index)">
|
||||
<div class="file-actions-dropdown" :data-dropdown-index="index">
|
||||
<button class="file-action-button" @click.stop="toggleCopyDropdown(index, $event)">
|
||||
<CopyIcon class="action-icon" />
|
||||
</button>
|
||||
<div v-if="copyDropdownIndex === index" class="file-actions-dropdown-content">
|
||||
<teleport to="body">
|
||||
<div
|
||||
v-for="format in linkFormatList"
|
||||
:key="format"
|
||||
class="file-actions-dropdown-item"
|
||||
@click.stop="copyLink(item, format)"
|
||||
v-if="copyDropdownIndex === index"
|
||||
class="file-actions-dropdown-content floating"
|
||||
:style="getDropdownStyle(index)"
|
||||
data-floating-dropdown
|
||||
>
|
||||
{{ t(`pages.manage.bucket.linkFormat.${format}`) }}
|
||||
<div
|
||||
v-for="format in linkFormatList"
|
||||
:key="format"
|
||||
class="file-actions-dropdown-item"
|
||||
@click.stop="copyLink(item, format)"
|
||||
>
|
||||
{{ t(`pages.manage.bucket.linkFormat.${format}`) }}
|
||||
</div>
|
||||
<div
|
||||
v-if="isShowPresignedUrl"
|
||||
class="file-actions-dropdown-item"
|
||||
@click.stop="async () => copyToClipboard(await getPreSignedUrl(item))"
|
||||
>
|
||||
{{ t('pages.manage.bucket.linkFormat.presign') }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="isShowPresignedUrl"
|
||||
class="file-actions-dropdown-item"
|
||||
@click.stop="async () => copyToClipboard(await getPreSignedUrl(item))"
|
||||
>
|
||||
{{ t('pages.manage.bucket.linkFormat.presign') }}
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</div>
|
||||
|
||||
<!-- File Info -->
|
||||
@@ -1246,6 +1253,7 @@ const isContentFullscreen = ref(false)
|
||||
const copyDropdownOpen = ref(false)
|
||||
const sortDropdownOpen = ref(false)
|
||||
const copyDropdownIndex = ref(-1)
|
||||
const dropdownPositions = ref(new Map<number, { left: boolean; up: boolean }>())
|
||||
const gridBreakpoints = ref([
|
||||
{ min: 0, cols: 1 },
|
||||
{ min: 380, cols: 2 },
|
||||
@@ -2492,11 +2500,13 @@ function handleBatchCopyInfo() {
|
||||
|
||||
async function copyLink(item: any, type: string) {
|
||||
copyToClipboard(await formatLink(item.url, item.fileName, type, manageStore.config.settings.customPasteFormat))
|
||||
copyDropdownIndex.value = -1
|
||||
}
|
||||
|
||||
async function handleBatchCopyLink(type: string) {
|
||||
if (!selectedItems.value.length) {
|
||||
message.warning(t('pages.manage.bucket.selectFileMsg'))
|
||||
copyDropdownOpen.value = false
|
||||
return
|
||||
}
|
||||
const result = [] as string[]
|
||||
@@ -2514,6 +2524,7 @@ async function handleBatchCopyLink(type: string) {
|
||||
}
|
||||
window.electron.clipboard.writeText(result.join('\n'))
|
||||
message.success(`${t('pages.manage.bucket.copySuccess')}`)
|
||||
copyDropdownOpen.value = false
|
||||
}
|
||||
|
||||
async function cancelLoading() {
|
||||
@@ -2860,13 +2871,77 @@ async function getPreSignedUrl(item: any) {
|
||||
function copyToClipboard(text: string) {
|
||||
window.electron.clipboard.writeText(text)
|
||||
message.success(t('pages.manage.bucket.copySuccess'))
|
||||
copyDropdownIndex.value = -1
|
||||
}
|
||||
|
||||
function toggleCopyDropdown(index: number) {
|
||||
function toggleCopyDropdown(index: number, event?: MouseEvent) {
|
||||
if (copyDropdownIndex.value === index) {
|
||||
copyDropdownIndex.value = -1
|
||||
} else {
|
||||
copyDropdownIndex.value = index
|
||||
|
||||
if (event) {
|
||||
const button = event.currentTarget as HTMLElement
|
||||
const rect = button.getBoundingClientRect()
|
||||
const viewportWidth = window.innerWidth
|
||||
const viewportHeight = window.innerHeight
|
||||
|
||||
const container = bucketContainerRef.value?.$el || bucketContainerRef.value
|
||||
const containerRect = container?.getBoundingClientRect()
|
||||
const dropdownWidth = 160
|
||||
const shouldShowLeft =
|
||||
rect.right > viewportWidth - dropdownWidth ||
|
||||
(containerRect && rect.right > containerRect.right - dropdownWidth)
|
||||
|
||||
const dropdownHeight = 200
|
||||
const shouldShowUp =
|
||||
rect.bottom > viewportHeight - dropdownHeight ||
|
||||
(containerRect && rect.bottom > containerRect.bottom - dropdownHeight)
|
||||
|
||||
dropdownPositions.value.set(index, {
|
||||
left: shouldShowLeft,
|
||||
up: shouldShowUp,
|
||||
x: rect.left,
|
||||
y: rect.top,
|
||||
width: rect.width,
|
||||
height: rect.height
|
||||
} as any)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getDropdownStyle(index: number) {
|
||||
const pos: any = dropdownPositions.value.get(index)
|
||||
if (!pos) return { display: 'none' as const }
|
||||
const estWidth = 180
|
||||
const estHeight = 240
|
||||
let left = pos.left ? pos.x + pos.width - estWidth : pos.x
|
||||
let top = pos.up ? pos.y - estHeight : pos.y + pos.height
|
||||
const vw = window.innerWidth
|
||||
const vh = window.innerHeight
|
||||
if (left + estWidth > vw - 4) left = vw - estWidth - 4
|
||||
if (left < 4) left = 4
|
||||
if (top + estHeight > vh - 4) top = vh - estHeight - 4
|
||||
if (top < 4) top = 4
|
||||
return {
|
||||
position: 'fixed' as const,
|
||||
left: left + 'px',
|
||||
top: top + 'px',
|
||||
maxHeight: '240px',
|
||||
zIndex: 4000,
|
||||
minWidth: '140px',
|
||||
maxWidth: '200px'
|
||||
}
|
||||
}
|
||||
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
const target = event.target as HTMLElement
|
||||
if (!target.closest('.file-actions-dropdown') && !target.closest('[data-floating-dropdown]')) {
|
||||
copyDropdownIndex.value = -1
|
||||
}
|
||||
if (!target.closest('.dropdown')) {
|
||||
copyDropdownOpen.value = false
|
||||
sortDropdownOpen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2910,11 +2985,13 @@ onBeforeMount(async () => {
|
||||
isShowLoadingPage.value = false
|
||||
document.addEventListener('keydown', handleDetectShiftKey)
|
||||
document.addEventListener('keyup', handleDetectShiftKey)
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('keydown', handleDetectShiftKey)
|
||||
document.removeEventListener('keyup', handleDetectShiftKey)
|
||||
document.removeEventListener('click', handleClickOutside)
|
||||
fileTransferInterval && clearInterval(fileTransferInterval)
|
||||
downloadInterval && clearInterval(downloadInterval)
|
||||
refreshUploadTaskId.value && clearInterval(refreshUploadTaskId.value)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
padding: 1rem;
|
||||
gap: 1rem;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Header Card */
|
||||
@@ -359,7 +360,7 @@
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
background: var(--color-surface);
|
||||
background: var(--color-background-primary);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-lg);
|
||||
@@ -407,15 +408,18 @@
|
||||
|
||||
.content-area {
|
||||
flex: 1;
|
||||
padding: 0.75rem;
|
||||
overflow: hidden;
|
||||
padding: 0.5rem;
|
||||
overflow-x: visible;
|
||||
overflow-y: hidden;
|
||||
background: var(--color-background-secondary);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Virtual Scroller Container */
|
||||
.virtual-scroller-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* File Grid Item */
|
||||
@@ -425,21 +429,25 @@
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border-secondary);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
transition: var(--transition-medium);
|
||||
cursor: pointer;
|
||||
height: 220px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.file-grid-item:hover {
|
||||
border-color: var(--color-border);
|
||||
box-shadow: var(--shadow-md);
|
||||
transform: translateY(-2px);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.file-grid-item.selected {
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.file-preview {
|
||||
@@ -471,6 +479,8 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
@@ -501,6 +511,8 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.file-action-group {
|
||||
@@ -639,6 +651,7 @@
|
||||
/* File Actions Dropdown */
|
||||
.file-actions-dropdown {
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.file-actions-dropdown-content {
|
||||
@@ -649,10 +662,56 @@
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-lg);
|
||||
z-index: 1000;
|
||||
z-index: 3000;
|
||||
min-width: 140px;
|
||||
max-width: 200px;
|
||||
margin-top: 0.25rem;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
white-space: nowrap;
|
||||
transform-origin: top right;
|
||||
animation: dropdown-appear 0.15s ease-out;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@keyframes dropdown-appear {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95) translateY(-5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.file-actions-dropdown-content.position-left {
|
||||
right: auto;
|
||||
left: 0;
|
||||
transform-origin: top left;
|
||||
}
|
||||
|
||||
.file-actions-dropdown-content.position-up {
|
||||
top: auto;
|
||||
bottom: 100%;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.25rem;
|
||||
transform-origin: bottom right;
|
||||
}
|
||||
|
||||
.file-actions-dropdown-content.position-up.position-left {
|
||||
transform-origin: bottom left;
|
||||
}
|
||||
|
||||
.content-fullscreen .file-actions-dropdown-content {
|
||||
z-index: 4000;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.file-actions-dropdown-content {
|
||||
min-width: 120px;
|
||||
max-width: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
.file-actions-dropdown-item {
|
||||
@@ -1077,6 +1136,7 @@ input:checked + .switch-slider:before {
|
||||
/* File Actions Dropdown */
|
||||
.file-actions-dropdown {
|
||||
position: relative;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.file-actions-dropdown-content {
|
||||
@@ -1087,14 +1147,52 @@ input:checked + .switch-slider:before {
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-lg);
|
||||
z-index: 1000;
|
||||
z-index: 3000;
|
||||
min-width: 120px;
|
||||
max-width: 200px;
|
||||
margin-top: 0.25rem;
|
||||
white-space: nowrap;
|
||||
overflow: visible;
|
||||
transform-origin: top right;
|
||||
animation: dropdown-appear 0.15s ease-out;
|
||||
/* Ensure dropdown is never clipped */
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.file-actions-dropdown-content.position-left {
|
||||
right: auto;
|
||||
left: 0;
|
||||
transform-origin: top left;
|
||||
}
|
||||
|
||||
.file-actions-dropdown-content.position-up {
|
||||
top: auto;
|
||||
bottom: 100%;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.25rem;
|
||||
transform-origin: bottom right;
|
||||
}
|
||||
|
||||
.file-actions-dropdown-content.position-up.position-left {
|
||||
transform-origin: bottom left;
|
||||
}
|
||||
|
||||
.content-fullscreen .file-actions-dropdown-content {
|
||||
z-index: 4000;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.file-actions-dropdown-content {
|
||||
min-width: 100px;
|
||||
max-width: 140px;
|
||||
}
|
||||
}
|
||||
|
||||
.file-actions-dropdown-item {
|
||||
padding: 0.5rem 0.75rem;
|
||||
cursor: pointer;
|
||||
background: var(--color-background-primary);
|
||||
transition: var(--transition-fast);
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-primary);
|
||||
@@ -1104,8 +1202,7 @@ input:checked + .switch-slider:before {
|
||||
}
|
||||
|
||||
.file-actions-dropdown-item:hover {
|
||||
background: var(--color-surface-elevated);
|
||||
color: var(--color-accent);
|
||||
color: var(--color-blue-common);
|
||||
}
|
||||
|
||||
/* Empty State */
|
||||
@@ -1162,6 +1259,7 @@ input:checked + .switch-slider:before {
|
||||
|
||||
.file-grid-item {
|
||||
height: 240px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.drawer-container {
|
||||
@@ -1246,12 +1344,12 @@ input:checked + .switch-slider:before {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.content-fullscreen .virtual-scroller-container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Responsive adjustments for fullscreen */
|
||||
|
||||
Reference in New Issue
Block a user