Merge pull request #50 from zanllp/feat/send-to-saved-dir-and-toggle-selected

support send selected img to saved dir and toggle selected。 fix refre…
pull/54/head
zanllp 2023-04-26 01:34:58 +08:00 committed by GitHub
commit 71d32b16ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 84 additions and 34 deletions

View File

@ -8,7 +8,7 @@
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
<script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-591714e7.js"></script>
<script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-2373fa97.js"></script>
<link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-7059ac8b.css">
</head>

View File

@ -109,10 +109,15 @@ def infinite_image_browsing_api(_: Any, app: FastAPI, **kwargs):
@app.post(pre + "/move_files/{target}")
async def move_files(req: MoveFilesReq, target: Literal["local", "netdisk"]):
conn = DataBase.get_conn()
if target == "local":
for path in req.file_paths:
try:
shutil.move(path, req.dest)
img = DbImg.get(conn, os.path.normpath(path))
if img:
ImageTag.remove_by_image(conn, img.id)
DbImg.remove(conn, img.id)
except OSError as e:
error_msg = f"Error moving file {path} to {req.dest}: {e}" if locale == "en" else f"移动文件 {path}{req.dest} 时出错:{e}"
raise HTTPException(400, detail=error_msg)

3
vue/dist/assets/FileItem-0caab9f3.js vendored Normal file

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{d as D,r as k,aW as F,a$ as G,n as N,K as U,L as V,c as n,N as o,O as s,Q as w,T as x,U as y,Y as $,aR as B,a6 as R,a8 as E}from"./index-591714e7.js";import{u as O,b as Q,f as q,d as j,h as H,j as K,k as L,t as W,M as Y,S as J}from"./FileItem-be64480a.js";import{g as P}from"./db-ec089088.js";import"./index-6bc723a3.js";import"./button-2a8cbb69.js";const X={class:"hint"},Z=D({__name:"MatchedImageGrid",props:{tabIdx:null,paneIdx:null,selectedTagIds:null,id:null},setup(h){const u=h,d=k(),p=F(new G);N(()=>u.selectedTagIds,async()=>{var e;const{res:i}=p.pushAction(()=>P(u.selectedTagIds));d.value=(await i).sort((a,l)=>Date.parse(l.date)-Date.parse(a.date)),(e=m.value)==null||e.scrollToItem(0)},{immediate:!0});const m=k(),f={tabIdx:-1,target:"local",paneIdx:-1},{stackViewEl:b,multiSelectedIdxs:v}=O().toRefs(),{itemSize:g,gridItems:M}=Q(f),{showMenuIdx:c}=q(),{showGenInfo:r,imageGenInfo:I,q:C,onContextMenuClick:S}=j(f,{openNext:B}),T=async(i,e,a)=>{if(await S(i,e,a),i.key==="deleteFiles"){const l=v.value.includes(a)?v.value:[a];d.value=d.value.filter((_,t)=>!l.includes(t))}};return(i,e)=>{const a=R,l=Y,_=J;return U(),V("div",{class:"container",ref_key:"stackViewEl",ref:b},[n(_,{size:"large",spinning:!p.isIdle},{default:o(()=>[n(l,{visible:s(r),"onUpdate:visible":e[1]||(e[1]=t=>w(r)?r.value=t:null),width:"70vw","mask-closable":"",onOk:e[2]||(e[2]=t=>r.value=!1)},{cancelText:o(()=>[]),default:o(()=>[n(a,{active:"",loading:!s(C).isIdle},{default:o(()=>[x("div",{style:{width:"100%","word-break":"break-all","white-space":"pre-line","max-height":"70vh",overflow:"auto"},onDblclick:e[0]||(e[0]=t=>s(H)(s(I),"copied"))},[x("div",X,y(i.$t("doubleClickToCopy")),1),$(" "+y(s(I)),1)],32)]),_:1},8,["loading"])]),_:1},8,["visible"]),n(s(K),{ref_key:"scroller",ref:m,class:"file-list",items:d.value||[],"item-size":s(g).first,"key-field":"fullpath","item-secondary-size":s(g).second,gridItems:s(M)},{default:o(({item:t,index:z})=>[n(L,{idx:z,file:t,"show-menu-idx":s(c),"onUpdate:showMenuIdx":e[3]||(e[3]=A=>w(c)?c.value=A:null),"full-screen-preview-image-url":s(W)(t),onContextMenuClick:T},null,8,["idx","file","show-menu-idx","full-screen-preview-image-url"])]),_:1},8,["items","item-size","item-secondary-size","gridItems"])]),_:1},8,["spinning"])],512)}}});const le=E(Z,[["__scopeId","data-v-177376f3"]]);export{le as default};
import{d as D,r as k,aW as F,a$ as G,n as N,K as U,L as V,c as n,N as o,O as s,Q as w,T as x,U as y,Y as $,aR as B,a6 as R,a8 as E}from"./index-2373fa97.js";import{u as O,b as Q,f as q,d as j,h as H,j as K,k as L,t as W,M as Y,S as J}from"./FileItem-0caab9f3.js";import{g as P}from"./db-965dc734.js";import"./index-b8c8219f.js";import"./button-bc78e5bf.js";const X={class:"hint"},Z=D({__name:"MatchedImageGrid",props:{tabIdx:null,paneIdx:null,selectedTagIds:null,id:null},setup(h){const u=h,d=k(),p=F(new G);N(()=>u.selectedTagIds,async()=>{var e;const{res:i}=p.pushAction(()=>P(u.selectedTagIds));d.value=(await i).sort((a,l)=>Date.parse(l.date)-Date.parse(a.date)),(e=m.value)==null||e.scrollToItem(0)},{immediate:!0});const m=k(),f={tabIdx:-1,target:"local",paneIdx:-1},{stackViewEl:b,multiSelectedIdxs:v}=O().toRefs(),{itemSize:g,gridItems:M}=Q(f),{showMenuIdx:c}=q(),{showGenInfo:r,imageGenInfo:I,q:C,onContextMenuClick:S}=j(f,{openNext:B}),T=async(i,e,a)=>{if(await S(i,e,a),i.key==="deleteFiles"){const l=v.value.includes(a)?v.value:[a];d.value=d.value.filter((_,t)=>!l.includes(t))}};return(i,e)=>{const a=R,l=Y,_=J;return U(),V("div",{class:"container",ref_key:"stackViewEl",ref:b},[n(_,{size:"large",spinning:!p.isIdle},{default:o(()=>[n(l,{visible:s(r),"onUpdate:visible":e[1]||(e[1]=t=>w(r)?r.value=t:null),width:"70vw","mask-closable":"",onOk:e[2]||(e[2]=t=>r.value=!1)},{cancelText:o(()=>[]),default:o(()=>[n(a,{active:"",loading:!s(C).isIdle},{default:o(()=>[x("div",{style:{width:"100%","word-break":"break-all","white-space":"pre-line","max-height":"70vh",overflow:"auto"},onDblclick:e[0]||(e[0]=t=>s(H)(s(I),"copied"))},[x("div",X,y(i.$t("doubleClickToCopy")),1),$(" "+y(s(I)),1)],32)]),_:1},8,["loading"])]),_:1},8,["visible"]),n(s(K),{ref_key:"scroller",ref:m,class:"file-list",items:d.value||[],"item-size":s(g).first,"key-field":"fullpath","item-secondary-size":s(g).second,gridItems:s(M)},{default:o(({item:t,index:z})=>[n(L,{idx:z,file:t,"show-menu-idx":s(c),"onUpdate:showMenuIdx":e[3]||(e[3]=A=>w(c)?c.value=A:null),"full-screen-preview-image-url":s(W)(t),onContextMenuClick:T},null,8,["idx","file","show-menu-idx","full-screen-preview-image-url"])]),_:1},8,["items","item-size","item-secondary-size","gridItems"])]),_:1},8,["spinning"])],512)}}});const le=E(Z,[["__scopeId","data-v-177376f3"]]);export{le as default};

View File

@ -0,0 +1 @@
.container[data-v-214b03ea]{height:var(--pane-max-height);overflow:auto;display:flex;flex-direction:column;align-items:stretch}.container .select[data-v-214b03ea]{padding:8px}.container .search-bar[data-v-214b03ea]{padding:8px;display:flex}.container .tag-list[data-v-214b03ea]{list-style:none;padding:0;overflow:scroll}.container .tag-list .tag[data-v-214b03ea]{border:2px solid var(--zp-secondary);color:var(--zp-primary);border-radius:999px;padding:4px 16px;margin:4px;display:inline-block;cursor:pointer}.container .tag-list .tag.selected[data-v-214b03ea]{color:var(--primary-color);border:2px solid var(--primary-color)}

View File

@ -1 +0,0 @@
import{d as T,V as b,aW as N,a$ as A,r as h,A as V,aa as $,p as D,K as o,L as l,$ as r,x as y,T as d,c as U,O as c,a2 as q,W as u,N as f,Y as p,U as g,Z as G,a4 as F,cj as L,a8 as M}from"./index-591714e7.js";/* empty css */import{a as k,u as O}from"./db-ec089088.js";import{B as W}from"./button-2a8cbb69.js";const j={class:"container"},z={class:"search-bar"},E={class:"tag-list"},K=["onClick"],Q=T({__name:"TagSearch",props:{tabIdx:null,paneIdx:null},setup(x){const I=x,S=b(),i=N(new A),a=h(),t=h(new Set),m=V(()=>a.value?a.value.tags.slice().sort((s,n)=>n.count-s.count):[]),C=$();D(async()=>{a.value=await k()});const B=async()=>{i.pushAction(async()=>{await O(),a.value=await k()})},w=()=>{S.openTagSearchMatchedImageGridInRight(I.tabIdx,C,Array.from(t.value))},_=s=>s.display_name?`${s.display_name} : ${s.name}`:s.name;return(s,n)=>{const v=W;return o(),l("div",j,[r("",!0),a.value?(o(),l(y,{key:1},[d("div",null,[d("div",z,[U(c(q),{conv:{value:e=>e.id,text:_},mode:"multiple",style:{width:"100%"},options:c(m),value:Array.from(t.value),placeholder:"Select tags to match images","onUpdate:value":n[0]||(n[0]=e=>t.value=new Set(e))},null,8,["conv","options","value"]),a.value.expired||!a.value.img_count?(o(),u(v,{key:0,onClick:B,loading:!i.isIdle,type:"primary"},{default:f(()=>[p(g(a.value.img_count===0?"Generate index for search image":"Update index"),1)]),_:1},8,["loading"])):(o(),u(v,{key:1,type:"primary",onClick:w,loading:!i.isIdle},{default:f(()=>[p("Search")]),_:1},8,["loading"]))])]),d("ul",E,[(o(!0),l(y,null,G(c(m),e=>(o(),l("li",{key:e.id,class:F(["tag",{selected:t.value.has(e.id)}]),onClick:R=>t.value.has(e.id)?t.value.delete(e.id):t.value.add(e.id)},[t.value.has(e.id)?(o(),u(c(L),{key:0})):r("",!0),p(" "+g(_(e)),1)],10,K))),128))])],64)):r("",!0)])}}});const P=M(Q,[["__scopeId","data-v-ffc944e7"]]);export{P as default};

1
vue/dist/assets/TagSearch-aaca7fae.js vendored Normal file
View File

@ -0,0 +1 @@
import{d as w,V as T,aW as N,a$ as A,r as y,A as V,aa as $,p as D,K as o,L as l,$ as r,x as g,T as u,c as U,O as c,a2 as q,W as d,N as f,Y as p,U as x,Z as G,a4 as F,cj as L,a8 as M}from"./index-2373fa97.js";/* empty css */import{a as k,u as O}from"./db-965dc734.js";import{B as W}from"./button-bc78e5bf.js";const j={class:"container"},z={class:"search-bar"},E={class:"tag-list"},K=["onClick"],Q=w({__name:"TagSearch",props:{tabIdx:null,paneIdx:null},setup(I){const S=I,C=T(),i=N(new A),a=y(),t=y(new Set),m=V(()=>a.value?a.value.tags.slice().sort((s,n)=>n.count-s.count):[]),B=$();D(async()=>{a.value=await k(),a.value.img_count&&a.value.expired&&_()});const _=async()=>{i.pushAction(async()=>{await O(),a.value=await k()})},b=()=>{C.openTagSearchMatchedImageGridInRight(S.tabIdx,B,Array.from(t.value))},v=s=>s.display_name?`${s.display_name} : ${s.name}`:s.name;return(s,n)=>{const h=W;return o(),l("div",j,[r("",!0),a.value?(o(),l(g,{key:1},[u("div",null,[u("div",z,[U(c(q),{conv:{value:e=>e.id,text:v},mode:"multiple",style:{width:"100%"},options:c(m),value:Array.from(t.value),placeholder:"Select tags to match images","onUpdate:value":n[0]||(n[0]=e=>t.value=new Set(e))},null,8,["conv","options","value"]),a.value.expired||!a.value.img_count?(o(),d(h,{key:0,onClick:_,loading:!i.isIdle,type:"primary"},{default:f(()=>[p(x(a.value.img_count===0?"Generate index for search image":"Update index"),1)]),_:1},8,["loading"])):(o(),d(h,{key:1,type:"primary",onClick:b,loading:!i.isIdle},{default:f(()=>[p("Search")]),_:1},8,["loading"]))])]),u("ul",E,[(o(!0),l(g,null,G(c(m),e=>(o(),l("li",{key:e.id,class:F(["tag",{selected:t.value.has(e.id)}]),onClick:R=>t.value.has(e.id)?t.value.delete(e.id):t.value.add(e.id)},[t.value.has(e.id)?(o(),d(c(L),{key:0})):r("",!0),p(" "+x(v(e)),1)],10,K))),128))])],64)):r("",!0)])}}});const P=M(Q,[["__scopeId","data-v-214b03ea"]]);export{P as default};

View File

@ -1 +0,0 @@
.container[data-v-ffc944e7]{height:var(--pane-max-height);overflow:auto;display:flex;flex-direction:column;align-items:stretch}.container .select[data-v-ffc944e7]{padding:8px}.container .search-bar[data-v-ffc944e7]{padding:8px;display:flex}.container .tag-list[data-v-ffc944e7]{list-style:none;padding:0;overflow:scroll}.container .tag-list .tag[data-v-ffc944e7]{border:2px solid var(--zp-secondary);color:var(--zp-primary);border-radius:999px;padding:4px 16px;margin:4px;display:inline-block;cursor:pointer}.container .tag-list .tag.selected[data-v-ffc944e7]{color:var(--primary-color);border:2px solid var(--primary-color)}

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
import{bC as a}from"./index-591714e7.js";const n=async()=>(await a.get("/db/basic_info")).data,i=async()=>{await a.post("/db/update_image_data",{},{timeout:1/0})},o=async t=>(await a.get("/db/match_images_by_tags",{params:{tag_ids:t.join()}})).data;export{n as a,o as g,i as u};
import{bC as a}from"./index-2373fa97.js";const n=async()=>(await a.get("/db/basic_info")).data,i=async()=>{await a.post("/db/update_image_data",{},{timeout:1/0})},o=async t=>(await a.get("/db/match_images_by_tags",{params:{tag_ids:t.join()}})).data;export{n as a,o as g,i 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

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

2
vue/dist/index.html vendored
View File

@ -7,7 +7,7 @@
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
<script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-591714e7.js"></script>
<script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-2373fa97.js"></script>
<link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-7059ac8b.css">
</head>

View File

@ -100,9 +100,12 @@ const zh = {
clickHere2install: '点此安装',
searchResults: "搜索结果",
imgSearch: '图像搜索',
"onlyFoldersAndImages": "只显示文件夹和图像"
"onlyFoldersAndImages": "只显示文件夹和图像",
"send2savedDir": "发送到保存的文件夹",
unknownSavedDir: "找不到保存的文件夹配置文件中的outdir_save字段"
}
const en: Record<keyof typeof zh, string> = {
unknownSavedDir: `Cannot find the saved folder (outdir_save field in the config)`,
errorOccurred: 'An error occurred',
logoutSuccess: 'Logged out successfully',
useThumbnailPreview: 'Use thumbnail preview',
@ -206,7 +209,8 @@ const en: Record<keyof typeof zh, string> = {
clickHere2install: 'Click here to install',
searchResults: "Search Results",
imgSearch: 'Image Search',
"onlyFoldersAndImages": "Only show folders and images"
"onlyFoldersAndImages": "Only show folders and images",
"send2savedDir": "Send to saved folder"
}
declare module 'vue' {
export interface ComponentCustomProperties {

View File

@ -15,6 +15,9 @@ const tags = computed(() => info.value ? info.value.tags.slice().sort((a, b) =>
const pairid = uniqueId()
onMounted(async () => {
info.value = await getDbBasicInfo()
if (info.value.img_count && info.value.expired) {
onUpdateBtnClick()
}
})
const onUpdateBtnClick = async () => {

View File

@ -101,6 +101,7 @@ const thumbnailSize = computed(() => props.viewMode === 'grid' ? [global.gridThu
<a-menu-item key="send2img2img">{{ $t('sendToImg2img') }}</a-menu-item>
<a-menu-item key="send2inpaint">{{ $t('sendToInpaint') }}</a-menu-item>
<a-menu-item key="send2extras">{{ $t('sendToExtraFeatures') }}</a-menu-item>
<a-menu-item key="send2savedDir" >{{ $t('send2savedDir') }}</a-menu-item>
</template>
</template>
</a-menu>

View File

@ -25,7 +25,10 @@ export const toRawFileUrl = (file: FileNodeInfo, download = false) => `/infinite
export const toImageThumbnailUrl = (file: FileNodeInfo, size: string) => `/infinite_image_browsing/image-thumbnail?path=${encodeURIComponent(file.fullpath)}&size=${size}`
const { eventEmitter: events, useEventListen } = typedEventEmitter<{ removeFiles: [paths: string[], loc: string], addFiles: [paths: string[], loc: string] }>()
const { eventEmitter: events, useEventListen } = typedEventEmitter<{
removeFiles: {paths: string[], loc: string},
addFiles: { files: FileNodeInfo[], loc: string}
}>()
export interface Scroller {
$_startIndex: number
@ -335,7 +338,7 @@ export function useLocation (props: Props) {
const refresh = async () => {
try {
np.value?.start()
if (walkModePath.value) {
if (walkModePath.value && walkModePath.value !== currLocation.value) {
await to(walkModePath.value, false)
await delay()
const [firstDir] = sortFiles(currPage.value!.files, sortMethod.value).filter(v => v.type === 'dir')
@ -343,6 +346,7 @@ export function useLocation (props: Props) {
await to(firstDir.fullpath, false)
}
} else {
const { files } = await getTargetFolderFiles(props.target, stack.value.length === 1 ? '/' : currLocation.value)
last(stack.value)!.files = files
}
@ -527,7 +531,7 @@ export function useFileTransfer (props: Props) {
maskClosable: true,
async onOk () {
await moveFiles(props.target, data.path, toPath)
events.emit('removeFiles', [data.path, data.loc])
events.emit('removeFiles', { paths: data.path, loc: data.loc })
await eventEmitter.value.emit('refresh')
}
})
@ -551,10 +555,11 @@ export function useFileItemActions (props: Props, { openNext }: { openNext: (fil
const imageGenInfo = ref('')
const { sortedFiles, previewIdx, multiSelectedIdxs, stack, currLocation, spinning } = useHookShareState().toRefs()
useEventListen('removeFiles', ([paths, loc]: [paths: string[], loc: string]) => {
useEventListen('removeFiles', ({ paths, loc }) => {
if (loc !== currLocation.value) {
return
}
console.log('removeFiles', { paths, loc })
const top = last(stack.value)
if (!top) {
return
@ -565,30 +570,50 @@ export function useFileItemActions (props: Props, { openNext }: { openNext: (fil
}
})
useEventListen('addFiles', ({ files, loc }) => {
if (loc !== currLocation.value) {
return
}
console.log('addFiles', { files, loc })
const top = last(stack.value)
if (!top) {
return
}
top.files.unshift(...files)
})
const q = reactive(new FetchQueue())
const onFileItemClick = async (e: MouseEvent, file: FileNodeInfo) => {
const files = sortedFiles.value
const idx = files.findIndex(v => v.name === file.name)
previewIdx.value = idx
const idxInSelected = multiSelectedIdxs.value.indexOf(idx)
if (e.shiftKey) {
multiSelectedIdxs.value.push(idx)
multiSelectedIdxs.value.sort((a, b) => a - b)
const first = multiSelectedIdxs.value[0]
const last = multiSelectedIdxs.value[multiSelectedIdxs.value.length - 1]
multiSelectedIdxs.value = range(first, last + 1)
console.log(multiSelectedIdxs.value)
if (idxInSelected !== -1) {
multiSelectedIdxs.value.splice(idxInSelected, 1)
} else {
multiSelectedIdxs.value.push(idx)
multiSelectedIdxs.value.sort((a, b) => a - b)
const first = multiSelectedIdxs.value[0]
const last = multiSelectedIdxs.value[multiSelectedIdxs.value.length - 1]
multiSelectedIdxs.value = range(first, last + 1)
}
e.stopPropagation()
} else if (e.ctrlKey || e.metaKey) {
multiSelectedIdxs.value.push(idx)
if (idxInSelected !== -1) {
multiSelectedIdxs.value.splice(idxInSelected, 1)
} else {
multiSelectedIdxs.value.push(idx)
}
e.stopPropagation()
} else {
await openNext(file)
}
}
const pathModule = path
const onContextMenuClick = async (e: MenuInfo, file: FileNodeInfo, idx: number) => {
const url = toRawFileUrl(file)
const path = currLocation.value
@ -619,6 +644,18 @@ export function useFileItemActions (props: Props, { openNext }: { openNext: (fil
case 'send2img2img': return copyImgTo('img2img')
case 'send2inpaint': return copyImgTo('inpaint')
case 'send2extras': return copyImgTo('extras')
case 'send2savedDir': {
const dir = global.autoCompletedDirList.find(v => v.key === 'outdir_save')
if (!dir) {
return message.error(t('unknownSavedDir'))
}
const absolutePath = pathModule.isAbsolute(dir.dir) ? dir.dir : pathModule.normalize(pathModule.join(global.conf!.sd_cwd, dir.dir)).replace(/\\/g, '/')
await moveFiles('local', [file.fullpath], absolutePath)
events.emit('removeFiles', { paths: [file.fullpath], loc: currLocation.value })
events.emit('addFiles', { files: [file], loc: absolutePath })
break;
}
case 'openWithWalkMode': {
stackCache.set(path, stack.value)
const tab = global.tabList[props.tabIdx]
@ -690,7 +727,7 @@ export function useFileItemActions (props: Props, { openNext }: { openNext: (fil
const paths = selectedFiles.map(v => v.fullpath)
await deleteFiles(props.target, paths)
message.success(t('deleteSuccess'))
events.emit('removeFiles', [paths, currLocation.value])
events.emit('removeFiles', { paths: paths, loc: currLocation.value })
resolve()
},
})