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