Add batch download feature, support collecting selected images from other pages for download and archiving

pull/329/head
zanllp 2023-07-22 11:41:00 +08:00
parent d3b3599b73
commit 3692b2e6c3
58 changed files with 324 additions and 135 deletions

1
.gitignore vendored
View File

@ -14,3 +14,4 @@ dist/**/*
*.spec *.spec
out/**/* out/**/*
venv/**/* venv/**/*
zip_temp/*.zip

View File

@ -12,8 +12,8 @@ Promise.resolve().then(async () => {
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Infinite Image Browsing</title> <title>Infinite Image Browsing</title>
<script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-23e5bc7c.js"></script> <script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-af270b30.js"></script>
<link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-618900f2.css"> <link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-90388ea3.css">
</head> </head>
<body> <body>

View File

@ -18,6 +18,7 @@ from scripts.iib.tool import (
open_folder, open_folder,
get_img_geninfo_txt_path, get_img_geninfo_txt_path,
unique_by, unique_by,
create_zip_file
) )
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
@ -442,6 +443,10 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
def index_bd(): def index_bd():
return FileResponse(index_html_path) return FileResponse(index_html_path)
class PathsReq(BaseModel):
paths: List[str]
class OpenFolderReq(BaseModel): class OpenFolderReq(BaseModel):
path: str path: str
@ -459,6 +464,16 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
os.kill(os.getpid(), 9) os.kill(os.getpid(), 9)
return {"message": "Application is shutting down."} return {"message": "Application is shutting down."}
@app.post(pre + "/zip", dependencies=[Depends(get_token)])
def zip_files(req: PathsReq):
now = datetime.now()
timestamp = now.strftime("%Y-%m-%d-%H-%M-%S")
zip_temp_dir = os.path.join(cwd, "zip_temp")
os.makedirs(zip_temp_dir, exist_ok=True)
file_path = os.path.join(zip_temp_dir, f"iib_batch_download_{timestamp}.zip")
create_zip_file(req.paths, file_path)
return FileResponse(file_path, media_type="application/zip")
db_pre = pre + "/db" db_pre = pre + "/db"
@app.get(db_pre + "/basic_info", dependencies=[Depends(get_token)]) @app.get(db_pre + "/basic_info", dependencies=[Depends(get_token)])
@ -532,9 +547,6 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
# tags = Tag.get_all_custom_tag() # tags = Tag.get_all_custom_tag()
return ImageTag.get_tags_for_image(conn, img.id, type="custom") return ImageTag.get_tags_for_image(conn, img.id, type="custom")
class PathsReq(BaseModel):
paths: List[str]
@app.post(db_pre + "/get_image_tags", dependencies=[Depends(get_token)]) @app.post(db_pre + "/get_image_tags", dependencies=[Depends(get_token)])
async def get_img_tags(req: PathsReq): async def get_img_tags(req: PathsReq):
conn = DataBase.get_conn() conn = DataBase.get_conn()

View File

@ -5,11 +5,12 @@ import re
import tempfile import tempfile
import imghdr import imghdr
import subprocess import subprocess
from typing import Dict from typing import Dict, List
import sys import sys
import piexif import piexif
import piexif.helper import piexif.helper
import json import json
import zipfile
sd_img_dirs = [ sd_img_dirs = [
"outdir_txt2img_samples", "outdir_txt2img_samples",
@ -145,6 +146,28 @@ def is_valid_image_path(path):
return True return True
def create_zip_file(file_paths: List[str], zip_file_name: str):
"""
将文件打包成一个压缩包
Args:
file_paths: 文件路径的列表
zip_file_name: 压缩包的文件名
Returns:
无返回值
"""
with zipfile.ZipFile(zip_file_name, 'w', zipfile.ZIP_DEFLATED) as zip_file:
for file_path in file_paths:
if os.path.isfile(file_path):
zip_file.write(file_path, os.path.basename(file_path))
elif os.path.isdir(file_path):
for root, _, files in os.walk(file_path):
for file in files:
full_path = os.path.join(root, file)
zip_file.write(full_path, os.path.relpath(full_path, file_path))
def get_temp_path(): def get_temp_path():
"""获取跨平台的临时文件目录路径""" """获取跨平台的临时文件目录路径"""
temp_path = None temp_path = None

2
vue/components.d.ts vendored
View File

@ -37,6 +37,8 @@ declare module '@vue/runtime-core' {
ATabs: typeof import('ant-design-vue/es')['Tabs'] ATabs: typeof import('ant-design-vue/es')['Tabs']
ATag: typeof import('ant-design-vue/es')['Tag'] ATag: typeof import('ant-design-vue/es')['Tag']
ATooltip: typeof import('ant-design-vue/es')['Tooltip'] ATooltip: typeof import('ant-design-vue/es')['Tooltip']
ContextMenu: typeof import('./src/components/ContextMenu.vue')['default']
FileItem: typeof import('./src/components/FileItem.vue')['default']
NumInput: typeof import('./src/components/numInput.vue')['default'] NumInput: typeof import('./src/components/numInput.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']

4
vue/dist/assets/FileItem-a4055f0b.js vendored Normal file

File diff suppressed because one or more lines are too long

1
vue/dist/assets/FileItem-b09f7869.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{d as t,o as a,m as r,cI as n}from"./index-23e5bc7c.js";const p=t({__name:"ImgSliPagePane",props:{paneIdx:{},tabIdx:{},left:{},right:{}},setup(o){return(e,s)=>(a(),r(n,{left:e.left,right:e.right},null,8,["left","right"]))}});export{p as default}; import{d as t,o as a,m as r,b$ as n}from"./index-af270b30.js";const p=t({__name:"ImgSliPagePane",props:{paneIdx:{},tabIdx:{},left:{},right:{}},setup(o){return(e,s)=>(a(),r(n,{left:e.left,right:e.right},null,8,["left","right"]))}});export{p as default};

View File

@ -0,0 +1 @@
import{d as q,l as Q,ax as W,o as r,y as _,c as s,n as a,r as e,s as h,p as y,t as X,v as b,x as j,m as M,L as H,E as m,N as S,Q as J,R as K,X as Y}from"./index-af270b30.js";import{L as Z,R as ee,f as te,S as ie}from"./fullScreenContextMenu-2284d97e.js";import{g as le,F as se}from"./FileItem-a4055f0b.js";import{g as ne}from"./db-dbaa937e.js";import{u as ae}from"./hook-c56860bd.js";import"./shortcut-c1bb7547.js";const oe={class:"hint"},re={key:1,class:"preview-switch"},de=q({__name:"MatchedImageGrid",props:{tabIdx:{},paneIdx:{},selectedTagIds:{},id:{}},setup(T){const u=T,{queue:p,images:i,onContextMenuClickU:g,stackViewEl:V,previewIdx:n,previewing:v,onPreviewVisibleChange:D,previewImgMove:f,canPreview:w,itemSize:I,gridItems:F,showGenInfo:o,imageGenInfo:k,q:z,multiSelectedIdxs:$,onFileItemClick:B,scroller:x,showMenuIdx:d,onFileDragStart:E,onFileDragEnd:G,cellWidth:N,onScroll:R,updateImageTag:A}=ae();return Q(()=>u.selectedTagIds,async()=>{const{res:c}=p.pushAction(()=>ne(u.selectedTagIds));i.value=await c,await W(),A(),x.value.scrollToItem(0)},{immediate:!0}),(c,t)=>{const P=J,U=K,L=ie;return r(),_("div",{class:"container",ref_key:"stackViewEl",ref:V},[s(L,{size:"large",spinning:!e(p).isIdle},{default:a(()=>[s(U,{visible:e(o),"onUpdate:visible":t[1]||(t[1]=l=>h(o)?o.value=l:null),width:"70vw","mask-closable":"",onOk:t[2]||(t[2]=l=>o.value=!1)},{cancelText:a(()=>[]),default:a(()=>[s(P,{active:"",loading:!e(z).isIdle},{default:a(()=>[y("div",{style:{width:"100%","word-break":"break-all","white-space":"pre-line","max-height":"70vh",overflow:"auto"},onDblclick:t[0]||(t[0]=l=>e(X)(e(k)))},[y("div",oe,b(c.$t("doubleClickToCopy")),1),j(" "+b(e(k)),1)],32)]),_:1},8,["loading"])]),_:1},8,["visible"]),e(i)?(r(),M(e(le),{key:0,ref_key:"scroller",ref:x,class:"file-list",items:e(i),"item-size":e(I).first,"key-field":"fullpath","item-secondary-size":e(I).second,gridItems:e(F),onScroll:e(R)},{default:a(({item:l,index:C})=>[s(se,{idx:C,file:l,"cell-width":e(N),"show-menu-idx":e(d),"onUpdate:showMenuIdx":t[3]||(t[3]=O=>h(d)?d.value=O:null),onDragstart:e(E),onDragend:e(G),onFileItemClick:e(B),"full-screen-preview-image-url":e(i)[e(n)]?e(H)(e(i)[e(n)]):"",selected:e($).includes(C),onContextMenuClick:e(g),onPreviewVisibleChange:e(D)},null,8,["idx","file","cell-width","show-menu-idx","onDragstart","onDragend","onFileItemClick","full-screen-preview-image-url","selected","onContextMenuClick","onPreviewVisibleChange"])]),_:1},8,["items","item-size","item-secondary-size","gridItems","onScroll"])):m("",!0),e(v)?(r(),_("div",re,[s(e(Z),{onClick:t[4]||(t[4]=l=>e(f)("prev")),class:S({disable:!e(w)("prev")})},null,8,["class"]),s(e(ee),{onClick:t[5]||(t[5]=l=>e(f)("next")),class:S({disable:!e(w)("next")})},null,8,["class"])])):m("",!0)]),_:1},8,["spinning"]),e(v)&&e(i)&&e(i)[e(n)]?(r(),M(te,{key:0,file:e(i)[e(n)],idx:e(n),onContextMenuClick:e(g)},null,8,["file","idx","onContextMenuClick"])):m("",!0)],512)}}});const fe=Y(de,[["__scopeId","data-v-d698e678"]]);export{fe as default};

View File

@ -1 +0,0 @@
.preview-switch[data-v-3c251729]{position:fixed;top:0;bottom:0;left:0;right:0;display:flex;align-items:center;justify-content:space-between;z-index:11111;pointer-events:none}.preview-switch>*[data-v-3c251729]{color:#fff;margin:16px;font-size:4em;pointer-events:all;cursor:pointer}.preview-switch>*.disable[data-v-3c251729]{opacity:0;pointer-events:none;cursor:none}.container[data-v-3c251729]{background:var(--zp-secondary-background)}.container .file-list[data-v-3c251729]{list-style:none;padding:8px;height:100%;overflow:auto;height:var(--pane-max-height);width:100%}

View File

@ -1 +0,0 @@
import{d as q,l as Q,ax as j,o as r,y as _,c as s,n as a,r as e,s as h,p as y,t as W,v as b,x as X,m as M,L as H,E as u,N as S,Q as J,R as K,X as Y}from"./index-23e5bc7c.js";import{h as Z,i as ee,L as te,R as ie,j as le,S as se}from"./fullScreenContextMenu-c5f9ce74.js";import{g as ne}from"./db-52d8ead8.js";import{u as ae}from"./hook-13bccbae.js";import"./shortcut-98354a10.js";const oe={class:"hint"},re={key:1,class:"preview-switch"},de=q({__name:"MatchedImageGrid",props:{tabIdx:{},paneIdx:{},selectedTagIds:{},id:{}},setup(T){const m=T,{queue:p,images:i,onContextMenuClickU:g,stackViewEl:V,previewIdx:n,previewing:v,onPreviewVisibleChange:D,previewImgMove:f,canPreview:w,itemSize:I,gridItems:z,showGenInfo:o,imageGenInfo:k,q:F,multiSelectedIdxs:$,onFileItemClick:B,scroller:x,showMenuIdx:d,onFileDragStart:E,onFileDragEnd:G,cellWidth:N,onScroll:R,updateImageTag:A}=ae();return Q(()=>m.selectedTagIds,async()=>{const{res:c}=p.pushAction(()=>ne(m.selectedTagIds));i.value=await c,await j(),A(),x.value.scrollToItem(0)},{immediate:!0}),(c,t)=>{const P=J,U=K,L=se;return r(),_("div",{class:"container",ref_key:"stackViewEl",ref:V},[s(L,{size:"large",spinning:!e(p).isIdle},{default:a(()=>[s(U,{visible:e(o),"onUpdate:visible":t[1]||(t[1]=l=>h(o)?o.value=l:null),width:"70vw","mask-closable":"",onOk:t[2]||(t[2]=l=>o.value=!1)},{cancelText:a(()=>[]),default:a(()=>[s(P,{active:"",loading:!e(F).isIdle},{default:a(()=>[y("div",{style:{width:"100%","word-break":"break-all","white-space":"pre-line","max-height":"70vh",overflow:"auto"},onDblclick:t[0]||(t[0]=l=>e(W)(e(k)))},[y("div",oe,b(c.$t("doubleClickToCopy")),1),X(" "+b(e(k)),1)],32)]),_:1},8,["loading"])]),_:1},8,["visible"]),e(i)?(r(),M(e(Z),{key:0,ref_key:"scroller",ref:x,class:"file-list",items:e(i),"item-size":e(I).first,"key-field":"fullpath","item-secondary-size":e(I).second,gridItems:e(z),onScroll:e(R)},{default:a(({item:l,index:C})=>[s(ee,{idx:C,file:l,"cell-width":e(N),"show-menu-idx":e(d),"onUpdate:showMenuIdx":t[3]||(t[3]=O=>h(d)?d.value=O:null),onDragstart:e(E),onDragend:e(G),onFileItemClick:e(B),"full-screen-preview-image-url":e(i)[e(n)]?e(H)(e(i)[e(n)]):"",selected:e($).includes(C),onContextMenuClick:e(g),onPreviewVisibleChange:e(D)},null,8,["idx","file","cell-width","show-menu-idx","onDragstart","onDragend","onFileItemClick","full-screen-preview-image-url","selected","onContextMenuClick","onPreviewVisibleChange"])]),_:1},8,["items","item-size","item-secondary-size","gridItems","onScroll"])):u("",!0),e(v)?(r(),_("div",re,[s(e(te),{onClick:t[4]||(t[4]=l=>e(f)("prev")),class:S({disable:!e(w)("prev")})},null,8,["class"]),s(e(ie),{onClick:t[5]||(t[5]=l=>e(f)("next")),class:S({disable:!e(w)("next")})},null,8,["class"])])):u("",!0)]),_:1},8,["spinning"]),e(v)&&e(i)&&e(i)[e(n)]?(r(),M(le,{key:0,file:e(i)[e(n)],idx:e(n),onContextMenuClick:e(g)},null,8,["file","idx","onContextMenuClick"])):u("",!0)],512)}}});const ve=Y(de,[["__scopeId","data-v-3c251729"]]);export{ve as default};

View File

@ -0,0 +1 @@
.preview-switch[data-v-d698e678]{position:fixed;top:0;bottom:0;left:0;right:0;display:flex;align-items:center;justify-content:space-between;z-index:11111;pointer-events:none}.preview-switch>*[data-v-d698e678]{color:#fff;margin:16px;font-size:4em;pointer-events:all;cursor:pointer}.preview-switch>*.disable[data-v-d698e678]{opacity:0;pointer-events:none;cursor:none}.container[data-v-d698e678]{background:var(--zp-secondary-background)}.container .file-list[data-v-d698e678]{list-style:none;padding:8px;height:100%;overflow:auto;height:var(--pane-max-height);width:100%}

View File

@ -0,0 +1 @@
.search-bar[data-v-bb005cb9]{padding:8px;display:flex}.preview-switch[data-v-bb005cb9]{position:fixed;top:0;bottom:0;left:0;right:0;display:flex;align-items:center;justify-content:space-between;z-index:11111;pointer-events:none}.preview-switch>*[data-v-bb005cb9]{color:#fff;margin:16px;font-size:4em;pointer-events:all;cursor:pointer}.preview-switch>*.disable[data-v-bb005cb9]{opacity:0;pointer-events:none;cursor:none}.container[data-v-bb005cb9]{background:var(--zp-secondary-background)}.container .file-list[data-v-bb005cb9]{list-style:none;padding:8px;height:100%;overflow:auto;height:var(--pane-max-height);width:100%}

View File

@ -0,0 +1 @@
import{d as Y,$,aw as Z,bQ as ee,bP as B,o,y as k,c as r,r as e,bT as ne,m,n as u,x as w,v,E as g,s as V,p as F,t as te,L as ae,N as A,ax as se,ar as le,ai as ie,U as oe,V as re,Q as ue,R as de,X as ce}from"./index-af270b30.js";import{L as pe,R as me,f as ve,S as ge}from"./fullScreenContextMenu-2284d97e.js";/* empty css */import{g as fe,F as ke}from"./FileItem-a4055f0b.js";import{b as E,c as we,e as be,u as ye}from"./db-dbaa937e.js";import{u as Ie}from"./hook-c56860bd.js";import"./shortcut-c1bb7547.js";const xe={key:0,class:"search-bar"},Ce={class:"hint"},_e={key:1,class:"preview-switch"},he=Y({__name:"SubstrSearch",setup(Se){const{queue:l,images:a,onContextMenuClickU:b,stackViewEl:T,previewIdx:d,previewing:y,onPreviewVisibleChange:U,previewImgMove:I,canPreview:x,itemSize:C,gridItems:R,showGenInfo:c,imageGenInfo:_,q:N,multiSelectedIdxs:P,onFileItemClick:L,scroller:h,showMenuIdx:f,onFileDragStart:q,onFileDragEnd:G,cellWidth:K,onScroll:O,updateImageTag:Q}=Ie(),p=$(""),t=$();Z(async()=>{t.value=await E(),t.value.img_count&&t.value.expired&&S()});const S=ee(()=>l.pushAction(async()=>(await ye(),t.value=await E(),t.value)).res),M=async()=>{a.value=await l.pushAction(()=>be(p.value)).res,await se(),Q(),h.value.scrollToItem(0),a.value.length||le.info(ie("fuzzy-search-noResults"))};return B("returnToIIB",async()=>{const i=await l.pushAction(we).res;t.value.expired=i.expired}),B("searchIndexExpired",()=>t.value&&(t.value.expired=!0)),(i,n)=>{const H=oe,z=re,W=ue,X=de,j=ge;return o(),k("div",{class:"container",ref_key:"stackViewEl",ref:T},[t.value?(o(),k("div",xe,[r(H,{value:p.value,"onUpdate:value":n[0]||(n[0]=s=>p.value=s),placeholder:i.$t("fuzzy-search-placeholder"),disabled:!e(l).isIdle,onKeydown:ne(M,["enter"])},null,8,["value","placeholder","disabled","onKeydown"]),t.value.expired||!t.value.img_count?(o(),m(z,{key:0,onClick:e(S),loading:!e(l).isIdle,type:"primary"},{default:u(()=>[w(v(t.value.img_count===0?i.$t("generateIndexHint"):i.$t("UpdateIndex")),1)]),_:1},8,["onClick","loading"])):(o(),m(z,{key:1,type:"primary",onClick:M,loading:!e(l).isIdle,disabled:!p.value},{default:u(()=>[w(v(i.$t("search")),1)]),_:1},8,["loading","disabled"]))])):g("",!0),r(j,{size:"large",spinning:!e(l).isIdle},{default:u(()=>[r(X,{visible:e(c),"onUpdate:visible":n[2]||(n[2]=s=>V(c)?c.value=s:null),width:"70vw","mask-closable":"",onOk:n[3]||(n[3]=s=>c.value=!1)},{cancelText:u(()=>[]),default:u(()=>[r(W,{active:"",loading:!e(N).isIdle},{default:u(()=>[F("div",{style:{width:"100%","word-break":"break-all","white-space":"pre-line","max-height":"70vh",overflow:"auto"},onDblclick:n[1]||(n[1]=s=>e(te)(e(_)))},[F("div",Ce,v(i.$t("doubleClickToCopy")),1),w(" "+v(e(_)),1)],32)]),_:1},8,["loading"])]),_:1},8,["visible"]),e(a)?(o(),m(e(fe),{key:0,ref_key:"scroller",ref:h,class:"file-list",items:e(a),"item-size":e(C).first,"key-field":"fullpath","item-secondary-size":e(C).second,gridItems:e(R),onScroll:e(O)},{default:u(({item:s,index:D})=>[r(ke,{idx:D,file:s,"show-menu-idx":e(f),"onUpdate:showMenuIdx":n[4]||(n[4]=J=>V(f)?f.value=J:null),onFileItemClick:e(L),"full-screen-preview-image-url":e(a)[e(d)]?e(ae)(e(a)[e(d)]):"","cell-width":e(K),selected:e(P).includes(D),onContextMenuClick:e(b),onDragstart:e(q),onDragend:e(G),onPreviewVisibleChange:e(U)},null,8,["idx","file","show-menu-idx","onFileItemClick","full-screen-preview-image-url","cell-width","selected","onContextMenuClick","onDragstart","onDragend","onPreviewVisibleChange"])]),_:1},8,["items","item-size","item-secondary-size","gridItems","onScroll"])):g("",!0),e(y)?(o(),k("div",_e,[r(e(pe),{onClick:n[5]||(n[5]=s=>e(I)("prev")),class:A({disable:!e(x)("prev")})},null,8,["class"]),r(e(me),{onClick:n[6]||(n[6]=s=>e(I)("next")),class:A({disable:!e(x)("next")})},null,8,["class"])])):g("",!0)]),_:1},8,["spinning"]),e(y)&&e(a)&&e(a)[e(d)]?(o(),m(ve,{key:1,file:e(a)[e(d)],idx:e(d),onContextMenuClick:e(b)},null,8,["file","idx","onContextMenuClick"])):g("",!0)],512)}}});const Ae=ce(he,[["__scopeId","data-v-bb005cb9"]]);export{Ae as default};

View File

@ -1 +0,0 @@
import{d as Y,$,aw as Z,bQ as ee,bP as B,o,y as k,c as r,r as e,bT as ae,m,n as d,x as w,v,E as f,s as V,p as A,t as ne,L as te,N as E,ax as le,ar as se,ai as ie,U as oe,V as re,Q as de,R as ue,X as ce}from"./index-23e5bc7c.js";import{h as pe,i as me,L as ve,R as fe,j as ge,S as ke}from"./fullScreenContextMenu-c5f9ce74.js";/* empty css */import{b as T,c as we,e as ye,u as Ie}from"./db-52d8ead8.js";import{u as xe}from"./hook-13bccbae.js";import"./shortcut-98354a10.js";const be={key:0,class:"search-bar"},Ce={class:"hint"},he={key:1,class:"preview-switch"},_e=Y({__name:"SubstrSearch",setup(Se){const{queue:s,images:t,onContextMenuClickU:y,stackViewEl:U,previewIdx:u,previewing:I,onPreviewVisibleChange:F,previewImgMove:x,canPreview:b,itemSize:C,gridItems:R,showGenInfo:c,imageGenInfo:h,q:N,multiSelectedIdxs:P,onFileItemClick:L,scroller:_,showMenuIdx:g,onFileDragStart:q,onFileDragEnd:G,cellWidth:K,onScroll:O,updateImageTag:Q}=xe(),p=$(""),n=$();Z(async()=>{n.value=await T(),n.value.img_count&&n.value.expired&&S()});const S=ee(()=>s.pushAction(async()=>(await Ie(),n.value=await T(),n.value)).res),M=async()=>{t.value=await s.pushAction(()=>ye(p.value)).res,await le(),Q(),_.value.scrollToItem(0),t.value.length||se.info(ie("fuzzy-search-noResults"))};return B("returnToIIB",async()=>{const i=await s.pushAction(we).res;n.value.expired=i.expired}),B("searchIndexExpired",()=>n.value&&(n.value.expired=!0)),(i,a)=>{const j=oe,z=re,H=de,W=ue,X=ke;return o(),k("div",{class:"container",ref_key:"stackViewEl",ref:U},[n.value?(o(),k("div",be,[r(j,{value:p.value,"onUpdate:value":a[0]||(a[0]=l=>p.value=l),placeholder:i.$t("fuzzy-search-placeholder"),disabled:!e(s).isIdle,onKeydown:ae(M,["enter"])},null,8,["value","placeholder","disabled","onKeydown"]),n.value.expired||!n.value.img_count?(o(),m(z,{key:0,onClick:e(S),loading:!e(s).isIdle,type:"primary"},{default:d(()=>[w(v(n.value.img_count===0?i.$t("generateIndexHint"):i.$t("UpdateIndex")),1)]),_:1},8,["onClick","loading"])):(o(),m(z,{key:1,type:"primary",onClick:M,loading:!e(s).isIdle,disabled:!p.value},{default:d(()=>[w(v(i.$t("search")),1)]),_:1},8,["loading","disabled"]))])):f("",!0),r(X,{size:"large",spinning:!e(s).isIdle},{default:d(()=>[r(W,{visible:e(c),"onUpdate:visible":a[2]||(a[2]=l=>V(c)?c.value=l:null),width:"70vw","mask-closable":"",onOk:a[3]||(a[3]=l=>c.value=!1)},{cancelText:d(()=>[]),default:d(()=>[r(H,{active:"",loading:!e(N).isIdle},{default:d(()=>[A("div",{style:{width:"100%","word-break":"break-all","white-space":"pre-line","max-height":"70vh",overflow:"auto"},onDblclick:a[1]||(a[1]=l=>e(ne)(e(h)))},[A("div",Ce,v(i.$t("doubleClickToCopy")),1),w(" "+v(e(h)),1)],32)]),_:1},8,["loading"])]),_:1},8,["visible"]),e(t)?(o(),m(e(pe),{key:0,ref_key:"scroller",ref:_,class:"file-list",items:e(t),"item-size":e(C).first,"key-field":"fullpath","item-secondary-size":e(C).second,gridItems:e(R),onScroll:e(O)},{default:d(({item:l,index:D})=>[r(me,{idx:D,file:l,"show-menu-idx":e(g),"onUpdate:showMenuIdx":a[4]||(a[4]=J=>V(g)?g.value=J:null),onFileItemClick:e(L),"full-screen-preview-image-url":e(t)[e(u)]?e(te)(e(t)[e(u)]):"","cell-width":e(K),selected:e(P).includes(D),onContextMenuClick:e(y),onDragstart:e(q),onDragend:e(G),onPreviewVisibleChange:e(F)},null,8,["idx","file","show-menu-idx","onFileItemClick","full-screen-preview-image-url","cell-width","selected","onContextMenuClick","onDragstart","onDragend","onPreviewVisibleChange"])]),_:1},8,["items","item-size","item-secondary-size","gridItems","onScroll"])):f("",!0),e(I)?(o(),k("div",he,[r(e(ve),{onClick:a[5]||(a[5]=l=>e(x)("prev")),class:E({disable:!e(b)("prev")})},null,8,["class"]),r(e(fe),{onClick:a[6]||(a[6]=l=>e(x)("next")),class:E({disable:!e(b)("next")})},null,8,["class"])])):f("",!0)]),_:1},8,["spinning"]),e(I)&&e(t)&&e(t)[e(u)]?(o(),m(ge,{key:1,file:e(t)[e(u)],idx:e(u),onContextMenuClick:e(y)},null,8,["file","idx","onContextMenuClick"])):f("",!0)],512)}}});const Ae=ce(_e,[["__scopeId","data-v-905bf6da"]]);export{Ae as default};

View File

@ -1 +0,0 @@
.search-bar[data-v-905bf6da]{padding:8px;display:flex}.preview-switch[data-v-905bf6da]{position:fixed;top:0;bottom:0;left:0;right:0;display:flex;align-items:center;justify-content:space-between;z-index:11111;pointer-events:none}.preview-switch>*[data-v-905bf6da]{color:#fff;margin:16px;font-size:4em;pointer-events:all;cursor:pointer}.preview-switch>*.disable[data-v-905bf6da]{opacity:0;pointer-events:none;cursor:none}.container[data-v-905bf6da]{background:var(--zp-secondary-background)}.container .file-list[data-v-905bf6da]{list-style:none;padding:8px;height:100%;overflow:auto;height:var(--pane-max-height);width:100%}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
.container[data-v-aab31da2]{background:var(--zp-secondary-background);height:100%;overflow:auto;display:flex;flex-direction:column}.container .actions-panel[data-v-aab31da2]{padding:8px;background-color:var(--zp-primary-background)}.container .file-list[data-v-aab31da2]{flex:1;list-style:none;padding:8px;height:var(--pane-max-height);width:100%}.container .file-list .hint[data-v-aab31da2]{text-align:center;font-size:2em;padding:30vh 128px 0}

View File

@ -0,0 +1 @@
import{d as v,c0 as C,bO as I,o as i,y as _,p as f,c,n as r,x as h,v as d,r as e,m as F,L as x,c1 as z,c2 as B,V as $,X as R}from"./index-af270b30.js";import{u as S,b as V,m as E,F as A,g as L}from"./FileItem-a4055f0b.js";import"./db-dbaa937e.js";import"./shortcut-c1bb7547.js";const T={class:"actions-panel actions"},N={key:0,class:"file-list"},U={class:"hint"},H=v({__name:"batchDownload",props:{tabIdx:{},paneIdx:{},id:{}},setup(O){const{stackViewEl:w}=S().toRefs(),{itemSize:p,gridItems:k,cellWidth:b}=V(),l=E(),{selectdFiles:n}=C(l),m=I(),y=async t=>{const s=z(t);s&&l.addFiles(s.nodes)},D=async()=>{m.pushAction(async()=>{const t=await B.value.post("/zip",{paths:n.value.map(o=>o.fullpath)},{responseType:"blob"}),s=window.URL.createObjectURL(new Blob([t.data])),a=document.createElement("a");a.href=s,a.setAttribute("download",`iib_${new Date().toLocaleString()}.zip`),document.body.appendChild(a),a.click()})},g=t=>{n.value.splice(t,1)};return(t,s)=>{const a=$;return i(),_("div",{class:"container",ref_key:"stackViewEl",ref:w,onDrop:y},[f("div",T,[c(a,{onClick:s[0]||(s[0]=o=>e(l).selectdFiles=[])},{default:r(()=>[h(d(t.$t("clear")),1)]),_:1}),c(a,{onClick:D,type:"primary",loading:!e(m).isIdle},{default:r(()=>[h(d(t.$t("zipDownload")),1)]),_:1},8,["loading"])]),e(n).length?(i(),F(e(L),{key:1,ref:"scroller",class:"file-list",items:e(n).slice(),"item-size":e(p).first,"key-field":"fullpath","item-secondary-size":e(p).second,gridItems:e(k)},{default:r(({item:o,index:u})=>[c(A,{idx:u,file:o,"cell-width":e(b),"enable-close-icon":"",onCloseIconClick:j=>g(u),"full-screen-preview-image-url":e(x)(o),"enable-right-click-menu":!1},null,8,["idx","file","cell-width","onCloseIconClick","full-screen-preview-image-url"])]),_:1},8,["items","item-size","item-secondary-size","gridItems"])):(i(),_("div",N,[f("p",U,d(t.$t("batchDownloaDDragAndDropHint")),1)]))],544)}}});const G=R(H,[["__scopeId","data-v-aab31da2"]]);export{G as default};

View File

@ -1 +1 @@
import{c6 as t}from"./index-23e5bc7c.js";const o=async()=>(await t.value.get("/db/basic_info")).data,c=async()=>(await t.value.get("/db/expired_dirs")).data,r=async()=>{await t.value.post("/db/update_image_data",{},{timeout:1/0})},d=async a=>(await t.value.post("/db/match_images_by_tags",a)).data,g=async a=>(await t.value.post("/db/add_custom_tag",a)).data,u=async a=>(await t.value.post("/db/toggle_custom_tag_to_img",a)).data,p=async a=>{await t.value.post("/db/remove_custom_tag",a)},i=async a=>(await t.value.get("/db/search_by_substr",{params:{substr:a}})).data,e="/db/scanned_paths",m=async a=>{await t.value.post(e,{path:a})},_=async a=>{await t.value.delete(e,{data:{path:a}})},b=async a=>(await t.value.post("/db/get_image_tags",{paths:a})).data;export{m as a,o as b,c,g as d,i as e,b as f,d as g,_ as h,p as r,u as t,r as u}; import{c2 as t}from"./index-af270b30.js";const o=async()=>(await t.value.get("/db/basic_info")).data,c=async()=>(await t.value.get("/db/expired_dirs")).data,r=async()=>{await t.value.post("/db/update_image_data",{},{timeout:1/0})},d=async a=>(await t.value.post("/db/match_images_by_tags",a)).data,g=async a=>(await t.value.post("/db/add_custom_tag",a)).data,u=async a=>(await t.value.post("/db/toggle_custom_tag_to_img",a)).data,p=async a=>{await t.value.post("/db/remove_custom_tag",a)},i=async a=>(await t.value.get("/db/search_by_substr",{params:{substr:a}})).data,e="/db/scanned_paths",m=async a=>{await t.value.post(e,{path:a})},_=async a=>{await t.value.delete(e,{data:{path:a}})},b=async a=>(await t.value.post("/db/get_image_tags",{paths:a})).data;export{m as a,o as b,c,g as d,i as e,b as f,d as g,_ as h,p as r,u as t,r as u};

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
.ant-alert{box-sizing:border-box;margin:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:flex;align-items:center;padding:8px 15px;word-wrap:break-word;border-radius:2px}.ant-alert-content{flex:1;min-width:0}.ant-alert-icon{margin-right:8px}.ant-alert-description{display:none;font-size:14px;line-height:22px}.ant-alert-success{background-color:#f6ffed;border:1px solid #b7eb8f}.ant-alert-success .ant-alert-icon{color:#52c41a}.ant-alert-info{background-color:#fff1e6;border:1px solid #f7ae83}.ant-alert-info .ant-alert-icon{color:#d03f0a}.ant-alert-warning{background-color:#fffbe6;border:1px solid #ffe58f}.ant-alert-warning .ant-alert-icon{color:#faad14}.ant-alert-error{background-color:#fff2f0;border:1px solid #ffccc7}.ant-alert-error .ant-alert-icon{color:#ff4d4f}.ant-alert-error .ant-alert-description>pre{margin:0;padding:0}.ant-alert-action{margin-left:8px}.ant-alert-close-icon{margin-left:8px;padding:0;overflow:hidden;font-size:12px;line-height:12px;background-color:transparent;border:none;outline:none;cursor:pointer}.ant-alert-close-icon .anticon-close{color:#00000073;transition:color .3s}.ant-alert-close-icon .anticon-close:hover{color:#000000bf}.ant-alert-close-text{color:#00000073;transition:color .3s}.ant-alert-close-text:hover{color:#000000bf}.ant-alert-with-description{align-items:flex-start;padding:15px 15px 15px 24px}.ant-alert-with-description.ant-alert-no-icon{padding:15px}.ant-alert-with-description .ant-alert-icon{margin-right:15px;font-size:24px}.ant-alert-with-description .ant-alert-message{display:block;margin-bottom:4px;color:#000000d9;font-size:16px}.ant-alert-message{color:#000000d9}.ant-alert-with-description .ant-alert-description{display:block}.ant-alert.ant-alert-motion-leave{overflow:hidden;opacity:1;transition:max-height .3s cubic-bezier(.78,.14,.15,.86),opacity .3s cubic-bezier(.78,.14,.15,.86),padding-top .3s cubic-bezier(.78,.14,.15,.86),padding-bottom .3s cubic-bezier(.78,.14,.15,.86),margin-bottom .3s cubic-bezier(.78,.14,.15,.86)}.ant-alert.ant-alert-motion-leave-active{max-height:0;margin-bottom:0!important;padding-top:0;padding-bottom:0;opacity:0}.ant-alert-banner{margin-bottom:0;border:0;border-radius:0}.ant-alert.ant-alert-rtl{direction:rtl}.ant-alert-rtl .ant-alert-icon{margin-right:auto;margin-left:8px}.ant-alert-rtl .ant-alert-action,.ant-alert-rtl .ant-alert-close-icon{margin-right:8px;margin-left:auto}.ant-alert-rtl.ant-alert-with-description{padding-right:24px;padding-left:15px}.ant-alert-rtl.ant-alert-with-description .ant-alert-icon{margin-right:auto;margin-left:15px}.access-mode-message[data-v-903b3fda]{display:flex;flex-direction:row;align-items:center}.access-mode-message a[data-v-903b3fda]{margin-left:16px}.container[data-v-903b3fda]{padding:20px;background-color:var(--zp-secondary-background);height:100%;overflow:auto}.header[data-v-903b3fda]{display:flex;justify-content:space-between;align-items:center}.header h1[data-v-903b3fda]{font-size:28px;font-weight:700;color:var(--zp-primary);margin:0}.last-record[data-v-903b3fda]{margin-left:16px;font-size:14px;color:var(--zp-secondary)}.last-record a[data-v-903b3fda]{text-decoration:none;color:var(--zp-secondary)}.last-record a[data-v-903b3fda]:hover{color:var(--zp-primary)}.content[data-v-903b3fda]{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));grid-gap:20px;margin-top:16px}.feature-item[data-v-903b3fda]{background-color:var(--zp-primary-background);border-radius:8px;box-shadow:0 1px 2px #0000001a;padding:20px}.feature-item ul[data-v-903b3fda]{list-style:none;padding:4px;max-height:70vh;overflow-y:auto}.feature-item .item[data-v-903b3fda]{margin-bottom:10px;padding:4px 8px;display:flex;align-items:center}.feature-item .item[data-v-903b3fda]:hover{background:var(--zp-secondary-background);border-radius:4px;color:var(--primary-color);cursor:pointer}.feature-item .icon[data-v-903b3fda]{margin-right:8px}.feature-item h2[data-v-903b3fda]{margin-top:0;margin-bottom:20px;font-size:20px;font-weight:700;color:var(--zp-primary)}.text[data-v-903b3fda]{flex:1;font-size:16px} .ant-alert{box-sizing:border-box;margin:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:flex;align-items:center;padding:8px 15px;word-wrap:break-word;border-radius:2px}.ant-alert-content{flex:1;min-width:0}.ant-alert-icon{margin-right:8px}.ant-alert-description{display:none;font-size:14px;line-height:22px}.ant-alert-success{background-color:#f6ffed;border:1px solid #b7eb8f}.ant-alert-success .ant-alert-icon{color:#52c41a}.ant-alert-info{background-color:#fff1e6;border:1px solid #f7ae83}.ant-alert-info .ant-alert-icon{color:#d03f0a}.ant-alert-warning{background-color:#fffbe6;border:1px solid #ffe58f}.ant-alert-warning .ant-alert-icon{color:#faad14}.ant-alert-error{background-color:#fff2f0;border:1px solid #ffccc7}.ant-alert-error .ant-alert-icon{color:#ff4d4f}.ant-alert-error .ant-alert-description>pre{margin:0;padding:0}.ant-alert-action{margin-left:8px}.ant-alert-close-icon{margin-left:8px;padding:0;overflow:hidden;font-size:12px;line-height:12px;background-color:transparent;border:none;outline:none;cursor:pointer}.ant-alert-close-icon .anticon-close{color:#00000073;transition:color .3s}.ant-alert-close-icon .anticon-close:hover{color:#000000bf}.ant-alert-close-text{color:#00000073;transition:color .3s}.ant-alert-close-text:hover{color:#000000bf}.ant-alert-with-description{align-items:flex-start;padding:15px 15px 15px 24px}.ant-alert-with-description.ant-alert-no-icon{padding:15px}.ant-alert-with-description .ant-alert-icon{margin-right:15px;font-size:24px}.ant-alert-with-description .ant-alert-message{display:block;margin-bottom:4px;color:#000000d9;font-size:16px}.ant-alert-message{color:#000000d9}.ant-alert-with-description .ant-alert-description{display:block}.ant-alert.ant-alert-motion-leave{overflow:hidden;opacity:1;transition:max-height .3s cubic-bezier(.78,.14,.15,.86),opacity .3s cubic-bezier(.78,.14,.15,.86),padding-top .3s cubic-bezier(.78,.14,.15,.86),padding-bottom .3s cubic-bezier(.78,.14,.15,.86),margin-bottom .3s cubic-bezier(.78,.14,.15,.86)}.ant-alert.ant-alert-motion-leave-active{max-height:0;margin-bottom:0!important;padding-top:0;padding-bottom:0;opacity:0}.ant-alert-banner{margin-bottom:0;border:0;border-radius:0}.ant-alert.ant-alert-rtl{direction:rtl}.ant-alert-rtl .ant-alert-icon{margin-right:auto;margin-left:8px}.ant-alert-rtl .ant-alert-action,.ant-alert-rtl .ant-alert-close-icon{margin-right:8px;margin-left:auto}.ant-alert-rtl.ant-alert-with-description{padding-right:24px;padding-left:15px}.ant-alert-rtl.ant-alert-with-description .ant-alert-icon{margin-right:auto;margin-left:15px}.access-mode-message[data-v-81c5212e]{display:flex;flex-direction:row;align-items:center}.access-mode-message a[data-v-81c5212e]{margin-left:16px}.container[data-v-81c5212e]{padding:20px;background-color:var(--zp-secondary-background);height:100%;overflow:auto}.header[data-v-81c5212e]{display:flex;justify-content:space-between;align-items:center}.header h1[data-v-81c5212e]{font-size:28px;font-weight:700;color:var(--zp-primary);margin:0}.last-record[data-v-81c5212e]{margin-left:16px;font-size:14px;color:var(--zp-secondary)}.last-record a[data-v-81c5212e]{text-decoration:none;color:var(--zp-secondary)}.last-record a[data-v-81c5212e]:hover{color:var(--zp-primary)}.content[data-v-81c5212e]{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));grid-gap:20px;margin-top:16px}.feature-item[data-v-81c5212e]{background-color:var(--zp-primary-background);border-radius:8px;box-shadow:0 1px 2px #0000001a;padding:20px}.feature-item ul[data-v-81c5212e]{list-style:none;padding:4px;max-height:70vh;overflow-y:auto}.feature-item .item[data-v-81c5212e]{margin-bottom:10px;padding:4px 8px;display:flex;align-items:center}.feature-item .item[data-v-81c5212e]:hover{background:var(--zp-secondary-background);border-radius:4px;color:var(--primary-color);cursor:pointer}.feature-item .icon[data-v-81c5212e]{margin-right:8px}.feature-item h2[data-v-81c5212e]{margin-top:0;margin-bottom:20px;font-size:20px;font-weight:700;color:var(--zp-primary)}.text[data-v-81c5212e]{flex:1;font-size:16px}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
.ant-spin{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:absolute;display:none;color:#d03f0a;text-align:center;vertical-align:middle;opacity:0;transition:transform .3s cubic-bezier(.78,.14,.15,.86)}.ant-spin-spinning{position:static;display:inline-block;opacity:1}.ant-spin-nested-loading{position:relative}.ant-spin-nested-loading>div>.ant-spin{position:absolute;top:0;left:0;z-index:4;display:block;width:100%;height:100%;max-height:400px}.ant-spin-nested-loading>div>.ant-spin .ant-spin-dot{position:absolute;top:50%;left:50%;margin:-10px}.ant-spin-nested-loading>div>.ant-spin .ant-spin-text{position:absolute;top:50%;width:100%;padding-top:5px;text-shadow:0 1px 2px #fff}.ant-spin-nested-loading>div>.ant-spin.ant-spin-show-text .ant-spin-dot{margin-top:-20px}.ant-spin-nested-loading>div>.ant-spin-sm .ant-spin-dot{margin:-7px}.ant-spin-nested-loading>div>.ant-spin-sm .ant-spin-text{padding-top:2px}.ant-spin-nested-loading>div>.ant-spin-sm.ant-spin-show-text .ant-spin-dot{margin-top:-17px}.ant-spin-nested-loading>div>.ant-spin-lg .ant-spin-dot{margin:-16px}.ant-spin-nested-loading>div>.ant-spin-lg .ant-spin-text{padding-top:11px}.ant-spin-nested-loading>div>.ant-spin-lg.ant-spin-show-text .ant-spin-dot{margin-top:-26px}.ant-spin-container{position:relative;transition:opacity .3s}.ant-spin-container:after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:10;display:none \ ;width:100%;height:100%;background:#fff;opacity:0;transition:all .3s;content:"";pointer-events:none}.ant-spin-blur{clear:both;opacity:.5;user-select:none;pointer-events:none}.ant-spin-blur:after{opacity:.4;pointer-events:auto}.ant-spin-tip{color:#00000073}.ant-spin-dot{position:relative;display:inline-block;font-size:20px;width:1em;height:1em}.ant-spin-dot-item{position:absolute;display:block;width:9px;height:9px;background-color:#d03f0a;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.ant-spin-dot-item:nth-child(1){top:0;left:0}.ant-spin-dot-item:nth-child(2){top:0;right:0;animation-delay:.4s}.ant-spin-dot-item:nth-child(3){right:0;bottom:0;animation-delay:.8s}.ant-spin-dot-item:nth-child(4){bottom:0;left:0;animation-delay:1.2s}.ant-spin-dot-spin{transform:rotate(45deg);animation:antRotate 1.2s infinite linear}.ant-spin-sm .ant-spin-dot{font-size:14px}.ant-spin-sm .ant-spin-dot i{width:6px;height:6px}.ant-spin-lg .ant-spin-dot{font-size:32px}.ant-spin-lg .ant-spin-dot i{width:14px;height:14px}.ant-spin.ant-spin-show-text .ant-spin-text{display:block}@media all and (-ms-high-contrast: none),(-ms-high-contrast: active){.ant-spin-blur{background:#fff;opacity:.5}}@keyframes antSpinMove{to{opacity:1}}@keyframes antRotate{to{transform:rotate(405deg)}}.ant-spin-rtl{direction:rtl}.ant-spin-rtl .ant-spin-dot-spin{transform:rotate(-45deg);animation-name:antRotateRtl}@keyframes antRotateRtl{to{transform:rotate(-405deg)}}.full-screen-menu[data-v-c968315f]{position:fixed;z-index:99999;background:var(--zp-primary-background);padding:8px 16px;box-shadow:0 0 4px var(--zp-secondary);border-radius:4px}.full-screen-menu .tags-container>*[data-v-c968315f]{margin-right:4px;font-size:14px;line-height:1.6}.full-screen-menu .container[data-v-c968315f]{height:100%;display:flex;overflow:hidden;flex-direction:column}.full-screen-menu .gen-info[data-v-c968315f]{flex:1;word-break:break-all;white-space:pre-line;overflow:auto;z-index:1;padding-top:4px;position:relative}.full-screen-menu .gen-info .tags .tag[data-v-c968315f]{display:inline-block;overflow:hidden;border-radius:4px;margin-right:8px;border:2px solid var(--zp-primary)}.full-screen-menu .gen-info .tags .name[data-v-c968315f]{background-color:var(--zp-primary);color:var(--zp-primary-background);padding:4px}.full-screen-menu .gen-info .tags .value[data-v-c968315f]{padding:4px}.full-screen-menu.unset-size[data-v-c968315f]{width:unset!important;height:unset!important}.full-screen-menu .mouse-sensor[data-v-c968315f]{position:absolute;bottom:0;right:0;transform:rotate(90deg);cursor:se-resize;z-index:1;background:var(--zp-primary-background);border-radius:2px}.full-screen-menu .mouse-sensor>*[data-v-c968315f]{font-size:18px;padding:4px}.full-screen-menu .action-bar[data-v-c968315f]{display:flex;align-items:center;user-select:none}.full-screen-menu .action-bar .icon[data-v-c968315f]{font-size:1.5em;padding:2px 4px;border-radius:4px}.full-screen-menu .action-bar .icon[data-v-c968315f]:hover{background:var(--zp-secondary-variant-background)}.full-screen-menu .action-bar>*[data-v-c968315f]{flex-wrap:wrap}.full-screen-menu .action-bar>*[data-v-c968315f]:not(:last-child){margin-right:8px}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
import{$ as D,bO as E,bd as P,aC as $}from"./index-23e5bc7c.js";import{k as z,u as G,b as L,f as O,a as Q,c as R,d as V,e as _,l as A}from"./fullScreenContextMenu-c5f9ce74.js";const U=()=>{const e=D(),c=E(),u=z(),n={tabIdx:-1,target:"local",paneIdx:-1,walkMode:!1},{stackViewEl:r,multiSelectedIdxs:d,stack:m,scroller:o}=G({images:e}).toRefs(),{itemSize:g,gridItems:p,cellWidth:v}=L(n),{showMenuIdx:I}=O();Q(n);const{onFileDragStart:f,onFileDragEnd:x}=R(),{showGenInfo:h,imageGenInfo:w,q:k,onContextMenuClick:i,onFileItemClick:S}=V(n,{openNext:P}),{previewIdx:M,previewing:b,onPreviewVisibleChange:C,previewImgMove:F,canPreview:y}=_(n),T=async(s,a,t)=>{m.value=[{curr:"",files:e.value}],await i(s,a,t)};A("removeFiles",async({paths:s})=>{var a;e.value=(a=e.value)==null?void 0:a.filter(t=>!s.includes(t.fullpath))});const l=()=>{const s=o.value;if(s&&e.value){const a=e.value.slice(Math.max(s.$_startIndex-10,0),s.$_endIndex+10).map(t=>t.fullpath);u.fetchImageTags(a)}},q=$(l,300);return{scroller:o,queue:c,images:e,onContextMenuClickU:T,stackViewEl:r,previewIdx:M,previewing:b,onPreviewVisibleChange:C,previewImgMove:F,canPreview:y,itemSize:g,gridItems:p,showGenInfo:h,imageGenInfo:w,q:k,onContextMenuClick:i,onFileItemClick:S,showMenuIdx:I,multiSelectedIdxs:d,onFileDragStart:f,onFileDragEnd:x,cellWidth:v,onScroll:q,updateImageTag:l}};export{U as u};

1
vue/dist/assets/hook-c56860bd.js vendored Normal file
View File

@ -0,0 +1 @@
import{$ as q,bO as D,bd as E,aC as P}from"./index-af270b30.js";import{h as $,u as z,b as G,f as L,a as O,c as Q,d as R,e as V,i as _}from"./FileItem-a4055f0b.js";const N=()=>{const e=q(),c=D(),l=$(),{stackViewEl:u,multiSelectedIdxs:r,stack:m,scroller:n}=z({images:e}).toRefs(),{itemSize:v,gridItems:d,cellWidth:g}=G(),{showMenuIdx:f}=L();O();const{onFileDragStart:I,onFileDragEnd:p}=Q(),{showGenInfo:h,imageGenInfo:w,q:x,onContextMenuClick:o,onFileItemClick:S}=R({openNext:E}),{previewIdx:C,previewing:F,onPreviewVisibleChange:b,previewImgMove:k,canPreview:M}=V(),y=async(s,t,a)=>{m.value=[{curr:"",files:e.value}],await o(s,t,a)};_("removeFiles",async({paths:s})=>{var t;e.value=(t=e.value)==null?void 0:t.filter(a=>!s.includes(a.fullpath))});const i=()=>{const s=n.value;if(s&&e.value){const t=e.value.slice(Math.max(s.$_startIndex-10,0),s.$_endIndex+10).map(a=>a.fullpath);l.fetchImageTags(t)}},T=P(i,300);return{scroller:n,queue:c,images:e,onContextMenuClickU:y,stackViewEl:u,previewIdx:C,previewing:F,onPreviewVisibleChange:b,previewImgMove:k,canPreview:M,itemSize:v,gridItems:d,showGenInfo:h,imageGenInfo:w,q:x,onContextMenuClick:o,onFileItemClick:S,showMenuIdx:f,multiSelectedIdxs:r,onFileDragStart:I,onFileDragEnd:p,cellWidth:g,onScroll:T,updateImageTag:i}};export{N as u};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{cH as s}from"./index-23e5bc7c.js";var r=1/0,i=17976931348623157e292;function e(t){if(!t)return t===0?t:0;if(t=s(t),t===r||t===-r){var n=t<0?-1:1;return n*i}return t===t?t:0}function f(t){var n=t==null?0:t.length;return n?t[n-1]:void 0}const h=t=>{const n=[];return t.shiftKey&&n.push("Shift"),t.ctrlKey&&n.push("Ctrl"),t.metaKey&&n.push("Cmd"),(t.code.startsWith("Key")||t.code.startsWith("Digit"))&&n.push(t.code),n.join(" + ")};export{h as g,f as l,e as t}; import{cK as s}from"./index-af270b30.js";var r=1/0,i=17976931348623157e292;function e(t){if(!t)return t===0?t:0;if(t=s(t),t===r||t===-r){var n=t<0?-1:1;return n*i}return t===t?t:0}function f(t){var n=t==null?0:t.length;return n?t[n-1]:void 0}const h=t=>{const n=[];return t.shiftKey&&n.push("Shift"),t.ctrlKey&&n.push("Ctrl"),t.metaKey&&n.push("Cmd"),(t.code.startsWith("Key")||t.code.startsWith("Digit"))&&n.push(t.code),n.join(" + ")};export{h as g,f as l,e as t};

1
vue/dist/assets/stackView-5134a008.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
.ant-breadcrumb{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";color:#00000073;font-size:14px}.ant-breadcrumb .anticon{font-size:14px}.ant-breadcrumb a{color:#00000073;transition:color .3s}.ant-breadcrumb a:hover{color:#de632f}.ant-breadcrumb>span:last-child{color:#000000d9}.ant-breadcrumb>span:last-child a{color:#000000d9}.ant-breadcrumb>span:last-child .ant-breadcrumb-separator{display:none}.ant-breadcrumb-separator{margin:0 8px;color:#00000073}.ant-breadcrumb-link>.anticon+span,.ant-breadcrumb-link>.anticon+a{margin-left:4px}.ant-breadcrumb-overlay-link>.anticon{margin-left:4px}.ant-breadcrumb-rtl{direction:rtl}.ant-breadcrumb-rtl:before{display:table;content:""}.ant-breadcrumb-rtl:after{display:table;clear:both;content:""}.ant-breadcrumb-rtl>span{float:right}.ant-breadcrumb-rtl .ant-breadcrumb-link>.anticon+span,.ant-breadcrumb-rtl .ant-breadcrumb-link>.anticon+a{margin-right:4px;margin-left:0}.ant-breadcrumb-rtl .ant-breadcrumb-overlay-link>.anticon{margin-right:4px;margin-left:0}.nprogress{pointer-events:none}.nprogress .bar{background:#29d;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}.nprogress .peg{display:block;position:absolute;right:0px;width:100px;height:100%;box-shadow:0 0 10px #29d,0 0 5px #29d;opacity:1;-webkit-transform:rotate(3deg) translate(0px,-4px);-ms-transform:rotate(3deg) translate(0px,-4px);transform:rotate(3deg) translateY(-4px)}.nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}.nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border:solid 2px transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;-webkit-animation:nprogress-spinner .4s linear infinite;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent .nprogress .spinner,.nprogress-custom-parent .nprogress .bar{position:absolute}@-webkit-keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(360deg)}}@keyframes nprogress-spinner{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.preview-switch[data-v-43659a67]{position:fixed;top:0;bottom:0;left:0;right:0;display:flex;align-items:center;justify-content:space-between;z-index:11111;pointer-events:none}.preview-switch>*[data-v-43659a67]{color:#fff;margin:16px;font-size:4em;pointer-events:all;cursor:pointer}.preview-switch>*.disable[data-v-43659a67]{opacity:0;pointer-events:none;cursor:none}.breadcrumb[data-v-43659a67]{display:flex;align-items:center}.breadcrumb>*[data-v-43659a67]{margin-right:4px}.container[data-v-43659a67]{background:var(--zp-secondary-background);height:var(--pane-max-height)}.location-bar[data-v-43659a67]{padding:4px 16px;background:var(--zp-primary-background);border-bottom:1px solid var(--zp-border);display:flex;align-items:center;justify-content:space-between}.location-bar .actions[data-v-43659a67]{display:flex;align-items:center;flex-shrink:0}.location-bar a.opt[data-v-43659a67]{margin-left:8px}.view[data-v-43659a67]{padding:8px;height:calc(100vh - 48px)}.view .file-list[data-v-43659a67]{list-style:none;padding:8px;height:100%;overflow:auto}.hint[data-v-43659a67]{padding:4px;border:4px;background:var(--zp-secondary-background);border:1px solid var(--zp-border)} .ant-breadcrumb{box-sizing:border-box;margin:0;padding:0;color:#000000d9;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";color:#00000073;font-size:14px}.ant-breadcrumb .anticon{font-size:14px}.ant-breadcrumb a{color:#00000073;transition:color .3s}.ant-breadcrumb a:hover{color:#de632f}.ant-breadcrumb>span:last-child{color:#000000d9}.ant-breadcrumb>span:last-child a{color:#000000d9}.ant-breadcrumb>span:last-child .ant-breadcrumb-separator{display:none}.ant-breadcrumb-separator{margin:0 8px;color:#00000073}.ant-breadcrumb-link>.anticon+span,.ant-breadcrumb-link>.anticon+a{margin-left:4px}.ant-breadcrumb-overlay-link>.anticon{margin-left:4px}.ant-breadcrumb-rtl{direction:rtl}.ant-breadcrumb-rtl:before{display:table;content:""}.ant-breadcrumb-rtl:after{display:table;clear:both;content:""}.ant-breadcrumb-rtl>span{float:right}.ant-breadcrumb-rtl .ant-breadcrumb-link>.anticon+span,.ant-breadcrumb-rtl .ant-breadcrumb-link>.anticon+a{margin-right:4px;margin-left:0}.ant-breadcrumb-rtl .ant-breadcrumb-overlay-link>.anticon{margin-right:4px;margin-left:0}.nprogress{pointer-events:none}.nprogress .bar{background:#29d;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}.nprogress .peg{display:block;position:absolute;right:0px;width:100px;height:100%;box-shadow:0 0 10px #29d,0 0 5px #29d;opacity:1;-webkit-transform:rotate(3deg) translate(0px,-4px);-ms-transform:rotate(3deg) translate(0px,-4px);transform:rotate(3deg) translateY(-4px)}.nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}.nprogress .spinner-icon{width:18px;height:18px;box-sizing:border-box;border:solid 2px transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;-webkit-animation:nprogress-spinner .4s linear infinite;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent .nprogress .spinner,.nprogress-custom-parent .nprogress .bar{position:absolute}@-webkit-keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(360deg)}}@keyframes nprogress-spinner{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.preview-switch[data-v-4d3aae06]{position:fixed;top:0;bottom:0;left:0;right:0;display:flex;align-items:center;justify-content:space-between;z-index:11111;pointer-events:none}.preview-switch>*[data-v-4d3aae06]{color:#fff;margin:16px;font-size:4em;pointer-events:all;cursor:pointer}.preview-switch>*.disable[data-v-4d3aae06]{opacity:0;pointer-events:none;cursor:none}.breadcrumb[data-v-4d3aae06]{display:flex;align-items:center}.breadcrumb>*[data-v-4d3aae06]{margin-right:4px}.container[data-v-4d3aae06]{background:var(--zp-secondary-background);height:var(--pane-max-height)}.location-bar[data-v-4d3aae06]{padding:4px 16px;background:var(--zp-primary-background);border-bottom:1px solid var(--zp-border);display:flex;align-items:center;justify-content:space-between}.location-bar .actions[data-v-4d3aae06]{display:flex;align-items:center;flex-shrink:0}.location-bar a.opt[data-v-4d3aae06]{margin-left:8px}.view[data-v-4d3aae06]{padding:8px;height:calc(100vh - 48px)}.view .file-list[data-v-4d3aae06]{list-style:none;padding:8px;height:100%;overflow:auto}.hint[data-v-4d3aae06]{padding:4px;border:4px;background:var(--zp-secondary-background);border:1px solid var(--zp-border)}

File diff suppressed because one or more lines are too long

4
vue/dist/index.html vendored
View File

@ -7,8 +7,8 @@
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Infinite Image Browsing</title> <title>Infinite Image Browsing</title>
<script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-23e5bc7c.js"></script> <script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-af270b30.js"></script>
<link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-618900f2.css"> <link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-90388ea3.css">
</head> </head>
<body> <body>

View File

@ -46,6 +46,7 @@ const tags = computed(() => {
<a-menu-item key="send2outpaint">openOutpaint</a-menu-item> <a-menu-item key="send2outpaint">openOutpaint</a-menu-item>
</a-sub-menu> </a-sub-menu>
</template> </template>
<a-menu-item key="send2BatchDownload">{{ $t('sendToBatchDownload') }}</a-menu-item>
<a-menu-item key="send2savedDir">{{ $t('send2savedDir') }}</a-menu-item> <a-menu-item key="send2savedDir">{{ $t('send2savedDir') }}</a-menu-item>
<a-menu-divider /> <a-menu-divider />
<a-sub-menu key="toggle-tag" :title="$t('toggleTag')"> <a-sub-menu key="toggle-tag" :title="$t('toggleTag')">

View File

@ -4,11 +4,12 @@ import { useGlobalStore } from '@/store/useGlobalStore'
import { fallbackImage } from 'vue3-ts-util' import { fallbackImage } from 'vue3-ts-util'
import type { FileNodeInfo } from '@/api/files' import type { FileNodeInfo } from '@/api/files'
import { isImageFile } from '@/util' import { isImageFile } from '@/util'
import { toImageThumbnailUrl, toRawFileUrl } from './hook' import { toImageThumbnailUrl, toRawFileUrl } from '@/util/file'
import type { MenuInfo } from 'ant-design-vue/lib/menu/src/interface' import type { MenuInfo } from 'ant-design-vue/lib/menu/src/interface'
import { computed } from 'vue' import { computed } from 'vue'
import ContextMenu from './ContextMenu.vue' import ContextMenu from './ContextMenu.vue'
import { useTagStore } from '@/store/useTagStore' import { useTagStore } from '@/store/useTagStore'
import { CloseCircleOutlined } from '@/icon'
const global = useGlobalStore() const global = useGlobalStore()
const tagStore = useTagStore() const tagStore = useTagStore()
@ -20,17 +21,20 @@ const props = withDefaults(
showMenuIdx?: number showMenuIdx?: number
cellWidth: number cellWidth: number
fullScreenPreviewImageUrl?: string fullScreenPreviewImageUrl?: string
enableRightClickMenu: boolean,
enableCloseIcon: boolean
}>(), }>(),
{ selected: false } { selected: false, enableRightClickMenu: true, enableCloseIcon: false }
) )
const emit = defineEmits<{ const emit = defineEmits<{
(type: 'update:showMenuIdx', v: number): void 'update:showMenuIdx': [v: number],
(type: 'fileItemClick', event: MouseEvent, file: FileNodeInfo, idx: number): void 'fileItemClick': [event: MouseEvent, file: FileNodeInfo, idx: number],
(type: 'dragstart', event: DragEvent, idx: number): void 'dragstart': [event: DragEvent, idx: number],
(type: 'dragend', event: DragEvent, idx: number): void 'dragend': [event: DragEvent, idx: number],
(type: 'previewVisibleChange', value: boolean, last: boolean): void 'previewVisibleChange': [value: boolean, last: boolean],
(type: 'contextMenuClick', e: MenuInfo, file: FileNodeInfo, idx: number): void 'contextMenuClick': [e: MenuInfo, file: FileNodeInfo, idx: number],
'close-icon-click': []
}>() }>()
const customTags = computed(() => { const customTags = computed(() => {
@ -52,7 +56,10 @@ const imageSrc = computed(() => {
@dragend="emit('dragend', $event, idx)" @click.capture="emit('fileItemClick', $event, file, idx)"> @dragend="emit('dragend', $event, idx)" @click.capture="emit('fileItemClick', $event, file, idx)">
<div> <div>
<a-dropdown> <div class="close-icon" v-if="enableCloseIcon" @click="emit('close-icon-click')">
<close-circle-outlined />
</div>
<a-dropdown v-if="enableRightClickMenu">
<div class="more"> <div class="more">
<ellipsis-outlined /> <ellipsis-outlined />
</div> </div>
@ -95,7 +102,7 @@ const imageSrc = computed(() => {
</div> </div>
</li> </li>
<template #overlay> <template #overlay>
<context-menu :file="file" :idx="idx" :selected-tag="customTags" <context-menu :file="file" :idx="idx" :selected-tag="customTags" v-if="enableRightClickMenu"
@context-menu-click="(e, f, i) => emit('contextMenuClick', e, f, i)" /> @context-menu-click="(e, f, i) => emit('contextMenuClick', e, f, i)" />
</template> </template>
</a-dropdown> </a-dropdown>
@ -123,6 +130,19 @@ const imageSrc = computed(() => {
} }
} }
.close-icon {
position: absolute;
top: 0;
right: 0;
transform: translate(50%, -50%) scale(1.5);
cursor: pointer;
z-index: 100;
border-radius: 100%;
overflow: hidden;
line-height: 1;
background-color: var(--zp-primary-background);
}
.file { .file {
padding: 8px 16px; padding: 8px 16px;
margin: 8px; margin: 8px;
@ -132,7 +152,6 @@ const imageSrc = computed(() => {
border-radius: 8px; border-radius: 8px;
box-shadow: 0 0 4px var(--zp-secondary-variant-background); box-shadow: 0 0 4px var(--zp-secondary-variant-background);
position: relative; position: relative;
overflow: hidden;
&:hover .more { &:hover .more {
opacity: 1; opacity: 1;
@ -220,6 +239,7 @@ const imageSrc = computed(() => {
} }
.basic-info { .basic-info {
overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-end; align-items: flex-end;

View File

@ -51,6 +51,7 @@ const zh = {
sendToImg2img: '发送到图生图', sendToImg2img: '发送到图生图',
sendToInpaint: '发送到局部重绘', sendToInpaint: '发送到局部重绘',
sendToControlNet: '发送到ControlNet', sendToControlNet: '发送到ControlNet',
sendToBatchDownload: '发送到批量下载',
sendToExtraFeatures: '发送到附加功能', sendToExtraFeatures: '发送到附加功能',
loadNextPage: '加载下一页', loadNextPage: '加载下一页',
localFile: '本地文件', localFile: '本地文件',
@ -185,11 +186,21 @@ const zh = {
inputFolderName: '输入文件夹名', inputFolderName: '输入文件夹名',
createFolder: '创建文件夹', createFolder: '创建文件夹',
sendToThirdPartyExtension: '发送到第三方拓展', sendToThirdPartyExtension: '发送到第三方拓展',
lyco: 'LyCORIS' lyco: 'LyCORIS',
batchDownloaDDragAndDropHint:
'使用拖拽或者右键菜单中的“发送到批量下载”将其他页面的图片添加到这里,支持多选',
zipDownload: '打包成zip下载',
archive: '归档',
batchDownload: '批量下载'
} }
const en: Record<keyof typeof zh, string> = { const en: Record<keyof typeof zh, string> = {
//! MissingTranslations: "Mark missing translations like this""shortcutKey": "Keyboard Shortcuts", //! MissingTranslations: "Mark missing translations like this""shortcutKey": "Keyboard Shortcuts",
//! MissingTranslations //! MissingTranslations
batchDownload: 'Batch Download',
archive: 'Archive',
zipDownload: 'Download as ZIP',
batchDownloaDDragAndDropHint:
"Use drag and drop or the 'Send to Batch Download' option in the right-click menu to add images from other pages here. Multiple selections are supported.",
lyco: 'LyCORIS', lyco: 'LyCORIS',
sendToThirdPartyExtension: 'Send to third-party extension', sendToThirdPartyExtension: 'Send to third-party extension',
createFolder: 'Create Folder', createFolder: 'Create Folder',
@ -354,6 +365,7 @@ const en: Record<keyof typeof zh, string> = {
sendToTxt2img: 'Send to txt2img', sendToTxt2img: 'Send to txt2img',
sendToImg2img: 'Send to img2img', sendToImg2img: 'Send to img2img',
sendToInpaint: 'Send to Inpaint', sendToInpaint: 'Send to Inpaint',
sendToBatchDownload: 'Send to BatchDownload',
sendToExtraFeatures: 'Send to Extra', sendToExtraFeatures: 'Send to Extra',
sendToControlNet: 'Send to ControlNet', sendToControlNet: 'Send to ControlNet',
loadNextPage: 'Load next page', loadNextPage: 'Load next page',

View File

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { useImgSliStore } from '@/store/useImgSli' import { useImgSliStore } from '@/store/useImgSli'
import { isFileTransferData, toImageThumbnailUrl, toRawFileUrl } from '@/page/fileTransfer/util' import { toImageThumbnailUrl, toRawFileUrl, getFileTransferDataFromDragEvent } from '@/util/file'
import { CloseCircleOutlined } from '@/icon' import { CloseCircleOutlined } from '@/icon'
import { isImageFile } from '@/util' import { isImageFile } from '@/util'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
@ -14,8 +14,8 @@ const g = useGlobalStore()
const { left, right } = storeToRefs(sliStore) const { left, right } = storeToRefs(sliStore)
const onImageDrop = async (e: DragEvent, side: 'left' | 'right') => { const onImageDrop = async (e: DragEvent, side: 'left' | 'right') => {
const data = JSON.parse(e.dataTransfer?.getData('text') ?? '{}') const data = getFileTransferDataFromDragEvent(e)
if (isFileTransferData(data)) { if (data) {
const img = data.nodes[0] const img = data.nodes[0]
if (!isImageFile(img.name)) { if (!isImageFile(img.name)) {
return return

View File

@ -5,7 +5,7 @@ import ImgSliSide from './ImgSliSide.vue'
import { asyncComputed, useElementSize } from '@vueuse/core' import { asyncComputed, useElementSize } from '@vueuse/core'
import { ref } from 'vue' import { ref } from 'vue'
import { FileNodeInfo } from '@/api/files' import { FileNodeInfo } from '@/api/files'
import { toRawFileUrl } from '../fileTransfer/util' import { toRawFileUrl } from '@/util/file'
import { createImage } from '@/util' import { createImage } from '@/util'
const props = defineProps<{ const props = defineProps<{

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { toRawFileUrl } from '../fileTransfer/util' import { toRawFileUrl } from '@/util/file'
import { computed } from 'vue' import { computed } from 'vue'
import { FileNodeInfo } from '@/api/files' import { FileNodeInfo } from '@/api/files'
const props = defineProps<{ side: 'left' | 'right', containerWidth: number, img: FileNodeInfo, maxEdge: 'width' | 'height', percent: number }>() const props = defineProps<{ side: 'left' | 'right', containerWidth: number, img: FileNodeInfo, maxEdge: 'width' | 'height', percent: number }>()

View File

@ -24,6 +24,7 @@ const compMap: Record<TabPane['type'], ReturnType<typeof defineAsyncComponent>>
'tag-search': defineAsyncComponent(() => import('@/page/TagSearch/TagSearch.vue')), 'tag-search': defineAsyncComponent(() => import('@/page/TagSearch/TagSearch.vue')),
'fuzzy-search': defineAsyncComponent(() => import('@/page/TagSearch/SubstrSearch.vue')), 'fuzzy-search': defineAsyncComponent(() => import('@/page/TagSearch/SubstrSearch.vue')),
'img-sli': defineAsyncComponent(() => import('@/page/ImgSli/ImgSliPagePane.vue')), 'img-sli': defineAsyncComponent(() => import('@/page/ImgSli/ImgSliPagePane.vue')),
'batch-download': defineAsyncComponent(() => import('@/page/batchDownload/batchDownload.vue'))
} }
const onEdit = (idx: number, targetKey: any, action: string) => { const onEdit = (idx: number, targetKey: any, action: string) => {
const tab = global.tabList[idx] const tab = global.tabList[idx]

View File

@ -20,7 +20,8 @@ const compCnMap: Partial<Record<TabPane['type'], string>> = {
local: t('local'), local: t('local'),
'tag-search': t('imgSearch'), 'tag-search': t('imgSearch'),
'fuzzy-search': t('fuzzy-search'), 'fuzzy-search': t('fuzzy-search'),
'global-setting': t('globalSettings') 'global-setting': t('globalSettings'),
'batch-download': t('batchDownload') + ' / ' + t('archive')
} }
const openInCurrentTab = (type: TabPane['type'], path?: string, walkMode = false) => { const openInCurrentTab = (type: TabPane['type'], path?: string, walkMode = false) => {
let pane: TabPane let pane: TabPane
@ -30,6 +31,7 @@ const openInCurrentTab = (type: TabPane['type'], path?: string, walkMode = false
return return
case 'global-setting': case 'global-setting':
case 'tag-search': case 'tag-search':
case 'batch-download':
case 'fuzzy-search': case 'fuzzy-search':
case 'empty': case 'empty':
pane = { type, name: compCnMap[type]!, key: Date.now() + uniqueId() } pane = { type, name: compCnMap[type]!, key: Date.now() + uniqueId() }
@ -162,7 +164,9 @@ const addToSearchScanPathAndQuickMove = async () => {
<h2>{{ $t('launchFromQuickMove') }}</h2> <h2>{{ $t('launchFromQuickMove') }}</h2>
<ul> <ul>
<li @click="addToSearchScanPathAndQuickMove" class="item" style="text-align: ;"> <li @click="addToSearchScanPathAndQuickMove" class="item" style="text-align: ;">
<span class="text line-clamp-1"> <PlusOutlined/> {{ $t('add') }}</span> <span class="text line-clamp-1">
<PlusOutlined /> {{ $t('add') }}
</span>
</li> </li>
<li v-for="dir in global.quickMovePaths" :key="dir.key" class="item" <li v-for="dir in global.quickMovePaths" :key="dir.key" class="item"
@click.prevent="openInCurrentTab('local', dir.dir)"> @click.prevent="openInCurrentTab('local', dir.dir)">
@ -177,7 +181,7 @@ const addToSearchScanPathAndQuickMove = async () => {
@click.prevent="openInCurrentTab(comp)"> @click.prevent="openInCurrentTab(comp)">
<span class="text line-clamp-1">{{ compCnMap[comp] }}</span> <span class="text line-clamp-1">{{ compCnMap[comp] }}</span>
</li> </li>
<li class="item" @click="imgsli.opened = true"> <li class="item" @click="imgsli.opened = true">
<span class="text line-clamp-1">{{ $t('imgCompare') }}</span> <span class="text line-clamp-1">{{ $t('imgCompare') }}</span>
</li> </li>
<li class="item" v-if="canpreviewInNewWindow" @click="previewInNewWindow"> <li class="item" v-if="canpreviewInNewWindow" @click="previewInNewWindow">

View File

@ -1,9 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import fileItemCell from '@/page/fileTransfer/FileItem.vue' import fileItemCell from '@/components/FileItem.vue'
import '@zanllp/vue-virtual-scroller/dist/vue-virtual-scroller.css' import '@zanllp/vue-virtual-scroller/dist/vue-virtual-scroller.css'
// @ts-ignore // @ts-ignore
import { RecycleScroller } from '@zanllp/vue-virtual-scroller' import { RecycleScroller } from '@zanllp/vue-virtual-scroller'
import { toRawFileUrl } from '@/page/fileTransfer/hook' import { toRawFileUrl } from '@/util/file'
import { getImagesByTags, type MatchImageByTagsReq } from '@/api/db' import { getImagesByTags, type MatchImageByTagsReq } from '@/api/db'
import { nextTick, watch } from 'vue' import { nextTick, watch } from 'vue'
import { copy2clipboardI18n } from '@/util' import { copy2clipboardI18n } from '@/util'

View File

@ -1,10 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, onMounted, ref } from 'vue' import { nextTick, onMounted, ref } from 'vue'
import fileItemCell from '@/page/fileTransfer/FileItem.vue' import fileItemCell from '@/components/FileItem.vue'
import '@zanllp/vue-virtual-scroller/dist/vue-virtual-scroller.css' import '@zanllp/vue-virtual-scroller/dist/vue-virtual-scroller.css'
// @ts-ignore // @ts-ignore
import { RecycleScroller } from '@zanllp/vue-virtual-scroller' import { RecycleScroller } from '@zanllp/vue-virtual-scroller'
import { toRawFileUrl } from '@/page/fileTransfer/hook' import { toRawFileUrl } from '@/util/file'
import { getDbBasicInfo, getExpiredDirs, getImagesBySubstr, updateImageData, type DataBaseBasicInfo } from '@/api/db' import { getDbBasicInfo, getExpiredDirs, getImagesBySubstr, updateImageData, type DataBaseBasicInfo } from '@/api/db'
import { copy2clipboardI18n, makeAsyncFunctionSingle, useGlobalEventListen } from '@/util' import { copy2clipboardI18n, makeAsyncFunctionSingle, useGlobalEventListen } from '@/util'
import fullScreenContextMenu from '@/page/fileTransfer/fullScreenContextMenu.vue' import fullScreenContextMenu from '@/page/fileTransfer/fullScreenContextMenu.vue'

View File

@ -19,11 +19,10 @@ export const useImageSearch = () => {
const images = ref<FileNodeInfo[]>() const images = ref<FileNodeInfo[]>()
const queue = createReactiveQueue() const queue = createReactiveQueue()
const tagStore = useTagStore() const tagStore = useTagStore()
const propsMock = { tabIdx: -1, target: 'local', paneIdx: -1, walkMode: false } as const
const { stackViewEl, multiSelectedIdxs, stack, scroller } = useHookShareState({ images }).toRefs() const { stackViewEl, multiSelectedIdxs, stack, scroller } = useHookShareState({ images }).toRefs()
const { itemSize, gridItems, cellWidth } = useFilesDisplay(propsMock) const { itemSize, gridItems, cellWidth } = useFilesDisplay()
const { showMenuIdx } = useMobileOptimization() const { showMenuIdx } = useMobileOptimization()
useLocation(propsMock) useLocation()
const { onFileDragStart, onFileDragEnd } = useFileTransfer() const { onFileDragStart, onFileDragEnd } = useFileTransfer()
const { const {
showGenInfo, showGenInfo,
@ -31,8 +30,8 @@ export const useImageSearch = () => {
q: genInfoQueue, q: genInfoQueue,
onContextMenuClick, onContextMenuClick,
onFileItemClick onFileItemClick
} = useFileItemActions(propsMock, { openNext: identity }) } = useFileItemActions({ openNext: identity })
const { previewIdx, previewing, onPreviewVisibleChange, previewImgMove, canPreview } = usePreview(propsMock) const { previewIdx, previewing, onPreviewVisibleChange, previewImgMove, canPreview } = usePreview()
const onContextMenuClickU: typeof onContextMenuClick = async (e, file, idx) => { const onContextMenuClickU: typeof onContextMenuClick = async (e, file, idx) => {
stack.value = [{ curr: '', files: images.value! }] // hackfor delete multi files stack.value = [{ curr: '', files: images.value! }] // hackfor delete multi files

View File

@ -0,0 +1,93 @@
<script lang="ts" setup>
// @ts-ignore
import { RecycleScroller } from '@zanllp/vue-virtual-scroller'
import '@zanllp/vue-virtual-scroller/dist/vue-virtual-scroller.css'
import FileItem from '@/components/FileItem.vue'
import { useBatchDownloadStore } from '@/store/useBatchDownloadStore'
import { storeToRefs } from 'pinia'
import { useFilesDisplay, useHookShareState } from '@/page/fileTransfer/hook'
import { getFileTransferDataFromDragEvent, toRawFileUrl } from '@/util/file'
import { axiosInst } from '@/api'
import { createReactiveQueue } from '@/util'
const { stackViewEl } = useHookShareState().toRefs()
const { itemSize, gridItems, cellWidth } = useFilesDisplay()
const store = useBatchDownloadStore()
const { selectdFiles } = storeToRefs(store)
const q = createReactiveQueue()
defineProps<{
tabIdx: number
paneIdx: number
id: string
}>()
const onDrop = async (e: DragEvent) => {
const data = getFileTransferDataFromDragEvent(e)
if (data) {
store.addFiles(data.nodes)
}
}
const onDownloadClick = async () => {
q.pushAction(async () => {
const resp = await axiosInst.value.post('/zip', { paths: selectdFiles.value.map(v => v.fullpath) }, {
responseType: 'blob',
})
const url = window.URL.createObjectURL(new Blob([resp.data]))
const link = document.createElement('a')
link.href = url
link.setAttribute('download', `iib_${new Date().toLocaleString()}.zip`)
document.body.appendChild(link)
link.click()
})
}
const onDeleteClick = (idx: number) => {
selectdFiles.value.splice(idx, 1)
}
</script>
<template>
<div class="container" ref="stackViewEl" @drop="onDrop">
<div class="actions-panel actions">
<AButton @click="store.selectdFiles = []">{{ $t('clear') }}</AButton>
<AButton @click="onDownloadClick" type="primary" :loading="!q.isIdle">{{ $t('zipDownload') }}</AButton>
</div>
<div v-if="!selectdFiles.length" class="file-list">
<p class="hint">{{ $t('batchDownloaDDragAndDropHint') }}</p>
</div>
<RecycleScroller ref="scroller" v-else class="file-list" :items="selectdFiles.slice()" :item-size="itemSize.first"
key-field="fullpath" :item-secondary-size="itemSize.second" :gridItems="gridItems">
<template v-slot="{ item: file, index: idx }">
<file-item :idx="idx" :file="file" :cell-width="cellWidth" enable-close-icon
@close-icon-click="onDeleteClick(idx)" :full-screen-preview-image-url="toRawFileUrl(file)"
:enable-right-click-menu="false" />
</template>
</RecycleScroller>
</div>
</template>
<style scoped lang="scss">
.container {
background: var(--zp-secondary-background);
height: 100%;
overflow: auto;
display: flex;
flex-direction: column;
.actions-panel {
padding: 8px;
background-color: var(--zp-primary-background);
}
.file-list {
flex: 1;
list-style: none;
padding: 8px;
height: var(--pane-max-height);
width: 100%;
.hint {
text-align: center;
font-size: 2em;
padding: 30vh 128px 0;
}
}
}
</style>

View File

@ -21,8 +21,8 @@ import {
import { t } from '@/i18n' import { t } from '@/i18n'
import { type Tag } from '@/api/db' import { type Tag } from '@/api/db'
import { createReactiveQueue } from '@/util' import { createReactiveQueue } from '@/util'
import { toRawFileUrl } from './hook' import { toRawFileUrl } from '@/util/file'
import ContextMenu from './ContextMenu.vue' import ContextMenu from '@/components/ContextMenu.vue'
import { useWatchDocument } from 'vue3-ts-util' import { useWatchDocument } from 'vue3-ts-util'
import { useTagStore } from '@/store/useTagStore' import { useTagStore } from '@/store/useTagStore'
@ -156,6 +156,7 @@ const baseInfoTags = computed(() => {
<a-menu-item key="send2outpaint">openOutpaint</a-menu-item> <a-menu-item key="send2outpaint">openOutpaint</a-menu-item>
</a-sub-menu> </a-sub-menu>
</template> </template>
<a-menu-item key="send2BatchDownload">{{ $t('sendToBatchDownload') }}</a-menu-item>
<a-menu-item key="send2savedDir">{{ $t('send2savedDir') }}</a-menu-item> <a-menu-item key="send2savedDir">{{ $t('send2savedDir') }}</a-menu-item>
<a-menu-item key="deleteFiles" :disabled="toRawFileUrl(file) === global.fullscreenPreviewInitialUrl"> <a-menu-item key="deleteFiles" :disabled="toRawFileUrl(file) === global.fullscreenPreviewInitialUrl">
{{ $t('deleteSelected') }} {{ $t('deleteSelected') }}

View File

@ -30,16 +30,16 @@ import type { MenuInfo } from 'ant-design-vue/lib/menu/src/interface'
import { t } from '@/i18n' import { t } from '@/i18n'
import { DatabaseOutlined } from '@/icon' import { DatabaseOutlined } from '@/icon'
import { addScannedPath, removeScannedPath, toggleCustomTagToImg } from '@/api/db' import { addScannedPath, removeScannedPath, toggleCustomTagToImg } from '@/api/db'
import { FileTransferData, getFileTransferDataFromDragEvent, toRawFileUrl } from './util' import { FileTransferData, getFileTransferDataFromDragEvent, toRawFileUrl } from '../../util/file'
import { getShortcutStrFromEvent } from '@/util/shortcut' import { getShortcutStrFromEvent } from '@/util/shortcut'
import { openCreateFlodersModal, MultiSelectTips } from './functionalCallableComp' import { openCreateFlodersModal, MultiSelectTips } from './functionalCallableComp'
import { useTagStore } from '@/store/useTagStore' import { useTagStore } from '@/store/useTagStore'
export * from './util' import { useBatchDownloadStore } from '@/store/useBatchDownloadStore'
export const stackCache = new Map<string, Page[]>() export const stackCache = new Map<string, Page[]>()
const global = useGlobalStore() const global = useGlobalStore()
const batchDownload = useBatchDownloadStore()
const tagStore = useTagStore() const tagStore = useTagStore()
const sli = useImgSliStore() const sli = useImgSliStore()
const imgTransferBus = new BroadcastChannel('iib-image-transfer-bus') const imgTransferBus = new BroadcastChannel('iib-image-transfer-bus')
@ -143,14 +143,15 @@ export interface Page {
* @param props * @param props
* @returns * @returns
*/ */
export function usePreview (props: Props) { export function usePreview () {
const { const {
previewIdx, previewIdx,
eventEmitter, eventEmitter,
canLoadNext, canLoadNext,
previewing, previewing,
sortedFiles: files, sortedFiles: files,
scroller scroller,
props
} = useHookShareState().toRefs() } = useHookShareState().toRefs()
const { state } = useHookShareState() const { state } = useHookShareState()
let waitScrollTo = null as number | null let waitScrollTo = null as number | null
@ -164,7 +165,7 @@ export function usePreview (props: Props) {
} }
const loadNextIfNeeded = () => { const loadNextIfNeeded = () => {
if (props.walkModePath) { if (props.value.walkModePath) {
if (!canPreview('next') && canLoadNext) { if (!canPreview('next') && canLoadNext) {
message.info(t('loadingNextFolder')) message.info(t('loadingNextFolder'))
eventEmitter.value.emit('loadNextDir', true) // 如果在全屏预览时外面scroller可能还停留在很久之前使用全屏预览的索引 eventEmitter.value.emit('loadNextDir', true) // 如果在全屏预览时外面scroller可能还停留在很久之前使用全屏预览的索引
@ -259,7 +260,7 @@ export function usePreview (props: Props) {
/** /**
* *
*/ */
export function useLocation (props: Props) { export function useLocation () {
const np = ref<Progress.NProgress>() const np = ref<Progress.NProgress>()
const { const {
scroller, scroller,
@ -272,7 +273,8 @@ export function useLocation (props: Props) {
eventEmitter, eventEmitter,
getPane, getPane,
multiSelectedIdxs, multiSelectedIdxs,
sortedFiles sortedFiles,
props
} = useHookShareState().toRefs() } = useHookShareState().toRefs()
watch( watch(
@ -286,7 +288,7 @@ export function useLocation (props: Props) {
const handleWalkModeTo = async (path: string) => { const handleWalkModeTo = async (path: string) => {
await to(path) await to(path)
if (props.walkModePath) { if (props.value.walkModePath) {
await delay() await delay()
const [firstDir] = sortFiles(currPage.value!.files, sortMethod.value).filter( const [firstDir] = sortFiles(currPage.value!.files, sortMethod.value).filter(
(v) => v.type === 'dir' (v) => v.type === 'dir'
@ -309,8 +311,8 @@ export function useLocation (props: Props) {
} }
np.value = new NProgress() np.value = new NProgress()
np.value!.configure({ parent: stackViewEl.value as any }) np.value!.configure({ parent: stackViewEl.value as any })
if (props.path && props.path !== '/') { if (props.value.path && props.value.path !== '/') {
await handleWalkModeTo(props.walkModePath ?? props.path) await handleWalkModeTo(props.value.walkModePath ?? props.value.path)
} else { } else {
global.conf?.home && to(global.conf.home) global.conf?.home && to(global.conf.home)
} }
@ -326,7 +328,7 @@ export function useLocation (props: Props) {
pane.path = loc pane.path = loc
const filename = pane.path!.split('/').pop() const filename = pane.path!.split('/').pop()
const getTitle = () => { const getTitle = () => {
if (!props.walkModePath) { if (!props.value.walkModePath) {
const np = Path.normalize(loc) const np = Path.normalize(loc)
for (const [k, v] of Object.entries(global.pathAliasMap)) { for (const [k, v] of Object.entries(global.pathAliasMap)) {
if (np.startsWith(v)) { if (np.startsWith(v)) {
@ -430,9 +432,9 @@ export function useLocation (props: Props) {
const refresh = makeAsyncFunctionSingle(async () => { const refresh = makeAsyncFunctionSingle(async () => {
try { try {
np.value?.start() np.value?.start()
if (props.walkModePath) { if (props.value.walkModePath) {
back(0) back(0)
await handleWalkModeTo(props.walkModePath) await handleWalkModeTo(props.value.walkModePath)
} else { } else {
const { files } = await getTargetFolderFiles( const { files } = await getTargetFolderFiles(
stack.value.length === 1 ? '/' : currLocation.value stack.value.length === 1 ? '/' : currLocation.value
@ -449,7 +451,7 @@ export function useLocation (props: Props) {
useGlobalEventListen( useGlobalEventListen(
'returnToIIB', 'returnToIIB',
makeAsyncFunctionSingle(async () => { makeAsyncFunctionSingle(async () => {
if (!props.walkModePath) { if (!props.value.walkModePath) {
try { try {
np.value?.start() np.value?.start()
const { files } = await getTargetFolderFiles( const { files } = await getTargetFolderFiles(
@ -470,7 +472,7 @@ export function useLocation (props: Props) {
useEventListen.value('refresh', refresh) useEventListen.value('refresh', refresh)
const quickMoveTo = (path: string) => { const quickMoveTo = (path: string) => {
if (props.walkModePath) { if (props.value.walkModePath) {
getPane.value().walkModePath = path getPane.value().walkModePath = path
} }
handleWalkModeTo(path) handleWalkModeTo(path)
@ -564,7 +566,7 @@ export function useLocation (props: Props) {
} }
} }
export function useFilesDisplay (props: Props) { export function useFilesDisplay () {
const { const {
scroller, scroller,
sortedFiles, sortedFiles,
@ -574,7 +576,8 @@ export function useFilesDisplay (props: Props) {
currPage, currPage,
stackViewEl, stackViewEl,
canLoadNext, canLoadNext,
previewIdx previewIdx,
props
} = useHookShareState().toRefs() } = useHookShareState().toRefs()
const { state } = useHookShareState() const { state } = useHookShareState()
const moreActionsDropdownShow = ref(false) const moreActionsDropdownShow = ref(false)
@ -597,7 +600,7 @@ export function useFilesDisplay (props: Props) {
const loadNextDirLoading = ref(false) const loadNextDirLoading = ref(false)
const loadNextDir = async () => { const loadNextDir = async () => {
if (loadNextDirLoading.value || !props.walkModePath || !canLoadNext.value) { if (loadNextDirLoading.value || !props.value.walkModePath || !canLoadNext.value) {
return return
} }
try { try {
@ -763,7 +766,6 @@ export function useFileTransfer () {
} }
export function useFileItemActions ( export function useFileItemActions (
props: Props,
{ openNext }: { openNext: (file: FileNodeInfo) => Promise<void> } { openNext }: { openNext: (file: FileNodeInfo) => Promise<void> }
) { ) {
const showGenInfo = ref(false) const showGenInfo = ref(false)
@ -777,7 +779,8 @@ export function useFileItemActions (
spinning, spinning,
previewing, previewing,
stackViewEl, stackViewEl,
eventEmitter eventEmitter,
props
} = useHookShareState().toRefs() } = useHookShareState().toRefs()
const nor = Path.normalize const nor = Path.normalize
useEventListen('removeFiles', ({ paths, loc }) => { useEventListen('removeFiles', ({ paths, loc }) => {
@ -944,7 +947,7 @@ export function useFileItemActions (
} }
case 'openWithWalkMode': { case 'openWithWalkMode': {
stackCache.set(path, stack.value) stackCache.set(path, stack.value)
const tab = global.tabList[props.tabIdx] const tab = global.tabList[props.value.tabIdx]
const pane: FileTransferTabPane = { const pane: FileTransferTabPane = {
type: 'local', type: 'local',
key: uniqueId(), key: uniqueId(),
@ -959,7 +962,7 @@ export function useFileItemActions (
} }
case 'openInNewTab': { case 'openInNewTab': {
stackCache.set(path, stack.value) stackCache.set(path, stack.value)
const tab = global.tabList[props.tabIdx] const tab = global.tabList[props.value.tabIdx]
const pane: FileTransferTabPane = { const pane: FileTransferTabPane = {
type: 'local', type: 'local',
key: uniqueId(), key: uniqueId(),
@ -973,10 +976,10 @@ export function useFileItemActions (
} }
case 'openOnTheRight': { case 'openOnTheRight': {
stackCache.set(path, stack.value) stackCache.set(path, stack.value)
let tab = global.tabList[props.tabIdx + 1] let tab = global.tabList[props.value.tabIdx + 1]
if (!tab) { if (!tab) {
tab = { panes: [], key: '', id: uniqueId() } tab = { panes: [], key: '', id: uniqueId() }
global.tabList[props.tabIdx + 1] = tab global.tabList[props.value.tabIdx + 1] = tab
} }
const pane: FileTransferTabPane = { const pane: FileTransferTabPane = {
type: 'local', type: 'local',
@ -989,6 +992,10 @@ export function useFileItemActions (
tab.key = pane.key tab.key = pane.key
break break
} }
case 'send2BatchDownload': {
batchDownload.addFiles(getSelectedImg())
break
}
case 'viewGenInfo': { case 'viewGenInfo': {
showGenInfo.value = true showGenInfo.value = true
imageGenInfo.value = await q.pushAction(() => getImageGenerationInfo(file.fullpath)).res imageGenInfo.value = await q.pushAction(() => getImageGenerationInfo(file.fullpath)).res

View File

@ -8,18 +8,18 @@ import {
useLocation, useLocation,
usePreview, usePreview,
useFileItemActions, useFileItemActions,
toRawFileUrl,
stackCache, stackCache,
useMobileOptimization useMobileOptimization
} from './hook' } from './hook'
import { SearchSelect } from 'vue3-ts-util' import { SearchSelect } from 'vue3-ts-util'
import { toRawFileUrl } from '@/util/file'
import 'multi-nprogress/nprogress.css' import 'multi-nprogress/nprogress.css'
// @ts-ignore // @ts-ignore
import { RecycleScroller } from '@zanllp/vue-virtual-scroller' import { RecycleScroller } from '@zanllp/vue-virtual-scroller'
import '@zanllp/vue-virtual-scroller/dist/vue-virtual-scroller.css' import '@zanllp/vue-virtual-scroller/dist/vue-virtual-scroller.css'
import { watch } from 'vue' import { watch } from 'vue'
import FileItem from './FileItem.vue' import FileItem from '@/components/FileItem.vue'
import fullScreenContextMenu from './fullScreenContextMenu.vue' import fullScreenContextMenu from './fullScreenContextMenu.vue'
import { copy2clipboardI18n } from '@/util' import { copy2clipboardI18n } from '@/util'
import { openFolder } from '@/api' import { openFolder } from '@/api'
@ -50,7 +50,7 @@ const {
const { currLocation, currPage, refresh, copyLocation, back, openNext, stack, quickMoveTo, const { currLocation, currPage, refresh, copyLocation, back, openNext, stack, quickMoveTo,
addToSearchScanPathAndQuickMove, searchPathInfo, locInputValue, isLocationEditing, addToSearchScanPathAndQuickMove, searchPathInfo, locInputValue, isLocationEditing,
onLocEditEnter, onEditBtnClick, share, selectAll, onCreateFloderBtnClick onLocEditEnter, onEditBtnClick, share, selectAll, onCreateFloderBtnClick
} = useLocation(props) } = useLocation()
const { const {
gridItems, gridItems,
sortMethodConv, sortMethodConv,
@ -63,14 +63,10 @@ const {
canLoadNext, canLoadNext,
onScroll, onScroll,
cellWidth cellWidth
} = useFilesDisplay(props) } = useFilesDisplay()
const { onDrop, onFileDragStart, onFileDragEnd } = useFileTransfer() const { onDrop, onFileDragStart, onFileDragEnd } = useFileTransfer()
const { onFileItemClick, onContextMenuClick, showGenInfo, imageGenInfo, q } = useFileItemActions( const { onFileItemClick, onContextMenuClick, showGenInfo, imageGenInfo, q } = useFileItemActions({ openNext })
props, const { previewIdx, onPreviewVisibleChange, previewing, previewImgMove, canPreview } = usePreview()
{ openNext }
)
const { previewIdx, onPreviewVisibleChange, previewing, previewImgMove, canPreview } =
usePreview(props)
const { showMenuIdx } = useMobileOptimization() const { showMenuIdx } = useMobileOptimization()
watch( watch(

View File

@ -0,0 +1,15 @@
import { FileNodeInfo } from '@/api/files'
import { uniqueFile } from '@/util'
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useBatchDownloadStore = defineStore('useBatchDownloadStore', () => {
const selectdFiles = ref<FileNodeInfo[]>([])
const addFiles = (files: FileNodeInfo[]) => {
selectdFiles.value = uniqueFile([...selectdFiles.value,...files])
}
return {
selectdFiles,
addFiles
}
})

View File

@ -19,7 +19,7 @@ interface TabPaneBase {
} }
interface OtherTabPane extends TabPaneBase { interface OtherTabPane extends TabPaneBase {
type: 'empty' | 'global-setting' | 'tag-search' | 'fuzzy-search' type: 'empty' | 'global-setting' | 'tag-search' | 'fuzzy-search' | 'batch-download'
} }
// logDetailId // logDetailId

View File

@ -1,5 +0,0 @@
import { defineStore } from 'pinia'
export const useTagSearch = defineStore('useTagSearch', () => {
return {}
})

View File

@ -1,5 +1,6 @@
import type { FileNodeInfo } from '@/api/files' import type { FileNodeInfo } from '@/api/files'
import { apiBase } from '@/api' import { apiBase } from '@/api'
import { uniqBy } from 'lodash-es'
const encode = encodeURIComponent const encode = encodeURIComponent
export const toRawFileUrl = (file: FileNodeInfo, download = false) => export const toRawFileUrl = (file: FileNodeInfo, download = false) =>
@ -26,3 +27,14 @@ export const getFileTransferDataFromDragEvent = (e: DragEvent) => {
const data = JSON.parse(e.dataTransfer?.getData('text') ?? '{}') const data = JSON.parse(e.dataTransfer?.getData('text') ?? '{}')
return isFileTransferData(data) ? data : null return isFileTransferData(data) ? data : null
} }
export const uniqueFile = (files: FileNodeInfo[]) => uniqBy(files, 'fullpath')
export function isImageFile(filename: string): boolean {
if (typeof filename !== 'string') {
return false
}
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
const extension = filename.split('.').pop()?.toLowerCase()
return extension !== undefined && imageExtensions.includes(`.${extension}`)
}

View File

@ -2,6 +2,7 @@ import { t } from '@/i18n'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { reactive } from 'vue' import { reactive } from 'vue'
import { FetchQueue, idKey, typedEventEmitter, type UniqueId } from 'vue3-ts-util' import { FetchQueue, idKey, typedEventEmitter, type UniqueId } from 'vue3-ts-util'
export * from './file'
export const parentWindow = () => { export const parentWindow = () => {
return parent.window as any as Window & { return parent.window as any as Window & {
@ -74,16 +75,6 @@ export const pick = <T extends Dict, keys extends Array<keyof T>>(v: T, ...keys:
* ReturnTypeAsync\<typeof fn\> * ReturnTypeAsync\<typeof fn\>
*/ */
export type ReturnTypeAsync<T extends (...arg: any) => Promise<any>> = Awaited<ReturnType<T>> export type ReturnTypeAsync<T extends (...arg: any) => Promise<any>> = Awaited<ReturnType<T>>
export function isImageFile(filename: string): boolean {
if (typeof filename !== 'string') {
return false
}
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
const extension = filename.split('.').pop()?.toLowerCase()
return extension !== undefined && imageExtensions.includes(`.${extension}`)
}
export const createReactiveQueue = () => reactive(new FetchQueue(-1, 0, -1, 'throw')) export const createReactiveQueue = () => reactive(new FetchQueue(-1, 0, -1, 'throw'))
export const copy2clipboardI18n = async (text: string, msg?: string) => { export const copy2clipboardI18n = async (text: string, msg?: string) => {

0
zip_temp/.gitkeep Normal file
View File