fixed loc
parent
b9f70a2a06
commit
f1f59dee74
|
|
@ -13,8 +13,8 @@ Promise.resolve().then(async () => {
|
|||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Infinite Image Browsing</title>
|
||||
<script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-f08bcee4.js"></script>
|
||||
<link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-b5fcdaa7.css">
|
||||
<script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-db6e6f1f.js"></script>
|
||||
<link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-daea5cd8.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -625,6 +625,7 @@ class Folder:
|
|||
|
||||
class ExtraPathType(Enum):
|
||||
scanned = "scanned"
|
||||
scanned_fixed = "scanned-fixed"
|
||||
walk = "walk"
|
||||
cli_only = "cli_access_only"
|
||||
|
||||
|
|
@ -638,7 +639,7 @@ class ExtraPath:
|
|||
def save(self, conn):
|
||||
type_str = '+'.join(self.types)
|
||||
for type in self.types:
|
||||
assert type in [ExtraPathType.walk.value, ExtraPathType.scanned.value]
|
||||
assert type in [ExtraPathType.walk.value, ExtraPathType.scanned.value, ExtraPathType.scanned_fixed.value]
|
||||
with closing(conn.cursor()) as cur:
|
||||
cur.execute(
|
||||
"INSERT INTO extra_path (path, type, alias) VALUES (?, ?, ?) "
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ from scripts.iib.tool import (
|
|||
get_video_type,
|
||||
is_dev,
|
||||
get_modified_date,
|
||||
is_image_file
|
||||
)
|
||||
from scripts.iib.parsers.model import ImageGenerationInfo, ImageGenerationParams
|
||||
from scripts.iib.logger import logger
|
||||
|
|
@ -146,6 +147,8 @@ def build_single_img_idx(conn, file_path, is_rebuild, safe_save_img_tag):
|
|||
type="size",
|
||||
)
|
||||
safe_save_img_tag(ImageTag(img.id, size_tag.id))
|
||||
media_type_tag = Tag.get_or_create(conn, "Image" if is_image_file(file_path) else "Video", 'Media Type')
|
||||
safe_save_img_tag(ImageTag(img.id, media_type_tag.id))
|
||||
for k in [
|
||||
"Model",
|
||||
"Sampler",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ export {}
|
|||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
AAlert: typeof import('ant-design-vue/es')['Alert']
|
||||
ABadge: typeof import('ant-design-vue/es')['Badge']
|
||||
ABadgeRibbon: typeof import('ant-design-vue/es')['BadgeRibbon']
|
||||
ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb']
|
||||
ABreadcrumbItem: typeof import('ant-design-vue/es')['BreadcrumbItem']
|
||||
AButton: typeof import('ant-design-vue/es')['Button']
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
import{d as E,bg as $,v as f,s as M,_ as T,a as c,a0 as W,h as g,c as v,P as z}from"./index-f08bcee4.js";var G=["prefixCls","name","id","type","disabled","readonly","tabindex","autofocus","value","required"],H={prefixCls:String,name:String,id:String,type:String,defaultChecked:{type:[Boolean,Number],default:void 0},checked:{type:[Boolean,Number],default:void 0},disabled:Boolean,tabindex:{type:[Number,String]},readonly:Boolean,autofocus:Boolean,value:z.any,required:Boolean};const L=E({compatConfig:{MODE:3},name:"Checkbox",inheritAttrs:!1,props:$(H,{prefixCls:"rc-checkbox",type:"checkbox",defaultChecked:!1}),emits:["click","change"],setup:function(a,d){var t=d.attrs,h=d.emit,x=d.expose,o=f(a.checked===void 0?a.defaultChecked:a.checked),i=f();M(function(){return a.checked},function(){o.value=a.checked}),x({focus:function(){var e;(e=i.value)===null||e===void 0||e.focus()},blur:function(){var e;(e=i.value)===null||e===void 0||e.blur()}});var l=f(),m=function(e){if(!a.disabled){a.checked===void 0&&(o.value=e.target.checked),e.shiftKey=l.value;var r={target:c(c({},a),{},{checked:e.target.checked}),stopPropagation:function(){e.stopPropagation()},preventDefault:function(){e.preventDefault()},nativeEvent:e};a.checked!==void 0&&(i.value.checked=!!a.checked),h("change",r),l.value=!1}},C=function(e){h("click",e),l.value=e.shiftKey};return function(){var n,e=a.prefixCls,r=a.name,s=a.id,p=a.type,b=a.disabled,K=a.readonly,P=a.tabindex,B=a.autofocus,S=a.value,N=a.required,_=T(a,G),q=t.class,D=t.onFocus,j=t.onBlur,w=t.onKeydown,A=t.onKeypress,F=t.onKeyup,y=c(c({},_),t),O=Object.keys(y).reduce(function(k,u){return(u.substr(0,5)==="aria-"||u.substr(0,5)==="data-"||u==="role")&&(k[u]=y[u]),k},{}),R=W(e,q,(n={},g(n,"".concat(e,"-checked"),o.value),g(n,"".concat(e,"-disabled"),b),n)),V=c(c({name:r,id:s,type:p,readonly:K,disabled:b,tabindex:P,class:"".concat(e,"-input"),checked:!!o.value,autofocus:B,value:S},O),{},{onChange:m,onClick:C,onFocus:D,onBlur:j,onKeydown:w,onKeypress:A,onKeyup:F,required:N});return v("span",{class:R},[v("input",c({ref:i},V),null),v("span",{class:"".concat(e,"-inner")},null)])}}});export{L as V};
|
||||
import{d as E,bo as $,r as f,k as M,_ as T,a as c,ai as W,h as g,c as v,P as z}from"./index-db6e6f1f.js";var G=["prefixCls","name","id","type","disabled","readonly","tabindex","autofocus","value","required"],H={prefixCls:String,name:String,id:String,type:String,defaultChecked:{type:[Boolean,Number],default:void 0},checked:{type:[Boolean,Number],default:void 0},disabled:Boolean,tabindex:{type:[Number,String]},readonly:Boolean,autofocus:Boolean,value:z.any,required:Boolean};const L=E({compatConfig:{MODE:3},name:"Checkbox",inheritAttrs:!1,props:$(H,{prefixCls:"rc-checkbox",type:"checkbox",defaultChecked:!1}),emits:["click","change"],setup:function(a,d){var t=d.attrs,h=d.emit,m=d.expose,o=f(a.checked===void 0?a.defaultChecked:a.checked),i=f();M(function(){return a.checked},function(){o.value=a.checked}),m({focus:function(){var e;(e=i.value)===null||e===void 0||e.focus()},blur:function(){var e;(e=i.value)===null||e===void 0||e.blur()}});var l=f(),x=function(e){if(!a.disabled){a.checked===void 0&&(o.value=e.target.checked),e.shiftKey=l.value;var r={target:c(c({},a),{},{checked:e.target.checked}),stopPropagation:function(){e.stopPropagation()},preventDefault:function(){e.preventDefault()},nativeEvent:e};a.checked!==void 0&&(i.value.checked=!!a.checked),h("change",r),l.value=!1}},C=function(e){h("click",e),l.value=e.shiftKey};return function(){var n,e=a.prefixCls,r=a.name,s=a.id,p=a.type,b=a.disabled,K=a.readonly,P=a.tabindex,B=a.autofocus,S=a.value,N=a.required,_=T(a,G),q=t.class,D=t.onFocus,j=t.onBlur,w=t.onKeydown,A=t.onKeypress,F=t.onKeyup,k=c(c({},_),t),O=Object.keys(k).reduce(function(y,u){return(u.substr(0,5)==="aria-"||u.substr(0,5)==="data-"||u==="role")&&(y[u]=k[u]),y},{}),R=W(e,q,(n={},g(n,"".concat(e,"-checked"),o.value),g(n,"".concat(e,"-disabled"),b),n)),V=c(c({name:r,id:s,type:p,readonly:K,disabled:b,tabindex:P,class:"".concat(e,"-input"),checked:!!o.value,autofocus:B,value:S},O),{},{onChange:x,onClick:C,onFocus:D,onBlur:j,onKeydown:w,onKeypress:A,onKeyup:F,required:N});return v("span",{class:R},[v("input",c({ref:i},V),null),v("span",{class:"".concat(e,"-inner")},null)])}}});export{L as V};
|
||||
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
|
|
@ -1 +1 @@
|
|||
import{d as a,o as t,k as s,c as n,ce as _,q as o}from"./index-f08bcee4.js";const c={class:"img-sli-container"},i=a({__name:"ImgSliPagePane",props:{paneIdx:{},tabIdx:{},left:{},right:{}},setup(l){return(e,r)=>(t(),s("div",c,[n(_,{left:e.left,right:e.right},null,8,["left","right"])]))}});const d=o(i,[["__scopeId","data-v-ae3fb9a8"]]);export{d as default};
|
||||
import{d as a,L as t,N as s,c as n,cv as _,U as o}from"./index-db6e6f1f.js";const c={class:"img-sli-container"},i=a({__name:"ImgSliPagePane",props:{paneIdx:{},tabIdx:{},left:{},right:{}},setup(l){return(e,r)=>(t(),s("div",c,[n(_,{left:e.left,right:e.right},null,8,["left","right"])]))}});const d=o(i,[["__scopeId","data-v-ae3fb9a8"]]);export{d as default};
|
||||
|
|
@ -1 +0,0 @@
|
|||
import{d as se,s as ne,a3 as oe,r as ie,o as u,k as I,c as s,B as e,A as n,E as R,l as d,G as ae,t as a,m as p,z as V,Q as de,p as k,R as D,U as re,V as ce,X as z,am as ue,an as me,bD as pe,q as ge}from"./index-f08bcee4.js";import{S as ve}from"./index-9cd8e050.js";import{L as fe,R as Ie,f as ke,M as _e}from"./MultiSelectKeep-f1e091a9.js";import{g as he,h as Ce,F as we}from"./FileItem-966f0b1f.js";import{c as Se,u as xe}from"./hook-8919e6ff.js";import{o as be}from"./functionalCallableComp-05bdb498.js";import"./index-846c776c.js";import"./index-23966e66.js";const Me=r=>(ue("data-v-479efe51"),r=r(),me(),r),ye={class:"hint"},Ae={class:"action-bar"},Te=Me(()=>d("div",{style:{padding:"16px 0 512px"}},null,-1)),$e={key:1},Fe={class:"no-res-hint"},Re={class:"hint"},Ve={key:2,class:"preview-switch"},De=se({__name:"MatchedImageGrid",props:{tabIdx:{},paneIdx:{},selectedTagIds:{},id:{}},setup(r){const _=r,g=Se(l=>pe(_.selectedTagIds,l)),{queue:B,images:i,onContextMenuClickU:h,stackViewEl:G,previewIdx:c,previewing:C,onPreviewVisibleChange:E,previewImgMove:w,canPreview:S,itemSize:x,gridItems:N,showGenInfo:m,imageGenInfo:b,q:U,multiSelectedIdxs:v,onFileItemClick:J,scroller:M,showMenuIdx:f,onFileDragStart:L,onFileDragEnd:P,cellWidth:q,onScroll:y,saveAllFileAsJson:K,saveLoadedFileAsJson:O}=xe(g);ne(()=>_.selectedTagIds,async()=>{var l;await g.reset(),await oe(),(l=M.value)==null||l.scrollToItem(0),y()},{immediate:!0});const Q=ie(),{onClearAllSelected:W,onSelectAll:X,onReverseSelect:j}=he();return(l,t)=>{const H=_e,Y=re,Z=ce,A=z,ee=z,te=ve;return u(),I("div",{class:"container",ref_key:"stackViewEl",ref:G},[s(H,{show:!!e(v).length||e(Q).keepMultiSelect,onClearAllSelected:e(W),onSelectAll:e(X),onReverseSelect:e(j)},null,8,["show","onClearAllSelected","onSelectAll","onReverseSelect"]),s(te,{size:"large",spinning:!e(B).isIdle},{default:n(()=>{var T,$;return[s(Z,{visible:e(m),"onUpdate:visible":t[1]||(t[1]=o=>R(m)?m.value=o:null),width:"70vw","mask-closable":"",onOk:t[2]||(t[2]=o=>m.value=!1)},{cancelText:n(()=>[]),default:n(()=>[s(Y,{active:"",loading:!e(U).isIdle},{default:n(()=>[d("div",{style:{width:"100%","word-break":"break-all","white-space":"pre-line","max-height":"70vh",overflow:"auto"},onDblclick:t[0]||(t[0]=o=>e(ae)(e(b)))},[d("div",ye,a(l.$t("doubleClickToCopy")),1),p(" "+a(e(b)),1)],32)]),_:1},8,["loading"])]),_:1},8,["visible"]),d("div",Ae,[s(A,{onClick:e(O)},{default:n(()=>[p(a(l.$t("saveLoadedImageAsJson")),1)]),_:1},8,["onClick"]),s(A,{onClick:e(K)},{default:n(()=>[p(a(l.$t("saveAllAsJson")),1)]),_:1},8,["onClick"])]),(T=e(i))!=null&&T.length?(u(),V(e(Ce),{key:0,ref_key:"scroller",ref:M,class:"file-list",items:e(i),"item-size":e(x).first,"key-field":"fullpath","item-secondary-size":e(x).second,gridItems:e(N),onScroll:e(y)},{after:n(()=>[Te]),default:n(({item:o,index:F})=>[s(we,{idx:F,file:o,"cell-width":e(q),"show-menu-idx":e(f),"onUpdate:showMenuIdx":t[3]||(t[3]=le=>R(f)?f.value=le:null),onDragstart:e(L),onDragend:e(P),onFileItemClick:e(J),"full-screen-preview-image-url":e(i)[e(c)]?e(de)(e(i)[e(c)]):"",selected:e(v).includes(F),onContextMenuClick:e(h),onPreviewVisibleChange:e(E),"is-selected-mutil-files":e(v).length>1},null,8,["idx","file","cell-width","show-menu-idx","onDragstart","onDragend","onFileItemClick","full-screen-preview-image-url","selected","onContextMenuClick","onPreviewVisibleChange","is-selected-mutil-files"])]),_:1},8,["items","item-size","item-secondary-size","gridItems","onScroll"])):e(g).load&&l.selectedTagIds.and_tags.length===1&&!(($=l.selectedTagIds.folder_paths_str)!=null&&$.trim())?(u(),I("div",$e,[d("div",Fe,[d("p",Re,a(l.$t("tagSearchNoResultsMessage")),1),s(ee,{onClick:t[4]||(t[4]=o=>e(be)()),type:"primary"},{default:n(()=>[p(a(l.$t("rebuildImageIndex")),1)]),_:1})])])):k("",!0),e(C)?(u(),I("div",Ve,[s(e(fe),{onClick:t[5]||(t[5]=o=>e(w)("prev")),class:D({disable:!e(S)("prev")})},null,8,["class"]),s(e(Ie),{onClick:t[6]||(t[6]=o=>e(w)("next")),class:D({disable:!e(S)("next")})},null,8,["class"])])):k("",!0)]}),_:1},8,["spinning"]),e(C)&&e(i)&&e(i)[e(c)]?(u(),V(ke,{key:0,file:e(i)[e(c)],idx:e(c),onContextMenuClick:e(h)},null,8,["file","idx","onContextMenuClick"])):k("",!0)],512)}}});const Pe=ge(De,[["__scopeId","data-v-479efe51"]]);export{Pe as default};
|
||||
|
|
@ -0,0 +1 @@
|
|||
import{d as se,k as ne,al as oe,V as ie,L as u,N as I,c as s,Y as e,X as n,$ as R,O as d,E as ae,R as a,Q as p,W as V,a8 as de,T as k,a9 as D,ab as re,ac as ce,ae as z,aw as ue,ax as me,bL as pe,U as ge}from"./index-db6e6f1f.js";import{S as ve}from"./index-78e8fb0a.js";import{L as fe,R as Ie,f as ke,M as _e}from"./MultiSelectKeep-c82145ae.js";import{c as we,d as Ce,F as he}from"./FileItem-e0fb56db.js";import{c as Se,u as xe}from"./hook-40c4a7de.js";import{a as be}from"./functionalCallableComp-398e1966.js";import"./shortcut-4f133b16.js";import"./Checkbox-b330ff1b.js";import"./index-fab27d40.js";/* empty css */const Me=r=>(ue("data-v-479efe51"),r=r(),me(),r),ye={class:"hint"},Ae={class:"action-bar"},Te=Me(()=>d("div",{style:{padding:"16px 0 512px"}},null,-1)),$e={key:1},Fe={class:"no-res-hint"},Re={class:"hint"},Ve={key:2,class:"preview-switch"},De=se({__name:"MatchedImageGrid",props:{tabIdx:{},paneIdx:{},selectedTagIds:{},id:{}},setup(r){const _=r,g=Se(l=>pe(_.selectedTagIds,l)),{queue:B,images:i,onContextMenuClickU:w,stackViewEl:G,previewIdx:c,previewing:C,onPreviewVisibleChange:L,previewImgMove:h,canPreview:S,itemSize:x,gridItems:N,showGenInfo:m,imageGenInfo:b,q:E,multiSelectedIdxs:v,onFileItemClick:U,scroller:M,showMenuIdx:f,onFileDragStart:J,onFileDragEnd:O,cellWidth:P,onScroll:y,saveAllFileAsJson:K,saveLoadedFileAsJson:q}=xe(g);ne(()=>_.selectedTagIds,async()=>{var l;await g.reset(),await oe(),(l=M.value)==null||l.scrollToItem(0),y()},{immediate:!0});const Q=ie(),{onClearAllSelected:W,onSelectAll:X,onReverseSelect:Y}=we();return(l,t)=>{const j=_e,H=re,Z=ce,A=z,ee=z,te=ve;return u(),I("div",{class:"container",ref_key:"stackViewEl",ref:G},[s(j,{show:!!e(v).length||e(Q).keepMultiSelect,onClearAllSelected:e(W),onSelectAll:e(X),onReverseSelect:e(Y)},null,8,["show","onClearAllSelected","onSelectAll","onReverseSelect"]),s(te,{size:"large",spinning:!e(B).isIdle},{default:n(()=>{var T,$;return[s(Z,{visible:e(m),"onUpdate:visible":t[1]||(t[1]=o=>R(m)?m.value=o:null),width:"70vw","mask-closable":"",onOk:t[2]||(t[2]=o=>m.value=!1)},{cancelText:n(()=>[]),default:n(()=>[s(H,{active:"",loading:!e(E).isIdle},{default:n(()=>[d("div",{style:{width:"100%","word-break":"break-all","white-space":"pre-line","max-height":"70vh",overflow:"auto"},onDblclick:t[0]||(t[0]=o=>e(ae)(e(b)))},[d("div",ye,a(l.$t("doubleClickToCopy")),1),p(" "+a(e(b)),1)],32)]),_:1},8,["loading"])]),_:1},8,["visible"]),d("div",Ae,[s(A,{onClick:e(q)},{default:n(()=>[p(a(l.$t("saveLoadedImageAsJson")),1)]),_:1},8,["onClick"]),s(A,{onClick:e(K)},{default:n(()=>[p(a(l.$t("saveAllAsJson")),1)]),_:1},8,["onClick"])]),(T=e(i))!=null&&T.length?(u(),V(e(Ce),{key:0,ref_key:"scroller",ref:M,class:"file-list",items:e(i),"item-size":e(x).first,"key-field":"fullpath","item-secondary-size":e(x).second,gridItems:e(N),onScroll:e(y)},{after:n(()=>[Te]),default:n(({item:o,index:F})=>[s(he,{idx:F,file:o,"cell-width":e(P),"show-menu-idx":e(f),"onUpdate:showMenuIdx":t[3]||(t[3]=le=>R(f)?f.value=le:null),onDragstart:e(J),onDragend:e(O),onFileItemClick:e(U),"full-screen-preview-image-url":e(i)[e(c)]?e(de)(e(i)[e(c)]):"",selected:e(v).includes(F),onContextMenuClick:e(w),onPreviewVisibleChange:e(L),"is-selected-mutil-files":e(v).length>1},null,8,["idx","file","cell-width","show-menu-idx","onDragstart","onDragend","onFileItemClick","full-screen-preview-image-url","selected","onContextMenuClick","onPreviewVisibleChange","is-selected-mutil-files"])]),_:1},8,["items","item-size","item-secondary-size","gridItems","onScroll"])):e(g).load&&l.selectedTagIds.and_tags.length===1&&!(($=l.selectedTagIds.folder_paths_str)!=null&&$.trim())?(u(),I("div",$e,[d("div",Fe,[d("p",Re,a(l.$t("tagSearchNoResultsMessage")),1),s(ee,{onClick:t[4]||(t[4]=o=>e(be)()),type:"primary"},{default:n(()=>[p(a(l.$t("rebuildImageIndex")),1)]),_:1})])])):k("",!0),e(C)?(u(),I("div",Ve,[s(e(fe),{onClick:t[5]||(t[5]=o=>e(h)("prev")),class:D({disable:!e(S)("prev")})},null,8,["class"]),s(e(Ie),{onClick:t[6]||(t[6]=o=>e(h)("next")),class:D({disable:!e(S)("next")})},null,8,["class"])])):k("",!0)]}),_:1},8,["spinning"]),e(C)&&e(i)&&e(i)[e(c)]?(u(),V(ke,{key:0,file:e(i)[e(c)],idx:e(c),onContextMenuClick:e(w)},null,8,["file","idx","onContextMenuClick"])):k("",!0)],512)}}});const Ke=ge(De,[["__scopeId","data-v-479efe51"]]);export{Ke as default};
|
||||
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
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
import{d as v,cf as C,bQ as I,o as i,k as _,l as f,c,A as r,m as h,t as d,B as e,z,Q as B,cg as F,ch as x,X as $,q as R}from"./index-f08bcee4.js";import{u as S,b as A,k as E,F as V,h as T}from"./FileItem-966f0b1f.js";import"./functionalCallableComp-05bdb498.js";import"./index-23966e66.js";import"./index-846c776c.js";const L={class:"actions-panel actions"},N={key:0,class:"file-list"},Q={class:"hint"},U=v({__name:"batchDownload",props:{tabIdx:{},paneIdx:{},id:{}},setup(q){const{stackViewEl:k}=S().toRefs(),{itemSize:p,gridItems:w,cellWidth:b}=A(),n=E(),{selectdFiles:l}=C(n),m=I(),y=async t=>{const s=F(t);s&&n.addFiles(s.nodes)},D=async()=>{m.pushAction(async()=>{const t=await x.value.post("/zip",{paths:l.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=>{l.value.splice(t,1)};return(t,s)=>{const a=$;return i(),_("div",{class:"container",ref_key:"stackViewEl",ref:k,onDrop:y},[f("div",L,[c(a,{onClick:s[0]||(s[0]=o=>e(n).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(l).length?(i(),z(e(T),{key:1,ref:"scroller",class:"file-list",items:e(l).slice(),"item-size":e(p).first,"key-field":"fullpath","item-secondary-size":e(p).second,gridItems:e(w)},{default:r(({item:o,index:u})=>[c(V,{idx:u,file:o,"cell-width":e(b),"enable-close-icon":"",onCloseIconClick:H=>g(u),"full-screen-preview-image-url":e(B)(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",Q,d(t.$t("batchDownloaDDragAndDropHint")),1)]))],544)}}});const J=R(U,[["__scopeId","data-v-aab31da2"]]);export{J as default};
|
||||
|
|
@ -0,0 +1 @@
|
|||
import{d as v,cw as C,bY as I,L as l,N as _,O as f,c,X as r,Q as w,R as d,Y as e,W as F,a8 as x,cb as z,cx as B,ae as R,U as $}from"./index-db6e6f1f.js";import{u as S,a as E,k as V,F as A,d as L}from"./FileItem-e0fb56db.js";import"./functionalCallableComp-398e1966.js";import"./index-fab27d40.js";/* empty css */const N={class:"actions-panel actions"},T={key:0,class:"file-list"},U={class:"hint"},H=v({__name:"batchDownload",props:{tabIdx:{},paneIdx:{},id:{}},setup(O){const{stackViewEl:h}=S().toRefs(),{itemSize:p,gridItems:k,cellWidth:b}=E(),i=V(),{selectdFiles:n}=C(i),u=I(),y=async t=>{const s=z(t);s&&i.addFiles(s.nodes)},D=async()=>{u.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=R;return l(),_("div",{class:"container",ref_key:"stackViewEl",ref:h,onDrop:y},[f("div",N,[c(a,{onClick:s[0]||(s[0]=o=>e(i).selectdFiles=[])},{default:r(()=>[w(d(t.$t("clear")),1)]),_:1}),c(a,{onClick:D,type:"primary",loading:!e(u).isIdle},{default:r(()=>[w(d(t.$t("zipDownload")),1)]),_:1},8,["loading"])]),e(n).length?(l(),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:m})=>[c(A,{idx:m,file:o,"cell-width":e(b),"enable-close-icon":"",onCloseIconClick:Q=>g(m),"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"])):(l(),_("div",T,[f("p",U,d(t.$t("batchDownloaDDragAndDropHint")),1)]))],544)}}});const G=$(H,[["__scopeId","data-v-aab31da2"]]);export{G as default};
|
||||
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
|
|
@ -0,0 +1 @@
|
|||
import{d4 as b,c as o,A as w,cx as i,cm as y,V as h,ac as c,d5 as x,d6 as O,ae as I,ci as F,a8 as S,v as l,d7 as M,ap as N,t as _,r as f,ad as v,p as k,Q as D,d8 as V}from"./index-db6e6f1f.js";var p=1/0,P=17976931348623157e292;function A(e){if(!e)return e===0?e:0;if(e=b(e),e===p||e===-p){var t=e<0?-1:1;return t*P}return e===e?e:0}function U(e){var t=e==null?0:e.length;return t?e[t-1]:void 0}var j={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"}}]},name:"download",theme:"outlined"};const C=j;function m(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]!=null?Object(arguments[t]):{},a=Object.keys(n);typeof Object.getOwnPropertySymbols=="function"&&(a=a.concat(Object.getOwnPropertySymbols(n).filter(function(r){return Object.getOwnPropertyDescriptor(n,r).enumerable}))),a.forEach(function(r){T(e,r,n[r])})}return e}function T(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var d=function(t,n){var a=m({},t,n.attrs);return o(w,m({},a,{icon:C}),null)};d.displayName="DownloadOutlined";d.inheritAttrs=!1;const E=d,H=async e=>(await i.value.get("/files",{params:{folder_path:e}})).data,$=async e=>(await i.value.post("/delete_files",{file_paths:e})).data,B=async(e,t,n)=>(await i.value.post("/move_files",{file_paths:e,dest:t,create_dest_folder:n})).data,G=async(e,t,n)=>(await i.value.post("/copy_files",{file_paths:e,dest:t,create_dest_folder:n})).data,R=async e=>{await i.value.post("/mkdirs",{dest_folder:e})},W=e=>{const t=f("");return new Promise(n=>{c.confirm({title:l("inputFolderName"),content:()=>o(v,{value:t.value,"onUpdate:value":a=>t.value=a},null),async onOk(){if(!t.value)return;const a=k(e,t.value);await R(a),n()}})})},L=()=>o("p",{style:{background:"var(--zp-secondary-background)",padding:"8px",borderLeft:"4px solid var(--primary-color)"}},[D("Tips: "),l("multiSelectTips")]),Q=(e,t)=>{const n=y(),a=h(),r=s=>{var u;return!!((u=n.tagMap.get(e.fullpath))!=null&&u.some(g=>g.id===s))};c.confirm({width:"80vw",title:e.name,icon:null,content:()=>o("div",{style:{display:"flex",alignItems:"center",justifyContent:"center",flexDirection:"column"}},[o("video",{style:{maxHeight:x?"80vh":"60vh",maxWidth:"100%",minWidth:"70%"},src:O(e),controls:!0,autoplay:!0},null),o("div",{style:{marginTop:"4px"}},[a.conf.all_custom_tags.map(s=>o("div",{key:s.id,onClick:()=>t==null?void 0:t(s.id),style:{background:r(s.id)?n.getColor(s.name):"var(--zp-primary-background)",color:r(s.id)?"white":n.getColor(s.name),margin:"2px",padding:"2px 16px","border-radius":"4px",display:"inline-block",cursor:"pointer","font-weight":"bold",transition:".5s all ease",border:`2px solid ${n.getColor(s.name)}`,"user-select":"none"}},[s.name]))]),o("div",{class:"actions",style:{marginTop:"16px"}},[o(I,{onClick:()=>F([S(e,!0)])},{icon:o(E,null,null),default:l("download")})])]),maskClosable:!0,wrapClassName:"hidden-antd-btns-modal"})},X=()=>{c.confirm({title:l("confirmRebuildImageIndex"),onOk:async()=>{await M(),N.emit("searchIndexExpired"),_.success(l("rebuildComplete"))}})},Y=e=>{const t=f(e.split(/[\\/]/).pop()??"");return new Promise(n=>{c.confirm({title:l("rename"),content:()=>o(v,{value:t.value,"onUpdate:value":a=>t.value=a},null),async onOk(){if(!t.value)return;const a=await V({path:e,name:t.value});n(a.new_path)}})})};export{L as M,X as a,Y as b,G as c,$ as d,Q as e,H as g,U as l,B as m,W as o,A as t};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
import{u as w,a as y,F as k,d as x}from"./FileItem-e0fb56db.js";import{d as F,V as b,cm as h,r as D,a_ as I,b2 as C,L as V,N as E,c,X as z,Y as e,a8 as S,cb as B,cy as R,U as A}from"./index-db6e6f1f.js";import"./functionalCallableComp-398e1966.js";import"./index-fab27d40.js";/* empty css */const K=F({__name:"gridView",props:{tabIdx:{},paneIdx:{},id:{},removable:{type:Boolean},allowDragAndDrop:{type:Boolean},files:{},paneKey:{}},setup(p){const o=p,d=b(),{stackViewEl:m}=w().toRefs(),{itemSize:i,gridItems:u,cellWidth:f}=y(),g=h(),s=D(o.files??[]),_=async a=>{const l=B(a);o.allowDragAndDrop&&l&&(s.value=R([...s.value,...l.nodes]))},v=a=>{s.value.splice(a,1)};return I(()=>{d.pageFuncExportMap.set(o.paneKey,{getFiles:()=>C(s.value),setFiles:a=>s.value=a})}),(a,l)=>(V(),E("div",{class:"container",ref_key:"stackViewEl",ref:m,onDrop:_},[c(e(x),{ref:"scroller",class:"file-list",items:s.value.slice(),"item-size":e(i).first,"key-field":"fullpath","item-secondary-size":e(i).second,gridItems:e(u)},{default:z(({item:t,index:r})=>{var n;return[c(k,{idx:r,file:t,"cell-width":e(f),"enable-close-icon":o.removable,onCloseIconClick:N=>v(r),"full-screen-preview-image-url":e(S)(t),"extra-tags":(n=t==null?void 0:t.tags)==null?void 0:n.map(e(g).tagConvert),"enable-right-click-menu":!1},null,8,["idx","file","cell-width","enable-close-icon","onCloseIconClick","full-screen-preview-image-url","extra-tags"])]}),_:1},8,["items","item-size","item-secondary-size","gridItems"])],544))}});const H=A(K,[["__scopeId","data-v-f35f4802"]]);export{H as default};
|
||||
|
|
@ -1 +0,0 @@
|
|||
import{u as w,b as k,F as y,h as x}from"./FileItem-966f0b1f.js";import{d as h,r as F,c4 as D,v as I,aS as b,aW as C,o as E,k as S,c,A as V,B as e,Q as z,cg as B,ci as A,q as R}from"./index-f08bcee4.js";import"./functionalCallableComp-05bdb498.js";import"./index-23966e66.js";import"./index-846c776c.js";const q=h({__name:"gridView",props:{tabIdx:{},paneIdx:{},id:{},removable:{type:Boolean},allowDragAndDrop:{type:Boolean},files:{},paneKey:{}},setup(p){const o=p,d=F(),{stackViewEl:m}=w().toRefs(),{itemSize:i,gridItems:u,cellWidth:f}=k(),g=D(),s=I(o.files??[]),_=async a=>{const l=B(a);o.allowDragAndDrop&&l&&(s.value=A([...s.value,...l.nodes]))},v=a=>{s.value.splice(a,1)};return b(()=>{d.pageFuncExportMap.set(o.paneKey,{getFiles:()=>C(s.value),setFiles:a=>s.value=a})}),(a,l)=>(E(),S("div",{class:"container",ref_key:"stackViewEl",ref:m,onDrop:_},[c(e(x),{ref:"scroller",class:"file-list",items:s.value.slice(),"item-size":e(i).first,"key-field":"fullpath","item-secondary-size":e(i).second,gridItems:e(u)},{default:V(({item:t,index:r})=>{var n;return[c(y,{idx:r,file:t,"cell-width":e(f),"enable-close-icon":o.removable,onCloseIconClick:K=>v(r),"full-screen-preview-image-url":e(z)(t),"extra-tags":(n=t==null?void 0:t.tags)==null?void 0:n.map(e(g).tagConvert),"enable-right-click-menu":!1},null,8,["idx","file","cell-width","enable-close-icon","onCloseIconClick","full-screen-preview-image-url","extra-tags"])]}),_:1},8,["items","item-size","item-secondary-size","gridItems"])],544))}});const M=R(q,[["__scopeId","data-v-f35f4802"]]);export{M as default};
|
||||
|
|
@ -0,0 +1 @@
|
|||
import{bl as F,r as g,c6 as q,c7 as x,G as A,z,bY as D,bb as G,c8 as N}from"./index-db6e6f1f.js";import{u as L,a as O,b as Q,e as j}from"./FileItem-e0fb56db.js";import{a as H,b as T,c as U}from"./MultiSelectKeep-c82145ae.js";let W=0;const Y=()=>++W,B=(o,c,{dataUpdateStrategy:l="replace"}={})=>{const n=F([""]),u=g(!1),t=g(),a=g(!1);let f=g(-1);const v=new Set,w=e=>{var s;l==="replace"?t.value=e:l==="merge"&&(A((Array.isArray(t.value)||typeof t.value>"u")&&Array.isArray(e),"数据更新策略为合并时仅可用于值为数组的情况"),t.value=[...(s=t==null?void 0:t.value)!==null&&s!==void 0?s:[],...e])},d=e=>x(void 0,void 0,void 0,function*(){if(a.value||u.value&&typeof e>"u")return!1;a.value=!0;const s=Y();f.value=s;try{let r;if(typeof e=="number"){if(r=n[e],typeof r!="string")return!1}else r=n[n.length-1];const p=yield o(r);if(v.has(s))return v.delete(s),!1;w(c(p));const i=p.cursor;if((e===n.length-1||typeof e!="number")&&(u.value=!i.has_next,i.has_next)){const I=i.next_cursor||i.next;A(typeof I=="string"),n.push(I)}}finally{f.value===s&&(a.value=!1)}return!0}),m=()=>{v.add(f.value),a.value=!1},b=(e=!1)=>x(void 0,void 0,void 0,function*(){const{refetch:s,force:r}=typeof e=="object"?e:{refetch:e};r&&m(),A(!a.value),n.splice(0,n.length,""),a.value=!1,t.value=void 0,u.value=!1,s&&(yield d())}),h=()=>({next:()=>x(void 0,void 0,void 0,function*(){if(a.value)throw new Error("不允许同时迭代");return{done:!(yield d()),value:t.value}})});return q({abort:m,load:u,next:d,res:t,loading:a,cursorStack:n,reset:b,[Symbol.asyncIterator]:h,iter:{[Symbol.asyncIterator]:h}})},$=o=>F(B(o,c=>c.files,{dataUpdateStrategy:"merge"})),ee=o=>{const c=F(new Set),l=z(()=>(o.res??[]).filter(y=>!c.has(y.fullpath))),n=D(),{stackViewEl:u,multiSelectedIdxs:t,stack:a,scroller:f}=L({images:l}).toRefs(),{itemSize:v,gridItems:w,cellWidth:d,onScroll:m}=O({fetchNext:()=>o.next()}),{showMenuIdx:b}=Q(),{onFileDragStart:h,onFileDragEnd:e}=H(),{showGenInfo:s,imageGenInfo:r,q:p,onContextMenuClick:i,onFileItemClick:I}=T({openNext:G}),{previewIdx:C,previewing:_,onPreviewVisibleChange:E,previewImgMove:M,canPreview:J}=U(),P=async(y,S,R)=>{a.value=[{curr:"",files:l.value}],await i(y,S,R)};j("removeFiles",async({paths:y})=>{y.forEach(S=>c.add(S))});const k=()=>{N(l.value)};return{images:l,scroller:f,queue:n,iter:o,onContextMenuClickU:P,stackViewEl:u,previewIdx:C,previewing:_,onPreviewVisibleChange:E,previewImgMove:M,canPreview:J,itemSize:v,gridItems:w,showGenInfo:s,imageGenInfo:r,q:p,onContextMenuClick:i,onFileItemClick:I,showMenuIdx:b,multiSelectedIdxs:t,onFileDragStart:h,onFileDragEnd:e,cellWidth:d,onScroll:m,saveLoadedFileAsJson:k,saveAllFileAsJson:async()=>{for(;!o.load;)await o.next();k()}}};export{$ as c,ee as u};
|
||||
|
|
@ -1 +0,0 @@
|
|||
import{bd as F,v as g,c1 as q,c2 as A,aj as b,ag as D,bQ as N,b3 as Q,c3 as j}from"./index-f08bcee4.js";import{u as z,b as G,f as L,c as O,d as H,e as T,i as U}from"./FileItem-966f0b1f.js";let W=0;const B=()=>++W,K=(o,c,{dataUpdateStrategy:l="replace"}={})=>{const n=F([""]),u=g(!1),t=g(),a=g(!1);let f=g(-1);const v=new Set,w=e=>{var s;l==="replace"?t.value=e:l==="merge"&&(b((Array.isArray(t.value)||typeof t.value>"u")&&Array.isArray(e),"数据更新策略为合并时仅可用于值为数组的情况"),t.value=[...(s=t==null?void 0:t.value)!==null&&s!==void 0?s:[],...e])},d=e=>A(void 0,void 0,void 0,function*(){if(a.value||u.value&&typeof e>"u")return!1;a.value=!0;const s=B();f.value=s;try{let r;if(typeof e=="number"){if(r=n[e],typeof r!="string")return!1}else r=n[n.length-1];const m=yield o(r);if(v.has(s))return v.delete(s),!1;w(c(m));const i=m.cursor;if((e===n.length-1||typeof e!="number")&&(u.value=!i.has_next,i.has_next)){const p=i.next_cursor||i.next;b(typeof p=="string"),n.push(p)}}finally{f.value===s&&(a.value=!1)}return!0}),h=()=>{v.add(f.value),a.value=!1},S=(e=!1)=>A(void 0,void 0,void 0,function*(){const{refetch:s,force:r}=typeof e=="object"?e:{refetch:e};r&&h(),b(!a.value),n.splice(0,n.length,""),a.value=!1,t.value=void 0,u.value=!1,s&&(yield d())}),I=()=>({next:()=>A(void 0,void 0,void 0,function*(){if(a.value)throw new Error("不允许同时迭代");return{done:!(yield d()),value:t.value}})});return q({abort:h,load:u,next:d,res:t,loading:a,cursorStack:n,reset:S,[Symbol.asyncIterator]:I,iter:{[Symbol.asyncIterator]:I}})},Z=o=>F(K(o,c=>c.files,{dataUpdateStrategy:"merge"})),$=o=>{const c=F(new Set),l=D(()=>(o.res??[]).filter(y=>!c.has(y.fullpath))),n=N(),{stackViewEl:u,multiSelectedIdxs:t,stack:a,scroller:f}=z({images:l}).toRefs(),{itemSize:v,gridItems:w,cellWidth:d,onScroll:h}=G({fetchNext:()=>o.next()}),{showMenuIdx:S}=L(),{onFileDragStart:I,onFileDragEnd:e}=O(),{showGenInfo:s,imageGenInfo:r,q:m,onContextMenuClick:i,onFileItemClick:p}=H({openNext:Q}),{previewIdx:C,previewing:_,onPreviewVisibleChange:E,previewImgMove:M,canPreview:J}=T(),P=async(y,x,R)=>{a.value=[{curr:"",files:l.value}],await i(y,x,R)};U("removeFiles",async({paths:y})=>{y.forEach(x=>c.add(x))});const k=()=>{j(l.value)};return{images:l,scroller:f,queue:n,iter:o,onContextMenuClickU:P,stackViewEl:u,previewIdx:C,previewing:_,onPreviewVisibleChange:E,previewImgMove:M,canPreview:J,itemSize:v,gridItems:w,showGenInfo:s,imageGenInfo:r,q:m,onContextMenuClick:i,onFileItemClick:p,showMenuIdx:S,multiSelectedIdxs:t,onFileDragStart:I,onFileDragEnd:e,cellWidth:d,onScroll:h,saveLoadedFileAsJson:k,saveAllFileAsJson:async()=>{for(;!o.load;)await o.next();k()}}};export{Z as c,$ as u};
|
||||
|
|
@ -1 +1 @@
|
|||
import{d as z,bg as D,Z as A,cb as j,a3 as k,ao as V,cc as B,cd as y,e as $,c as a,_ as T,h as r,a as P,br as M,P as b}from"./index-f08bcee4.js";var O=["class","style"],W=function(){return{prefixCls:String,spinning:{type:Boolean,default:void 0},size:String,wrapperClassName:String,tip:b.any,delay:Number,indicator:b.any}},p=null;function Z(t,n){return!!t&&!!n&&!isNaN(Number(n))}function F(t){var n=t.indicator;p=typeof n=="function"?n:function(){return a(n,null,null)}}const G=z({compatConfig:{MODE:3},name:"ASpin",inheritAttrs:!1,props:D(W(),{size:"default",spinning:!0,wrapperClassName:""}),setup:function(){return{originalUpdateSpinning:null,configProvider:A("configProvider",j)}},data:function(){var n=this.spinning,e=this.delay,i=Z(n,e);return{sSpinning:n&&!i}},created:function(){this.originalUpdateSpinning=this.updateSpinning,this.debouncifyUpdateSpinning(this.$props)},mounted:function(){this.updateSpinning()},updated:function(){var n=this;k(function(){n.debouncifyUpdateSpinning(),n.updateSpinning()})},beforeUnmount:function(){this.cancelExistingSpin()},methods:{debouncifyUpdateSpinning:function(n){var e=n||this.$props,i=e.delay;i&&(this.cancelExistingSpin(),this.updateSpinning=V(this.originalUpdateSpinning,i))},updateSpinning:function(){var n=this.spinning,e=this.sSpinning;e!==n&&(this.sSpinning=n)},cancelExistingSpin:function(){var n=this.updateSpinning;n&&n.cancel&&n.cancel()},renderIndicator:function(n){var e="".concat(n,"-dot"),i=B(this,"indicator");return i===null?null:(Array.isArray(i)&&(i=i.length===1?i[0]:i),y(i)?$(i,{class:e}):p&&y(p())?$(p(),{class:e}):a("span",{class:"".concat(e," ").concat(n,"-dot-spin")},[a("i",{class:"".concat(n,"-dot-item")},null),a("i",{class:"".concat(n,"-dot-item")},null),a("i",{class:"".concat(n,"-dot-item")},null),a("i",{class:"".concat(n,"-dot-item")},null)]))}},render:function(){var n,e,i,o=this.$props,f=o.size,x=o.prefixCls,h=o.tip,d=h===void 0?(n=(e=this.$slots).tip)===null||n===void 0?void 0:n.call(e):h,N=o.wrapperClassName,l=this.$attrs,v=l.class,_=l.style,C=T(l,O),S=this.configProvider,U=S.getPrefixCls,E=S.direction,s=U("spin",x),u=this.sSpinning,I=(i={},r(i,s,!0),r(i,"".concat(s,"-sm"),f==="small"),r(i,"".concat(s,"-lg"),f==="large"),r(i,"".concat(s,"-spinning"),u),r(i,"".concat(s,"-show-text"),!!d),r(i,"".concat(s,"-rtl"),E==="rtl"),r(i,v,!!v),i),m=a("div",P(P({},C),{},{style:_,class:I}),[this.renderIndicator(s),d?a("div",{class:"".concat(s,"-text")},[d]):null]),g=M(this);if(g&&g.length){var c,w=(c={},r(c,"".concat(s,"-container"),!0),r(c,"".concat(s,"-blur"),u),c);return a("div",{class:["".concat(s,"-nested-loading"),N]},[u&&a("div",{key:"loading"},[m]),a("div",{class:w,key:"container"},[g])])}return m}});export{G as S,F as s};
|
||||
import{d as w,bo as D,ag as A,ct as j,al as k,l as V,cu as B,cc as y,e as $,c as a,_ as T,h as r,a as P,bz as M,P as b}from"./index-db6e6f1f.js";var O=["class","style"],W=function(){return{prefixCls:String,spinning:{type:Boolean,default:void 0},size:String,wrapperClassName:String,tip:b.any,delay:Number,indicator:b.any}},p=null;function q(t,n){return!!t&&!!n&&!isNaN(Number(n))}function G(t){var n=t.indicator;p=typeof n=="function"?n:function(){return a(n,null,null)}}const H=w({compatConfig:{MODE:3},name:"ASpin",inheritAttrs:!1,props:D(W(),{size:"default",spinning:!0,wrapperClassName:""}),setup:function(){return{originalUpdateSpinning:null,configProvider:A("configProvider",j)}},data:function(){var n=this.spinning,e=this.delay,i=q(n,e);return{sSpinning:n&&!i}},created:function(){this.originalUpdateSpinning=this.updateSpinning,this.debouncifyUpdateSpinning(this.$props)},mounted:function(){this.updateSpinning()},updated:function(){var n=this;k(function(){n.debouncifyUpdateSpinning(),n.updateSpinning()})},beforeUnmount:function(){this.cancelExistingSpin()},methods:{debouncifyUpdateSpinning:function(n){var e=n||this.$props,i=e.delay;i&&(this.cancelExistingSpin(),this.updateSpinning=V(this.originalUpdateSpinning,i))},updateSpinning:function(){var n=this.spinning,e=this.sSpinning;e!==n&&(this.sSpinning=n)},cancelExistingSpin:function(){var n=this.updateSpinning;n&&n.cancel&&n.cancel()},renderIndicator:function(n){var e="".concat(n,"-dot"),i=B(this,"indicator");return i===null?null:(Array.isArray(i)&&(i=i.length===1?i[0]:i),y(i)?$(i,{class:e}):p&&y(p())?$(p(),{class:e}):a("span",{class:"".concat(e," ").concat(n,"-dot-spin")},[a("i",{class:"".concat(n,"-dot-item")},null),a("i",{class:"".concat(n,"-dot-item")},null),a("i",{class:"".concat(n,"-dot-item")},null),a("i",{class:"".concat(n,"-dot-item")},null)]))}},render:function(){var n,e,i,o=this.$props,f=o.size,x=o.prefixCls,h=o.tip,d=h===void 0?(n=(e=this.$slots).tip)===null||n===void 0?void 0:n.call(e):h,N=o.wrapperClassName,l=this.$attrs,v=l.class,_=l.style,C=T(l,O),S=this.configProvider,U=S.getPrefixCls,z=S.direction,s=U("spin",x),u=this.sSpinning,E=(i={},r(i,s,!0),r(i,"".concat(s,"-sm"),f==="small"),r(i,"".concat(s,"-lg"),f==="large"),r(i,"".concat(s,"-spinning"),u),r(i,"".concat(s,"-show-text"),!!d),r(i,"".concat(s,"-rtl"),z==="rtl"),r(i,v,!!v),i),m=a("div",P(P({},C),{},{style:_,class:E}),[this.renderIndicator(s),d?a("div",{class:"".concat(s,"-text")},[d]):null]),g=M(this);if(g&&g.length){var c,I=(c={},r(c,"".concat(s,"-container"),!0),r(c,"".concat(s,"-blur"),u),c);return a("div",{class:["".concat(s,"-nested-loading"),N]},[u&&a("div",{key:"loading"},[m]),a("div",{class:I,key:"container"},[g])])}return m}});export{H as S,G as s};
|
||||
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
File diff suppressed because one or more lines are too long
|
|
@ -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)}}.base-info[data-v-afd25667]{position:absolute;padding:4px;font-size:.8em;background:var(--zp-primary-background);color:var(--zp-primary);left:0;bottom:0;border-top-right-radius:4px}.preview-switch[data-v-b8160253]{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-b8160253]{color:#fff;margin:16px;font-size:4em;pointer-events:all;cursor:pointer}.preview-switch>*.disable[data-v-b8160253]{opacity:0;pointer-events:none;cursor:none}.location-act[data-v-b8160253]{margin-left:8px}.location-act .copy[data-v-b8160253]{margin-right:4px}@media (max-width: 768px){.location-act[data-v-b8160253]{display:flex;flex-direction:column}.location-act>*[data-v-b8160253],.location-act .copy[data-v-b8160253]{margin:2px}}.breadcrumb[data-v-b8160253]{display:flex;align-items:center}.breadcrumb>*[data-v-b8160253]{margin-right:4px}@media (max-width: 768px){.breadcrumb[data-v-b8160253]{width:100%}.breadcrumb .ant-breadcrumb>*[data-v-b8160253]{display:inline-block}}.container[data-v-b8160253]{background:var(--zp-secondary-background);height:var(--pane-max-height)}.location-bar[data-v-b8160253]{padding:4px 16px;background:var(--zp-primary-background);border-bottom:1px solid var(--zp-border);display:flex;align-items:center;justify-content:space-between}@media (max-width: 768px){.location-bar[data-v-b8160253]{flex-direction:column}.location-bar[data-v-b8160253] ::-webkit-scrollbar{height:2px;background-color:var(--zp-secondary-variant-background)}.location-bar .actions[data-v-b8160253]{padding:4px 0;width:100%;overflow:auto;display:flex;align-items:center}.location-bar .actions>*[data-v-b8160253]{flex-shrink:0}}.location-bar .actions[data-v-b8160253]{display:flex;align-items:center;flex-shrink:0}.location-bar a.opt[data-v-b8160253]{margin-left:8px}.view[data-v-b8160253]{padding:8px;height:calc(100vh - 48px)}.view .file-list[data-v-b8160253]{list-style:none;padding:8px;height:100%;overflow:auto}.hint[data-v-b8160253]{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)}}.base-info[data-v-afd25667]{position:absolute;padding:4px;font-size:.8em;background:var(--zp-primary-background);color:var(--zp-primary);left:0;bottom:0;border-top-right-radius:4px}.preview-switch[data-v-817f474b]{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-817f474b]{color:#fff;margin:16px;font-size:4em;pointer-events:all;cursor:pointer}.preview-switch>*.disable[data-v-817f474b]{opacity:0;pointer-events:none;cursor:none}.location-act[data-v-817f474b]{margin-left:8px}.location-act .copy[data-v-817f474b]{margin-right:4px}@media (max-width: 768px){.location-act[data-v-817f474b]{display:flex;flex-direction:column}.location-act>*[data-v-817f474b],.location-act .copy[data-v-817f474b]{margin:2px}}.breadcrumb[data-v-817f474b]{display:flex;align-items:center}.breadcrumb>*[data-v-817f474b]{margin-right:4px}@media (max-width: 768px){.breadcrumb[data-v-817f474b]{width:100%}.breadcrumb .ant-breadcrumb>*[data-v-817f474b]{display:inline-block}}.container[data-v-817f474b]{background:var(--zp-secondary-background);height:var(--pane-max-height)}.location-bar[data-v-817f474b]{padding:4px 16px;background:var(--zp-primary-background);border-bottom:1px solid var(--zp-border);display:flex;align-items:center;justify-content:space-between}@media (max-width: 768px){.location-bar[data-v-817f474b]{flex-direction:column}.location-bar[data-v-817f474b] ::-webkit-scrollbar{height:2px;background-color:var(--zp-secondary-variant-background)}.location-bar .actions[data-v-817f474b]{padding:4px 0;width:100%;overflow:auto;display:flex;align-items:center}.location-bar .actions>*[data-v-817f474b]{flex-shrink:0}}.location-bar .actions[data-v-817f474b]{display:flex;align-items:center;flex-shrink:0}.location-bar a.opt[data-v-817f474b]{margin-left:8px}.view[data-v-817f474b]{padding:8px;height:calc(100vh - 48px)}.view .file-list[data-v-817f474b]{list-style:none;padding:8px;height:100%;overflow:auto}.hint[data-v-817f474b]{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
|
|
@ -7,8 +7,8 @@
|
|||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Infinite Image Browsing</title>
|
||||
<script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-f08bcee4.js"></script>
|
||||
<link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-b5fcdaa7.css">
|
||||
<script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-db6e6f1f.js"></script>
|
||||
<link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-daea5cd8.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ export const getImagesBySubstr = async (req: SearchBySubstrReq) => {
|
|||
}
|
||||
|
||||
const extraPaths = '/db/extra_paths'
|
||||
export type ExtraPathType = 'scanned' | 'walk' | 'cli_access_only' | ''
|
||||
export type ExtraPathType = 'scanned' | 'walk' | 'cli_access_only' | '' | 'scanned-fixed'
|
||||
|
||||
export interface ExtraPathModel {
|
||||
path: string
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import type { FileNodeInfo } from '@/api/files'
|
|||
import { isImageFile, isVideoFile } from '@/util'
|
||||
import { toImageThumbnailUrl, toVideoCoverUrl, toRawFileUrl } from '@/util/file'
|
||||
import type { MenuInfo } from 'ant-design-vue/lib/menu/src/interface'
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import ContextMenu from './ContextMenu.vue'
|
||||
import ChangeIndicator from './ChangeIndicator.vue'
|
||||
import { useTagStore } from '@/store/useTagStore'
|
||||
|
|
@ -16,13 +16,15 @@ import { openVideoModal } from './functionalCallableComp'
|
|||
import type { GenDiffInfo } from '@/api/files'
|
||||
import { play } from '@/icon'
|
||||
import { Top4MediaInfo } from '@/api'
|
||||
import { watch } from 'vue'
|
||||
import { debounce } from 'lodash-es'
|
||||
|
||||
const global = useGlobalStore()
|
||||
const tagStore = useTagStore()
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
file: FileNodeInfo
|
||||
file: FileNodeInfo,
|
||||
idx: number
|
||||
selected?: boolean
|
||||
showMenuIdx?: number
|
||||
|
|
@ -31,29 +33,34 @@ const props = withDefaults(
|
|||
enableRightClickMenu?: boolean,
|
||||
enableCloseIcon?: boolean,
|
||||
isSelectedMutilFiles?: boolean
|
||||
genDiffToPrevious?: GenDiffInfo
|
||||
genDiffToNext?: GenDiffInfo
|
||||
genInfo?: string
|
||||
enableChangeIndicator?: boolean
|
||||
extraTags?: Tag[]
|
||||
coverFiles?: Top4MediaInfo[]
|
||||
getGenDiff?: (ownGenInfo: any, idx: any, increment: any, ownFile: FileNodeInfo) => GenDiffInfo,
|
||||
getGenDiffWatchDep?: (idx: number) => any
|
||||
}>(),
|
||||
{
|
||||
selected: false, enableRightClickMenu: true, enableCloseIcon: false, genDiffToNext: () => ({
|
||||
empty: true,
|
||||
ownFile: '',
|
||||
otherFile: '',
|
||||
diff: '',
|
||||
}), genDiffToPrevious: () => ({
|
||||
empty: true,
|
||||
ownFile: '',
|
||||
otherFile: '',
|
||||
diff: '',
|
||||
})
|
||||
selected: false, enableRightClickMenu: true, enableCloseIcon: false
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
const genDiffToPrevious = ref<GenDiffInfo>()
|
||||
const genDiffToNext = ref<GenDiffInfo>()
|
||||
const calcGenInfoDiff = debounce(() => {
|
||||
const { getGenDiff, file, idx } = props
|
||||
if (!getGenDiff) return
|
||||
genDiffToNext.value = getGenDiff(file.gen_info_obj, idx, 1, file)
|
||||
genDiffToPrevious.value = getGenDiff(file.gen_info_obj, idx, -1, file)
|
||||
}, 200 + 100 * Math.random())
|
||||
|
||||
watch(() => props.getGenDiffWatchDep?.(props.idx), () => {
|
||||
genDiffToNext.value = undefined
|
||||
genDiffToPrevious.value = undefined
|
||||
calcGenInfoDiff()
|
||||
}, { immediate: true, deep: true })
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:showMenuIdx': [v: number],
|
||||
'fileItemClick': [event: MouseEvent, file: FileNodeInfo, idx: number],
|
||||
|
|
@ -130,8 +137,8 @@ const taggleLikeTag = () => {
|
|||
<div :key="file.fullpath" :class="`idx-${idx} item-content`" v-if="isImageFile(file.name)">
|
||||
|
||||
<!-- change indicators -->
|
||||
<ChangeIndicator v-if="enableChangeIndicator" :gen-diff-to-next="genDiffToNext"
|
||||
:gen-diff-to-previous="genDiffToPrevious" />
|
||||
<ChangeIndicator v-if="enableChangeIndicator && genDiffToNext && genDiffToPrevious"
|
||||
:gen-diff-to-next="genDiffToNext" :gen-diff-to-previous="genDiffToPrevious" />
|
||||
<!-- change indicators END -->
|
||||
|
||||
<a-image :src="imageSrc" :fallback="fallbackImage" :preview="{
|
||||
|
|
@ -164,7 +171,7 @@ const taggleLikeTag = () => {
|
|||
:src="item.media_type === 'image' ? toImageThumbnailUrl(item) : toVideoCoverUrl(item)"
|
||||
v-for="item in coverFiles" :key="item.fullpath">
|
||||
</div>
|
||||
|
||||
|
||||
<folder-open-outlined class="icon center" v-else />
|
||||
</div>
|
||||
<div class="profile" v-if="cellWidth > 128">
|
||||
|
|
|
|||
|
|
@ -105,7 +105,6 @@ export const de: Partial<IIBI18nMap> = {
|
|||
restoreLastRecord: 'Letztes Verzeichnis wiederherstellen',
|
||||
launch: 'Ausführen',
|
||||
walkMode: 'Verwende den Walk-Modus, um Bilder zu durchsuchen',
|
||||
launchFromQuickMove: 'Ausführen aus Schnellzugriff',
|
||||
recent: 'Kürzlich',
|
||||
emptyStartPage: 'Leere Startseite',
|
||||
t2i: 'Text-zu-Bild',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { IIBI18nMap } from '.'
|
||||
|
||||
export const en: IIBI18nMap = {
|
||||
type: 'Type',
|
||||
filterByKeyword: 'Filter tags by keyword',
|
||||
loadmore: 'Load more',
|
||||
rename: 'Rename',
|
||||
|
|
@ -116,7 +117,7 @@ export const en: IIBI18nMap = {
|
|||
serverKeyRequired:
|
||||
'The server has configured a key. You must provide the same key to continue using it.',
|
||||
removeFromSearchScanPathAndQuickMove: 'Remove from Search Scan Path and Quick Move',
|
||||
addToSearchScanPathAndQuickMove: 'Add to Search Scan Path and Quick Move',
|
||||
addToSearchScanPathAndQuickMove: 'Add to Search Scan Path',
|
||||
openWithLocalFileBrowser: 'Open with Local File Browser',
|
||||
'fuzzy-search-noResults': 'Nothing was found',
|
||||
'fuzzy-search-placeholder': 'Enter a part of the image information or filename to search',
|
||||
|
|
@ -217,7 +218,7 @@ export const en: IIBI18nMap = {
|
|||
restoreLastRecord: 'Restore last record',
|
||||
launch: 'Launch',
|
||||
walkMode: 'Use Walk mode to browse images',
|
||||
launchFromQuickMove: 'Launch from Quick Move',
|
||||
launchFromNormalAndFixed: 'Use Normal / Fixed mode to browse images',
|
||||
recent: 'Recent',
|
||||
emptyStartPage: 'Empty start page',
|
||||
t2i: 'txt2img',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
export const zhHans = {
|
||||
type: '类型',
|
||||
filterByKeyword: '输入标签关键词过滤',
|
||||
loadmore: '加载更多',
|
||||
rename: '重命名',
|
||||
|
|
@ -79,7 +80,7 @@ export const zhHans = {
|
|||
restoreLastRecord: '还原上次记录',
|
||||
launch: '启动',
|
||||
walkMode: '使用 Walk 模式浏览图片',
|
||||
launchFromQuickMove: '从快速移动启动',
|
||||
launchFromNormalAndFixed: '使用 Normal / Fixed 模式浏览图片',
|
||||
recent: '最近',
|
||||
emptyStartPage: '空启动页',
|
||||
t2i: '文生图',
|
||||
|
|
@ -143,7 +144,7 @@ export const zhHans = {
|
|||
'fuzzy-search-placeholder': '输入图像信息或者文件名的一部分来进行搜索',
|
||||
'fuzzy-search-noResults': '什么都没找到',
|
||||
openWithLocalFileBrowser: '使用本地文件浏览器打开',
|
||||
addToSearchScanPathAndQuickMove: '添加到搜索扫描路径和快速移动',
|
||||
addToSearchScanPathAndQuickMove: '添加到搜索扫描路径',
|
||||
removeFromSearchScanPathAndQuickMove: '从搜索扫描路径和快速移动中移除',
|
||||
serverKeyRequired: '服务器配置了密匙,你必须提供相同的密匙才能继续使用',
|
||||
shortcutKey: '快捷键(仅允许在全屏查看下使用)',
|
||||
|
|
|
|||
|
|
@ -83,7 +83,8 @@ export const zhHant: Partial<IIBI18nMap> = {
|
|||
restoreLastRecord: '還原上次記錄',
|
||||
launch: '啟動',
|
||||
walkMode: '使用 Walk 模式瀏覽圖片',
|
||||
launchFromQuickMove: '從快速移動啟動',
|
||||
|
||||
launchFromNormalAndFixed: '使用 Normal / Fixed 模式瀏覽圖片',
|
||||
recent: '最近',
|
||||
emptyStartPage: '空啟動頁',
|
||||
t2i: '文生圖',
|
||||
|
|
@ -147,7 +148,7 @@ export const zhHant: Partial<IIBI18nMap> = {
|
|||
'fuzzy-search-placeholder': '輸入圖片信息或者文件名的一部分來進行搜尋',
|
||||
'fuzzy-search-noResults': '什麼都沒找到',
|
||||
openWithLocalFileBrowser: '使用本地檔案瀏覽器打開',
|
||||
addToSearchScanPathAndQuickMove: '加入搜尋掃描路徑和快速移動',
|
||||
addToSearchScanPathAndQuickMove: '加入搜尋掃描路徑',
|
||||
removeFromSearchScanPathAndQuickMove: '從搜尋掃描路徑和快速移動中移除',
|
||||
serverKeyRequired: '伺服器配置了密鑰,你必须提供相同的密鑰才能繼續使用',
|
||||
shortcutKey: '快速鍵(僅允許在全螢幕檢視下使用)',
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ tryOnMounted(async () => {
|
|||
})
|
||||
})
|
||||
watch(useDocumentVisibility(), v => v && emitReturnToIIB())
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div ref="container">
|
||||
|
|
|
|||
|
|
@ -9,10 +9,24 @@ import { cloneDeep } from 'lodash-es'
|
|||
import { useImgSliStore } from '@/store/useImgSli'
|
||||
import { addToExtraPath, onAliasExtraPathClick, onRemoveExtraPathClick } from './extraPathControlFunc'
|
||||
import actionContextMenu from './actionContextMenu.vue'
|
||||
import { ExtraPathType } from '@/api/db'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
const global = useGlobalStore()
|
||||
const imgsli = useImgSliStore()
|
||||
const props = defineProps<{ tabIdx: number; paneIdx: number }>()
|
||||
const props = defineProps<{
|
||||
tabIdx: number; paneIdx: number, popAddPathModal?: {
|
||||
path: string
|
||||
type: ExtraPathType
|
||||
}
|
||||
}>()
|
||||
|
||||
onMounted(() => {
|
||||
if (props.popAddPathModal) {
|
||||
addToExtraPath(props.popAddPathModal.type, props.popAddPathModal.path)
|
||||
}
|
||||
})
|
||||
|
||||
const compCnMap: Partial<Record<TabPane['type'], string>> = {
|
||||
local: t('local'),
|
||||
'tag-search': t('imgSearch'),
|
||||
|
|
@ -20,8 +34,8 @@ const compCnMap: Partial<Record<TabPane['type'], string>> = {
|
|||
'global-setting': t('globalSettings'),
|
||||
'batch-download': t('batchDownload') + ' / ' + t('archive')
|
||||
}
|
||||
|
||||
const createPane = (type: TabPane['type'], path?: string, walkMode = false) => {
|
||||
type FileTransModeIn = 'preset' | ExtraPathType
|
||||
const createPane = (type: TabPane['type'], path?: string, mode?: FileTransModeIn) => {
|
||||
let pane: TabPane
|
||||
switch (type) {
|
||||
case 'grid-view':
|
||||
|
|
@ -41,28 +55,28 @@ const createPane = (type: TabPane['type'], path?: string, walkMode = false) => {
|
|||
name: compCnMap[type]!,
|
||||
key: Date.now() + uniqueId(),
|
||||
path,
|
||||
walkModePath: walkMode ? path : undefined
|
||||
mode: mode === 'scanned-fixed' || mode === 'walk' ? mode : 'scanned'
|
||||
}
|
||||
}
|
||||
return pane
|
||||
}
|
||||
const openInCurrentTab = (type: TabPane['type'], path?: string, walkMode = false) => {
|
||||
const pane = createPane(type, path, walkMode)
|
||||
const openInCurrentTab = (type: TabPane['type'], path?: string, mode?: FileTransModeIn) => {
|
||||
const pane = createPane(type, path, mode)
|
||||
if (!pane) return
|
||||
const tab = global.tabList[props.tabIdx]
|
||||
tab.panes.splice(props.paneIdx, 1, pane)
|
||||
tab.key = pane.key
|
||||
}
|
||||
|
||||
const openInNewTab = (type: TabPane['type'], path?: string, walkMode = false) => {
|
||||
const pane = createPane(type, path, walkMode)
|
||||
const openInNewTab = (type: TabPane['type'], path?: string, mode?: FileTransModeIn) => {
|
||||
const pane = createPane(type, path, mode)
|
||||
if (!pane) return
|
||||
const tab = global.tabList[props.tabIdx]
|
||||
tab.panes.push(pane)
|
||||
}
|
||||
|
||||
const openOnTheRight = (type: TabPane['type'], path?: string, walkMode = false) => {
|
||||
const pane = createPane(type, path, walkMode)
|
||||
const openOnTheRight = (type: TabPane['type'], path?: string, mode?: FileTransModeIn) => {
|
||||
const pane = createPane(type, path, mode)
|
||||
if (!pane) return
|
||||
let tab = global.tabList[props.tabIdx + 1]
|
||||
if (!tab) {
|
||||
|
|
@ -115,9 +129,9 @@ const restoreRecord = () => {
|
|||
<a href="https://github.com/zanllp/sd-webui-infinite-image-browsing/issues/90" target="_blank"
|
||||
class="last-record">{{ $t('faq') }}</a>
|
||||
<a-radio-group v-model:value="global.darkModeControl" button-style="solid">
|
||||
<a-radio-button value="light">light</a-radio-button>
|
||||
<a-radio-button value="auto">auto</a-radio-button>
|
||||
<a-radio-button value="dark">dark</a-radio-button>
|
||||
<a-radio-button value="light">Light</a-radio-button>
|
||||
<a-radio-button value="auto">Auto</a-radio-button>
|
||||
<a-radio-button value="dark">Dark</a-radio-button>
|
||||
</a-radio-group>
|
||||
</div>
|
||||
<a-alert show-icon v-if="global.conf?.enable_access_control && !global.dontShowAgain">
|
||||
|
|
@ -155,9 +169,9 @@ const restoreRecord = () => {
|
|||
</span>
|
||||
</li>
|
||||
<actionContextMenu v-for="dir in walkModeSupportedDir" :key="dir.key"
|
||||
@open-in-new-tab="openInNewTab('local', dir.dir, true)"
|
||||
@open-on-the-right="openOnTheRight('local', dir.dir, true)">
|
||||
<li class="item rem" @click.prevent="openInCurrentTab('local', dir.dir, true)">
|
||||
@open-in-new-tab="openInNewTab('local', dir.dir, 'walk')"
|
||||
@open-on-the-right="openOnTheRight('local', dir.dir, 'walk')">
|
||||
<li class="item rem" @click.prevent="openInCurrentTab('local', dir.dir, 'walk')">
|
||||
<span class="text line-clamp-2">{{ dir.zh }}</span>
|
||||
<template v-if="dir.can_delete">
|
||||
<AButton type="link" @click.stop="onAliasExtraPathClick(dir.dir)">{{ $t('alias') }}
|
||||
|
|
@ -171,27 +185,31 @@ const restoreRecord = () => {
|
|||
</ul>
|
||||
</div>
|
||||
<div class="feature-item" v-if="global.quickMovePaths.length">
|
||||
<h2>{{ $t('launchFromQuickMove') }}</h2>
|
||||
<h2>{{ $t('launchFromNormalAndFixed') }}</h2>
|
||||
<ul>
|
||||
<li @click="addToExtraPath('scanned')" class="item">
|
||||
<span class="text line-clamp-1">
|
||||
<PlusOutlined /> {{ $t('add') }}
|
||||
</span>
|
||||
</li>
|
||||
<actionContextMenu
|
||||
v-for="dir in global.quickMovePaths.filter(({ types: ts }) => ts.includes('cli_access_only') || ts.includes('preset') || ts.includes('scanned'))"
|
||||
:key="dir.key" @open-in-new-tab="openInNewTab('local', dir.dir)"
|
||||
@open-on-the-right="openOnTheRight('local', dir.dir)">
|
||||
<li class="item rem" @click.prevent="openInCurrentTab('local', dir.dir)">
|
||||
<span class="text line-clamp-2">{{ dir.zh }}</span>
|
||||
<template v-if="dir.can_delete && dir.types.includes('scanned')">
|
||||
<AButton type="link" @click.stop="onAliasExtraPathClick(dir.dir)">{{ $t('alias') }}
|
||||
</AButton>
|
||||
<AButton type="link" @click.stop="onRemoveExtraPathClick(dir.dir, 'scanned')">{{ $t('remove') }}
|
||||
</AButton>
|
||||
</template>
|
||||
</li>
|
||||
</actionContextMenu>
|
||||
<template
|
||||
v-for="dir in global.quickMovePaths.filter(({ types: ts }) => ts.includes('cli_access_only') || ts.includes('preset') || ts.includes('scanned') || ts.includes('scanned-fixed')) "
|
||||
:key="dir.key">
|
||||
|
||||
<actionContextMenu v-for="t in dir.types.filter(v => v !== 'walk')" :key="t" @open-in-new-tab="openInNewTab('local', dir.dir, t)"
|
||||
@open-on-the-right="openOnTheRight('local', dir.dir,t)">
|
||||
|
||||
<li class="item rem" @click.prevent="openInCurrentTab('local', dir.dir, t)">
|
||||
<span class="text line-clamp-2"><span v-if="t == 'scanned-fixed'" class="fixed">Fixed</span>{{ dir.zh }}</span>
|
||||
<template v-if="dir.can_delete && (t === 'scanned-fixed' || t === 'scanned')">
|
||||
<AButton type="link" @click.stop="onAliasExtraPathClick(dir.dir)">{{ $t('alias') }}
|
||||
</AButton>
|
||||
<AButton type="link" @click.stop="onRemoveExtraPathClick(dir.dir, t)">{{ $t('remove') }}
|
||||
</AButton>
|
||||
</template>
|
||||
</li>
|
||||
</actionContextMenu>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
|
|
@ -315,6 +333,7 @@ const restoreRecord = () => {
|
|||
padding: 4px 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
&.rem {
|
||||
display: flex;
|
||||
|
|
@ -328,6 +347,15 @@ const restoreRecord = () => {
|
|||
color: var(--primary-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.fixed {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
font-size: .8em;
|
||||
padding: 2px 4px;
|
||||
border-radius: 8px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
import { ExtraPathType, addExtraPath, aliasExtraPath, removeExtraPath } from '@/api/db'
|
||||
import { globalEvents } from '@/util'
|
||||
import { Input, Modal, message } from 'ant-design-vue'
|
||||
import { Input, Modal, RadioButton, RadioGroup, message } from 'ant-design-vue'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { checkPathExists } from '@/api'
|
||||
import { h, ref } from 'vue'
|
||||
|
|
@ -10,11 +10,13 @@ import { useGlobalStore } from '@/store/useGlobalStore'
|
|||
|
||||
|
||||
|
||||
export const addToExtraPath = async (type: ExtraPathType) => {
|
||||
export const addToExtraPath = async (initType: ExtraPathType, initPath?: string) => {
|
||||
const g = useGlobalStore()
|
||||
let path: string
|
||||
let path: string = initPath ?? ''
|
||||
|
||||
const type = ref(initType)
|
||||
if (import.meta.env.TAURI_ARCH) {
|
||||
const ret = await open({ directory: true })
|
||||
const ret = await open({ directory: true, defaultPath: initPath })
|
||||
if (typeof ret === 'string') {
|
||||
path = ret
|
||||
} else {
|
||||
|
|
@ -22,14 +24,15 @@ export const addToExtraPath = async (type: ExtraPathType) => {
|
|||
}
|
||||
} else {
|
||||
path = await new Promise<string>((resolve) => {
|
||||
const key = ref('')
|
||||
const key = ref(path)
|
||||
console.log('dfd', key.value)
|
||||
Modal.confirm({
|
||||
title: t('inputTargetFolderPath'),
|
||||
width: '800px',
|
||||
content: () => {
|
||||
return h('div', [
|
||||
g.conf?.enable_access_control ? h('a', {
|
||||
style: {
|
||||
g.conf?.enable_access_control ? h('a', {
|
||||
style: {
|
||||
'word-break': 'break-all',
|
||||
'margin-bottom': '4px',
|
||||
display: 'block'
|
||||
|
|
@ -40,7 +43,23 @@ export const addToExtraPath = async (type: ExtraPathType) => {
|
|||
h(Input, {
|
||||
value: key.value,
|
||||
'onUpdate:value': (v: string) => (key.value = v)
|
||||
})
|
||||
}),
|
||||
h('div', [
|
||||
h('span', t('type')+': '),
|
||||
h(RadioGroup, {
|
||||
value: type.value,
|
||||
'onUpdate:value': (v: ExtraPathType) => (type.value = v),
|
||||
buttonStyle: 'solid',
|
||||
style: { margin: '16px 0 32px' }
|
||||
}, [
|
||||
h(RadioButton, { value: 'walk' }, 'Walk'),
|
||||
h(RadioButton, { value: 'scanned' }, 'Normal'),
|
||||
h(RadioButton, { value: 'scanned-fixed' }, 'Fixed')
|
||||
])
|
||||
]),
|
||||
h('p', 'Walk: 无需翻页即可浏览指定文件夹下的所有文件,使用无限滚动的方式呈现。将会使用DFS的方式遍历所有文件. 注意:该模式下排序仅在同层之间进行'),
|
||||
h('p', 'Normal: 类似于windows的文件浏览器,拥有较高的灵活性. 但在访问云存储或者类似SMB这样的地方时可能会出现异常'),
|
||||
h('p', 'Fixed: 类似Normal模式,但页面初始速度更快,兼容性更好,灵活性稍差。在Normal模式下出现了异常的话都可以改用这个试试')
|
||||
])
|
||||
},
|
||||
async onOk () {
|
||||
|
|
@ -58,7 +77,7 @@ export const addToExtraPath = async (type: ExtraPathType) => {
|
|||
Modal.confirm({
|
||||
content: t('confirmToAddToExtraPath'),
|
||||
async onOk () {
|
||||
await addExtraPath({ types: [type], path })
|
||||
await addExtraPath({ types: [type.value], path })
|
||||
message.success(t('addCompleted'))
|
||||
globalEvents.emit('searchIndexExpired')
|
||||
globalEvents.emit('updateGlobalSetting')
|
||||
|
|
@ -85,11 +104,11 @@ export const onAliasExtraPathClick = (path: string) => {
|
|||
title: t('inputAlias'),
|
||||
content: () => {
|
||||
return h('div', [
|
||||
h('div', {
|
||||
style: {
|
||||
h('div', {
|
||||
style: {
|
||||
'word-break': 'break-all',
|
||||
'margin-bottom': '4px'
|
||||
}
|
||||
}
|
||||
}, 'Path: ' + path),
|
||||
h(Input, {
|
||||
value: alias.value,
|
||||
|
|
|
|||
|
|
@ -37,13 +37,14 @@ const classSort = [
|
|||
'custom',
|
||||
'Source Identifier',
|
||||
'Model',
|
||||
'Media Type',
|
||||
'lora',
|
||||
'lyco',
|
||||
'pos',
|
||||
'size',
|
||||
'Sampler',
|
||||
'Postprocess upscaler',
|
||||
'Postprocess upscale by',
|
||||
'Sampler'
|
||||
].reduce((p, c, i) => {
|
||||
p[c] = i
|
||||
return p
|
||||
|
|
@ -51,7 +52,11 @@ const classSort = [
|
|||
|
||||
const classifyTags = computed(() => {
|
||||
return Object.entries(groupBy(tags.value, (v) => v.type)).sort(
|
||||
(a, b) => classSort[a[0]] - classSort[b[0]]
|
||||
(a, b) => {
|
||||
const aClass = classSort[a[0]] !== undefined ? classSort[a[0]] : Number.MAX_SAFE_INTEGER;
|
||||
const bClass = classSort[b[0]] !== undefined ? classSort[b[0]] : Number.MAX_SAFE_INTEGER;
|
||||
return aClass - bClass;
|
||||
}
|
||||
)
|
||||
})
|
||||
const tagMaxNum = reactive(new Map<string, number>())
|
||||
|
|
@ -218,7 +223,8 @@ const tagListFilter = (list: Tag[], name: string) => {
|
|||
{{ $t('needGenerateIdx') }}
|
||||
</p>
|
||||
<div class="list-container">
|
||||
<ul class="tag-list" :key="name" v-for="[name, list] in classifyTags">
|
||||
<template :key="name" v-for="[name, list] in classifyTags">
|
||||
<ul class="tag-list" v-if="name !== 'Media Type' || list.length > 1">
|
||||
<h3 class="cat-name"
|
||||
@click="!openedKeys.includes(name) ? openedKeys.push(name) : openedKeys.splice(openedKeys.indexOf(name), 1)">
|
||||
<ArrowRightOutlined class="arrow" :class="{ down: openedKeys.includes(name) }" />
|
||||
|
|
@ -254,6 +260,7 @@ const tagListFilter = (list: Tag[], name: string) => {
|
|||
</a-collapse-panel>
|
||||
</a-collapse>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
|
||||
</div>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,190 @@
|
|||
import { useGlobalStore, type FileTransferTabPane } from '@/store/useGlobalStore'
|
||||
import { useImgSliStore } from '@/store/useImgSli'
|
||||
import { onLongPress } from '@vueuse/core'
|
||||
import { ref, computed, watch, reactive } from 'vue'
|
||||
import {
|
||||
createTypedShareStateHook,
|
||||
typedEventEmitter
|
||||
} from 'vue3-ts-util'
|
||||
import { type FileNodeInfo } from '@/api/files'
|
||||
import { sortFiles } from '../fileSort'
|
||||
import { last, range } from 'lodash-es'
|
||||
import * as Path from '@/util/path'
|
||||
import { isMediaFile } from '@/util/file'
|
||||
import { useTagStore } from '@/store/useTagStore'
|
||||
import { useBatchDownloadStore } from '@/store/useBatchDownloadStore'
|
||||
import { Walker } from '../walker'
|
||||
|
||||
export const stackCache = new Map<string, Page[]>()
|
||||
|
||||
export const global = useGlobalStore()
|
||||
export const batchDownload = useBatchDownloadStore()
|
||||
export const tagStore = useTagStore()
|
||||
export const sli = useImgSliStore()
|
||||
export const imgTransferBus = new BroadcastChannel('iib-image-transfer-bus')
|
||||
export const { eventEmitter: events, useEventListen } = typedEventEmitter<{
|
||||
removeFiles (_: { paths: string[]; loc: string }): void
|
||||
addFiles (_: { files: FileNodeInfo[]; loc: string }): void
|
||||
}>()
|
||||
|
||||
|
||||
|
||||
export * from './useLocation'
|
||||
export * from './usePreview'
|
||||
export * from './useFilesDisplay'
|
||||
export * from './useFileTransfer'
|
||||
export * from './useFileItemActions'
|
||||
export * from './useGenInfoDiff'
|
||||
|
||||
|
||||
|
||||
export interface Scroller {
|
||||
$_startIndex: number
|
||||
$_endIndex: number
|
||||
scrollToItem (idx: number): void
|
||||
}
|
||||
|
||||
export const { useHookShareState } = createTypedShareStateHook(
|
||||
(_inst, { images }) => {
|
||||
const props = ref<Props>({ tabIdx: -1, paneIdx: -1 })
|
||||
const currPage = computed(() => last(stack.value))
|
||||
const stack = ref<Page[]>([])
|
||||
const basePath = computed(() =>
|
||||
stack.value.map((v) => v.curr).slice(global.conf?.is_win && props.value.mode !== 'scanned-fixed' ? 1 : 0)
|
||||
)
|
||||
const currLocation = computed(() => Path.join(...basePath.value))
|
||||
const sortMethod = ref(global.defaultSortingMethod)
|
||||
const walker = ref(props.value.mode == 'walk' ? new Walker(props.value.path!, sortMethod.value) : undefined)
|
||||
watch([() => props.value.mode, sortMethod], () => {
|
||||
walker.value = props.value.mode === 'walk' ? new Walker(props.value.path!, sortMethod.value) : undefined
|
||||
})
|
||||
|
||||
const deletedFiles = reactive(new Set<string>())
|
||||
watch(currPage, () => deletedFiles.clear())
|
||||
const sortedFiles = computed(() => {
|
||||
if (images.value) {
|
||||
return images.value
|
||||
}
|
||||
|
||||
if (walker.value) {
|
||||
return walker.value.images.filter(v => !deletedFiles.has(v.fullpath))
|
||||
}
|
||||
if (!currPage.value) {
|
||||
return []
|
||||
}
|
||||
const files = currPage.value?.files ?? []
|
||||
const method = sortMethod.value
|
||||
const filter = (files: FileNodeInfo[]) =>
|
||||
global.onlyFoldersAndImages
|
||||
? files.filter((file) => file.type === 'dir' || isMediaFile(file.name))
|
||||
: files
|
||||
return sortFiles(filter(files), method).filter(v => !deletedFiles.has(v.fullpath))
|
||||
})
|
||||
const multiSelectedIdxs = ref([] as number[])
|
||||
const previewIdx = ref(-1)
|
||||
|
||||
const canLoadNext = computed(() => walker.value ? !walker.value.isCompleted : false)
|
||||
|
||||
const spinning = ref(false)
|
||||
|
||||
const previewing = ref(false)
|
||||
|
||||
const getPane = () => {
|
||||
return global.tabList?.[props.value.tabIdx]?.panes?.[props.value.paneIdx] as FileTransferTabPane
|
||||
}
|
||||
|
||||
const events = typedEventEmitter<{
|
||||
loadNextDir (isFullscreenPreview?: boolean): Promise<void>
|
||||
refresh (): Promise<void>
|
||||
selectAll (): void
|
||||
|
||||
viewableAreaFilesChange(_: { files: FileNodeInfo[], startIdx: number }): void
|
||||
}>()
|
||||
events.useEventListen('selectAll', () => {
|
||||
console.log(`select all 0 -> ${sortedFiles.value.length}`)
|
||||
multiSelectedIdxs.value = range(0, sortedFiles.value.length)
|
||||
})
|
||||
|
||||
return {
|
||||
previewing,
|
||||
spinning,
|
||||
canLoadNext,
|
||||
multiSelectedIdxs,
|
||||
previewIdx,
|
||||
basePath,
|
||||
currLocation,
|
||||
currPage,
|
||||
stack,
|
||||
sortMethod,
|
||||
sortedFiles,
|
||||
scroller: ref<Scroller>(),
|
||||
stackViewEl: ref<HTMLDivElement>(),
|
||||
props,
|
||||
getPane,
|
||||
walker,
|
||||
deletedFiles,
|
||||
...events
|
||||
}
|
||||
},
|
||||
() => ({ images: ref<FileNodeInfo[]>() })
|
||||
)
|
||||
|
||||
export interface Props {
|
||||
tabIdx: number
|
||||
paneIdx: number
|
||||
path?: string
|
||||
mode?: 'walk' | 'scanned' | 'scanned-fixed'
|
||||
fixed?: boolean
|
||||
}
|
||||
|
||||
export interface Page {
|
||||
files: FileNodeInfo[]
|
||||
curr: string
|
||||
}
|
||||
|
||||
|
||||
export function useKeepMultiSelect () {
|
||||
const { eventEmitter, multiSelectedIdxs, sortedFiles } = useHookShareState().toRefs()
|
||||
const onSelectAll = () => eventEmitter.value.emit('selectAll')
|
||||
const onReverseSelect = () => {
|
||||
multiSelectedIdxs.value = sortedFiles.value.map((_, idx) => idx)
|
||||
.filter(v => !multiSelectedIdxs.value.includes(v))
|
||||
}
|
||||
const onClearAllSelected = () => {
|
||||
multiSelectedIdxs.value = []
|
||||
}
|
||||
return {
|
||||
onSelectAll,
|
||||
onReverseSelect,
|
||||
onClearAllSelected
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 针对移动端端操作优化,使用长按提到右键
|
||||
*/
|
||||
export const useMobileOptimization = () => {
|
||||
const { stackViewEl } = useHookShareState().toRefs()
|
||||
const showMenuIdx = ref(-1)
|
||||
onLongPress(stackViewEl, (e) => {
|
||||
let fileEl = e.target as HTMLDivElement
|
||||
while (fileEl.parentElement) {
|
||||
fileEl = fileEl.parentElement as any
|
||||
if (fileEl.tagName.toLowerCase() === 'li' && fileEl.classList.contains('file-item-trigger')) {
|
||||
const idx = fileEl.dataset?.idx
|
||||
if (idx && Number.isSafeInteger(+idx)) {
|
||||
showMenuIdx.value = +idx
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
return {
|
||||
showMenuIdx
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,368 @@
|
|||
import { type FileTransferTabPane, type Shortcut } from '@/store/useGlobalStore'
|
||||
import { useMouseInElement } from '@vueuse/core'
|
||||
import { ref } from 'vue'
|
||||
import { genInfoCompleted, getImageGenerationInfo, openFolder, openWithDefaultApp, setImgPath } from '@/api'
|
||||
import {
|
||||
useWatchDocument} from 'vue3-ts-util'
|
||||
import {
|
||||
createReactiveQueue,
|
||||
isImageFile,
|
||||
copy2clipboardI18n} from '@/util'
|
||||
import { type FileNodeInfo, deleteFiles, moveFiles } from '@/api/files'
|
||||
import { last, range, uniqueId } from 'lodash-es'
|
||||
import * as Path from '@/util/path'
|
||||
import { Checkbox, Modal, message } from 'ant-design-vue'
|
||||
import type { MenuInfo } from 'ant-design-vue/lib/menu/src/interface'
|
||||
import { t } from '@/i18n'
|
||||
import { batchUpdateImageTag, toggleCustomTagToImg } from '@/api/db'
|
||||
import { downloadFileInfoJSON, downloadFiles, toRawFileUrl } from '@/util/file'
|
||||
import { getShortcutStrFromEvent } from '@/util/shortcut'
|
||||
import { MultiSelectTips, openRenameFileModal } from '@/components/functionalCallableComp'
|
||||
import { batchDownload, events, imgTransferBus, stackCache, tagStore, useEventListen, useHookShareState, global } from '.'
|
||||
|
||||
|
||||
export function useFileItemActions (
|
||||
{ openNext }: { openNext: (file: FileNodeInfo) => Promise<void> }
|
||||
) {
|
||||
const showGenInfo = ref(false)
|
||||
const imageGenInfo = ref('')
|
||||
const {
|
||||
sortedFiles,
|
||||
previewIdx,
|
||||
multiSelectedIdxs,
|
||||
stack,
|
||||
currLocation,
|
||||
spinning,
|
||||
previewing,
|
||||
stackViewEl,
|
||||
eventEmitter,
|
||||
props,
|
||||
deletedFiles
|
||||
} = useHookShareState().toRefs()
|
||||
const nor = Path.normalize
|
||||
useEventListen('removeFiles', ({ paths, loc }) => {
|
||||
if (nor(loc) !== nor(currLocation.value)) {
|
||||
return
|
||||
}
|
||||
const top = last(stack.value)
|
||||
if (!top) {
|
||||
return
|
||||
}
|
||||
paths.forEach(path => deletedFiles.value.add(path))
|
||||
paths.filter(isImageFile).forEach(path => deletedFiles.value.add(path.replace(/\.\w+$/, '.txt')))
|
||||
})
|
||||
|
||||
useEventListen('addFiles', ({ files, loc }) => {
|
||||
if (nor(loc) !== nor(currLocation.value)) {
|
||||
return
|
||||
}
|
||||
const top = last(stack.value)
|
||||
if (!top) {
|
||||
return
|
||||
}
|
||||
|
||||
top.files.unshift(...files)
|
||||
})
|
||||
|
||||
const q = createReactiveQueue()
|
||||
const onFileItemClick = async (e: MouseEvent, file: FileNodeInfo, idx: number) => {
|
||||
previewIdx.value = idx
|
||||
global.fullscreenPreviewInitialUrl = toRawFileUrl(file)
|
||||
const idxInSelected = multiSelectedIdxs.value.indexOf(idx)
|
||||
if (e.shiftKey) {
|
||||
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) {
|
||||
if (idxInSelected !== -1) {
|
||||
multiSelectedIdxs.value.splice(idxInSelected, 1)
|
||||
} else {
|
||||
multiSelectedIdxs.value.push(idx)
|
||||
}
|
||||
e.stopPropagation()
|
||||
} else {
|
||||
await openNext(file)
|
||||
}
|
||||
}
|
||||
|
||||
const onContextMenuClick = async (e: MenuInfo, file: FileNodeInfo, idx: number) => {
|
||||
const url = toRawFileUrl(file)
|
||||
const path = currLocation.value
|
||||
const preset = { IIB_container_id: parent.IIB_container_id }
|
||||
|
||||
/**
|
||||
* 获取选中的图片信息
|
||||
* 选中的图片信息数组
|
||||
*/
|
||||
const getSelectedImg = () => {
|
||||
let selectedFiles: FileNodeInfo[] = []
|
||||
if (multiSelectedIdxs.value.includes(idx)) {
|
||||
// 如果索引已被选中,则获取所有已选中的图片信息
|
||||
selectedFiles = multiSelectedIdxs.value.map((idx) => sortedFiles.value[idx])
|
||||
} else {
|
||||
// 否则,只获取当前图片信息
|
||||
selectedFiles.push(file)
|
||||
}
|
||||
return selectedFiles
|
||||
}
|
||||
const copyImgTo = async (tab: ['txt2img', 'img2img', 'inpaint', 'extras'][number]) => {
|
||||
if (spinning.value) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
spinning.value = true
|
||||
await setImgPath(file.fullpath) // 设置图像路径
|
||||
imgTransferBus.postMessage({ ...preset, event: 'click_hidden_button', btnEleId: 'iib_hidden_img_update_trigger' }) // 触发图像组件更新
|
||||
// ok(await genInfoCompleted(), 'genInfoCompleted timeout') // 等待消息生成完成
|
||||
await genInfoCompleted() // 等待消息生成完成
|
||||
imgTransferBus.postMessage({ ...preset, event: 'click_hidden_button', btnEleId: `iib_hidden_tab_${tab}` }) // 触发粘贴
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
message.error('发送图像失败,请携带console的错误消息找开发者')
|
||||
} finally {
|
||||
spinning.value = false
|
||||
}
|
||||
}
|
||||
const key = `${e.key}`
|
||||
|
||||
if (key.startsWith('toggle-tag-')) {
|
||||
const tagId = +key.split('toggle-tag-')[1]
|
||||
const { is_remove } = await toggleCustomTagToImg({ tag_id: tagId, img_path: file.fullpath })
|
||||
const tag = global.conf?.all_custom_tags.find((v) => v.id === tagId)?.name!
|
||||
await tagStore.refreshTags([file.fullpath])
|
||||
message.success(t(is_remove ? 'removedTagFromImage' : 'addedTagToImage', { tag }))
|
||||
return
|
||||
} else if (key.startsWith('batch-add-tag-') || key.startsWith('batch-remove-tag-')) {
|
||||
const tagId = +key.split('-tag-')[1]
|
||||
const action = key.includes('add') ? 'add' : 'remove'
|
||||
const paths = getSelectedImg().map(v => v.fullpath)
|
||||
await batchUpdateImageTag({
|
||||
tag_id: tagId,
|
||||
img_paths: paths,
|
||||
action
|
||||
})
|
||||
await tagStore.refreshTags(paths)
|
||||
message.success(t(action === 'add' ? 'addCompleted' : 'removeCompleted'))
|
||||
return
|
||||
}
|
||||
switch (e.key) {
|
||||
case 'previewInNewWindow':
|
||||
return window.open(url)
|
||||
case 'saveSelectedAsJson':
|
||||
return downloadFileInfoJSON(getSelectedImg())
|
||||
case 'openWithDefaultApp':
|
||||
return openWithDefaultApp(file.fullpath)
|
||||
case 'download':{
|
||||
const selectedFiles = getSelectedImg()
|
||||
downloadFiles(selectedFiles.map(file => toRawFileUrl(file, true)))
|
||||
break
|
||||
}
|
||||
case 'copyPreviewUrl': {
|
||||
return copy2clipboardI18n(parent.document.location.origin + url)
|
||||
}
|
||||
case 'rename': {
|
||||
let newPath = await openRenameFileModal(file.fullpath)
|
||||
newPath = Path.normalize(newPath)
|
||||
const map = tagStore.tagMap
|
||||
map.set(newPath, map.get(file.fullpath) ?? [])
|
||||
map.delete(file.fullpath)
|
||||
file.fullpath = newPath
|
||||
file.name = newPath.split(/[\\/]/).pop() ?? ''
|
||||
return
|
||||
}
|
||||
case 'send2txt2img':
|
||||
return copyImgTo('txt2img')
|
||||
case 'send2img2img':
|
||||
return copyImgTo('img2img')
|
||||
case 'send2inpaint':
|
||||
return copyImgTo('inpaint')
|
||||
case 'send2extras':
|
||||
return copyImgTo('extras')
|
||||
case 'send2savedDir': {
|
||||
const dir = global.quickMovePaths.find((v) => v.key === 'outdir_save')
|
||||
if (!dir) {
|
||||
return message.error(t('unknownSavedDir'))
|
||||
}
|
||||
const absolutePath = Path.normalizeRelativePathToAbsolute(dir.dir, global.conf?.sd_cwd!)
|
||||
const selectedImg = getSelectedImg()
|
||||
await moveFiles(
|
||||
selectedImg.map((v) => v.fullpath),
|
||||
absolutePath,
|
||||
true
|
||||
)
|
||||
events.emit('removeFiles', {
|
||||
paths: selectedImg.map((v) => v.fullpath),
|
||||
loc: currLocation.value
|
||||
})
|
||||
events.emit('addFiles', { files: selectedImg, loc: absolutePath })
|
||||
break
|
||||
}
|
||||
case 'send2controlnet-img2img':
|
||||
case 'send2controlnet-txt2img': {
|
||||
const type = e.key.split('-')[1] as 'img2img' | 'txt2img'
|
||||
imgTransferBus.postMessage({ ...preset, event: 'send_to_control_net', type, url: toRawFileUrl(file) })
|
||||
break
|
||||
}
|
||||
case 'send2outpaint': {
|
||||
|
||||
imageGenInfo.value = await q.pushAction(() => getImageGenerationInfo(file.fullpath)).res
|
||||
const [prompt, negPrompt] = (imageGenInfo.value || '').split('\n')
|
||||
imgTransferBus.postMessage({
|
||||
...preset,
|
||||
event: 'send_to_outpaint',
|
||||
url: toRawFileUrl(file),
|
||||
prompt,
|
||||
negPrompt: negPrompt.slice('Negative prompt: '.length)
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
case 'openWithWalkMode': {
|
||||
stackCache.set(path, stack.value)
|
||||
const tab = global.tabList[props.value.tabIdx]
|
||||
const pane: FileTransferTabPane = {
|
||||
type: 'local',
|
||||
key: uniqueId(),
|
||||
path: file.fullpath,
|
||||
name: t('local'),
|
||||
stackKey: path,
|
||||
mode: 'walk'
|
||||
}
|
||||
tab.panes.push(pane)
|
||||
tab.key = pane.key
|
||||
break
|
||||
}
|
||||
case 'openInNewTab': {
|
||||
stackCache.set(path, stack.value)
|
||||
const tab = global.tabList[props.value.tabIdx]
|
||||
const pane: FileTransferTabPane = {
|
||||
type: 'local',
|
||||
key: uniqueId(),
|
||||
path: file.fullpath,
|
||||
name: t('local'),
|
||||
stackKey: path
|
||||
}
|
||||
tab.panes.push(pane)
|
||||
tab.key = pane.key
|
||||
break
|
||||
}
|
||||
case 'openOnTheRight': {
|
||||
stackCache.set(path, stack.value)
|
||||
let tab = global.tabList[props.value.tabIdx + 1]
|
||||
if (!tab) {
|
||||
tab = { panes: [], key: '', id: uniqueId() }
|
||||
global.tabList[props.value.tabIdx + 1] = tab
|
||||
}
|
||||
const pane: FileTransferTabPane = {
|
||||
type: 'local',
|
||||
key: uniqueId(),
|
||||
path: file.fullpath,
|
||||
name: t('local'),
|
||||
stackKey: path
|
||||
}
|
||||
tab.panes.push(pane)
|
||||
tab.key = pane.key
|
||||
break
|
||||
}
|
||||
case 'send2BatchDownload': {
|
||||
batchDownload.addFiles(getSelectedImg())
|
||||
break
|
||||
}
|
||||
case 'viewGenInfo': {
|
||||
showGenInfo.value = true
|
||||
imageGenInfo.value = await q.pushAction(() => getImageGenerationInfo(file.fullpath)).res
|
||||
break
|
||||
}
|
||||
case 'openWithLocalFileBrowser': {
|
||||
await openFolder(file.fullpath)
|
||||
break
|
||||
}
|
||||
case 'deleteFiles': {
|
||||
const selectedFiles = getSelectedImg()
|
||||
const removeFile = async () => {
|
||||
const paths = selectedFiles.map((v) => v.fullpath)
|
||||
await deleteFiles(paths)
|
||||
message.success(t('deleteSuccess'))
|
||||
events.emit('removeFiles', { paths: paths, loc: currLocation.value })
|
||||
}
|
||||
if (selectedFiles.length === 1 && global.ignoredConfirmActions.deleteOneOnly) {
|
||||
return removeFile()
|
||||
}
|
||||
await new Promise<void>((resolve) => {
|
||||
Modal.confirm({
|
||||
title: t('confirmDelete'),
|
||||
maskClosable: true,
|
||||
width: '60vw',
|
||||
content:() =>
|
||||
<div>
|
||||
<ol style={{ maxHeight: '50vh', overflow: 'auto' }}>
|
||||
{selectedFiles.map((v) => <li>{v.fullpath.split(/[/\\]/).pop()}</li>)}
|
||||
</ol>
|
||||
<MultiSelectTips />
|
||||
<Checkbox v-model:checked={global.ignoredConfirmActions.deleteOneOnly}>{t('deleteOneOnlySkipConfirm')} ({t('resetOnGlobalSettingsPage')})</Checkbox>
|
||||
</div>,
|
||||
async onOk () {
|
||||
await removeFile()
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
break
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
const { isOutside } = useMouseInElement(stackViewEl)
|
||||
|
||||
useWatchDocument('keydown', (e) => {
|
||||
const keysStr = getShortcutStrFromEvent(e)
|
||||
if (previewing.value) {
|
||||
const action = Object.entries(global.shortcut).find(
|
||||
(v) => v[1] === keysStr && v[1]
|
||||
)?.[0] as keyof Shortcut
|
||||
if (action) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
const idx = previewIdx.value
|
||||
const file = sortedFiles.value[idx]
|
||||
switch (action) {
|
||||
case 'delete': {
|
||||
if (toRawFileUrl(file) === global.fullscreenPreviewInitialUrl) {
|
||||
return message.warn(t('fullscreenRestriction'))
|
||||
}
|
||||
return onContextMenuClick({ key: 'deleteFiles' } as MenuInfo, file, idx)
|
||||
}
|
||||
case 'download': {
|
||||
return onContextMenuClick({ key: 'download' } as MenuInfo, file, idx)
|
||||
}
|
||||
default: {
|
||||
const name = /^toggle_tag_(.*)$/.exec(action)?.[1]
|
||||
const tag = global.conf?.all_custom_tags.find((v) => v.name === name)
|
||||
if (!tag) return
|
||||
return onContextMenuClick({ key: `toggle-tag-${tag.id}` } as MenuInfo, file, idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!isOutside.value && ['Ctrl + KeyA', 'Cmd + KeyA'].includes(keysStr)) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
eventEmitter.value.emit('selectAll')
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
onFileItemClick,
|
||||
onContextMenuClick,
|
||||
showGenInfo,
|
||||
imageGenInfo,
|
||||
q
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
import { watch } from 'vue'
|
||||
import {
|
||||
useWatchDocument
|
||||
} from 'vue3-ts-util'
|
||||
import { FileTransferData, getFileTransferDataFromDragEvent } from '@/util/file'
|
||||
import { useHookShareState, global, events, sli } from '.'
|
||||
import { copyFiles, moveFiles } from '@/api/files'
|
||||
import { MultiSelectTips } from '@/components/functionalCallableComp'
|
||||
import { t } from '@/i18n'
|
||||
import { createReactiveQueue } from '@/util'
|
||||
import { Modal, Button } from 'ant-design-vue'
|
||||
|
||||
import { cloneDeep, uniqBy } from 'lodash-es'
|
||||
|
||||
export function useFileTransfer () {
|
||||
const { currLocation, sortedFiles, currPage, multiSelectedIdxs, eventEmitter, walker } =
|
||||
useHookShareState().toRefs()
|
||||
const recover = () => {
|
||||
multiSelectedIdxs.value = []
|
||||
}
|
||||
useWatchDocument('click', () => {
|
||||
if (!global.keepMultiSelect) {
|
||||
recover()
|
||||
}
|
||||
})
|
||||
useWatchDocument('blur', () => {
|
||||
if (!global.keepMultiSelect) {
|
||||
recover()
|
||||
}
|
||||
})
|
||||
watch(currPage, recover)
|
||||
|
||||
const onFileDragStart = (e: DragEvent, idx: number) => {
|
||||
const file = cloneDeep(sortedFiles.value[idx])
|
||||
sli.fileDragging = true
|
||||
console.log('onFileDragStart set drag file ', e, idx, file)
|
||||
const files = [file]
|
||||
let includeDir = file.type === 'dir'
|
||||
if (multiSelectedIdxs.value.includes(idx)) {
|
||||
const selectedFiles = multiSelectedIdxs.value.map((idx) => sortedFiles.value[idx])
|
||||
files.push(...selectedFiles)
|
||||
includeDir = selectedFiles.some((v) => v.type === 'dir')
|
||||
}
|
||||
const data: FileTransferData = {
|
||||
includeDir,
|
||||
loc: currLocation.value || 'search-result',
|
||||
path: uniqBy(files, 'fullpath').map((f) => f.fullpath),
|
||||
nodes: uniqBy(files, 'fullpath'),
|
||||
__id: 'FileTransferData'
|
||||
}
|
||||
e.dataTransfer!.setData('text/plain', JSON.stringify(data))
|
||||
}
|
||||
|
||||
const onFileDragEnd = () => {
|
||||
sli.fileDragging = false
|
||||
}
|
||||
|
||||
const onDrop = async (e: DragEvent) => {
|
||||
if (walker.value) {
|
||||
return
|
||||
}
|
||||
const data = getFileTransferDataFromDragEvent(e)
|
||||
if (!data) {
|
||||
return
|
||||
}
|
||||
const toPath = currLocation.value
|
||||
if (data.loc === toPath) {
|
||||
return
|
||||
}
|
||||
const q = createReactiveQueue()
|
||||
const onCopyBtnClick = async () => q.pushAction(async () => {
|
||||
await copyFiles(data.path, toPath)
|
||||
eventEmitter.value.emit('refresh')
|
||||
Modal.destroyAll()
|
||||
})
|
||||
|
||||
const onMoveBtnClick = () => q.pushAction(async () => {
|
||||
await moveFiles(data.path, toPath)
|
||||
events.emit('removeFiles', { paths: data.path, loc: data.loc })
|
||||
eventEmitter.value.emit('refresh')
|
||||
Modal.destroyAll()
|
||||
})
|
||||
Modal.confirm({
|
||||
title: t('confirm') + '?',
|
||||
width: '60vw',
|
||||
content: () => <div>
|
||||
<div>
|
||||
{`${t('moveSelectedFilesTo')} ${toPath}`}
|
||||
<ol style={{ maxHeight: '50vh', overflow: 'auto' }}>
|
||||
{data.path.map((v) => <li>{v.split(/[/\\]/).pop()}</li>)}
|
||||
</ol>
|
||||
</div>
|
||||
<MultiSelectTips />
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end' }} class="actions">
|
||||
<Button onClick={Modal.destroyAll}>{t('cancel')}</Button>
|
||||
<Button type="primary" loading={!q.isIdle} onClick={onCopyBtnClick}>{t('copy')}</Button>
|
||||
<Button type="primary" loading={!q.isIdle} onClick={onMoveBtnClick}>{t('move')}</Button>
|
||||
</div>
|
||||
</div>,
|
||||
maskClosable: true,
|
||||
wrapClassName: 'hidden-antd-btns-modal'
|
||||
})
|
||||
}
|
||||
return {
|
||||
onFileDragStart,
|
||||
onDrop,
|
||||
multiSelectedIdxs,
|
||||
onFileDragEnd
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
import { useElementSize } from '@vueuse/core'
|
||||
import { ref, computed, watch, reactive } from 'vue'
|
||||
import { Top4MediaInfo, batchGetDirTop4MediaInfo } from '@/api'
|
||||
import {
|
||||
delay} from 'vue3-ts-util'
|
||||
import { sortMethodConv } from '../fileSort'
|
||||
import { debounce } from 'lodash-es'
|
||||
import { isMediaFile } from '@/util/file'
|
||||
import { useHookShareState, global, tagStore } from '.'
|
||||
|
||||
export function useFilesDisplay ({ fetchNext }: {fetchNext?: () => Promise<any>} = { }) {
|
||||
const {
|
||||
scroller,
|
||||
sortedFiles,
|
||||
sortMethod,
|
||||
currLocation,
|
||||
stackViewEl,
|
||||
canLoadNext,
|
||||
previewIdx,
|
||||
props,
|
||||
walker
|
||||
} = useHookShareState().toRefs()
|
||||
const { state } = useHookShareState()
|
||||
const moreActionsDropdownShow = ref(false)
|
||||
const cellWidth = ref(global.defaultGridCellWidth)
|
||||
const gridSize = computed(() => cellWidth.value + 16) // margin 8
|
||||
const profileHeight = 44
|
||||
const { width } = useElementSize(stackViewEl)
|
||||
const gridItems = computed(() => ~~(width.value / gridSize.value))
|
||||
const dirCoverCache = reactive(new Map<string, Top4MediaInfo[]>())
|
||||
|
||||
const itemSize = computed(() => {
|
||||
const second = gridSize.value
|
||||
const first = second + (cellWidth.value <= 160 ? 0 : profileHeight)
|
||||
|
||||
return {
|
||||
first,
|
||||
second
|
||||
}
|
||||
})
|
||||
|
||||
const loadNextDirLoading = ref(false)
|
||||
|
||||
const loadNextDir = async () => {
|
||||
if (loadNextDirLoading.value || props.value.mode !== 'walk' || !canLoadNext.value) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
loadNextDirLoading.value = true
|
||||
await walker.value?.next()
|
||||
} finally {
|
||||
loadNextDirLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 填充够一页,直到不行为止
|
||||
const fetchDataUntilViewFilled = async (isFullScreenPreview = false) => {
|
||||
const s = scroller.value
|
||||
const currIdx = () => (isFullScreenPreview ? previewIdx.value : s?.$_endIndex ?? 0)
|
||||
const needLoad = () => {
|
||||
const len = sortedFiles.value.length
|
||||
const preload = 50
|
||||
if (!len) {
|
||||
return true
|
||||
}
|
||||
if (fetchNext) {
|
||||
return currIdx() > len - preload
|
||||
}
|
||||
return currIdx() > len - preload && canLoadNext.value // canLoadNext 是walker的,表示加载完成
|
||||
}
|
||||
while (needLoad()) {
|
||||
await delay(30)
|
||||
const ret = await (fetchNext ?? loadNextDir)()
|
||||
if (typeof ret === 'boolean' && !ret) {
|
||||
return // 返回false同样表示加载完成
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.useEventListen('loadNextDir', fetchDataUntilViewFilled)
|
||||
|
||||
|
||||
const onViewableAreaChange = () => {
|
||||
const s = scroller.value
|
||||
if (s) {
|
||||
const startIdx = Math.max(s.$_startIndex - 10, 0)
|
||||
const viewableAreaFiles = sortedFiles.value.slice(startIdx, s.$_endIndex + 10)
|
||||
state.eventEmitter.emit('viewableAreaFilesChange', { files: viewableAreaFiles, startIdx })
|
||||
const fetchTagPaths = viewableAreaFiles
|
||||
.filter(v => v.is_under_scanned_path && isMediaFile(v.name))
|
||||
.map(v => v.fullpath)
|
||||
tagStore.fetchImageTags(fetchTagPaths)
|
||||
const fetchDirTop4MediaPaths = viewableAreaFiles
|
||||
.filter(v => v.is_under_scanned_path && v.type === 'dir' && !dirCoverCache.has(v.fullpath))
|
||||
.map(v => v.fullpath)
|
||||
if (fetchDirTop4MediaPaths.length) {
|
||||
batchGetDirTop4MediaInfo(fetchDirTop4MediaPaths).then(v => {
|
||||
for (const key in v) {
|
||||
if (Object.prototype.hasOwnProperty.call(v, key)) {
|
||||
const element = v[key];
|
||||
dirCoverCache.set(key, element)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
const onViewableAreaChangeDebounced = debounce(onViewableAreaChange, 300)
|
||||
watch(currLocation, onViewableAreaChangeDebounced)
|
||||
|
||||
const onScroll = debounce(async () => {
|
||||
await fetchDataUntilViewFilled()
|
||||
onViewableAreaChangeDebounced()
|
||||
}, 150)
|
||||
|
||||
return {
|
||||
gridItems,
|
||||
sortedFiles,
|
||||
sortMethodConv,
|
||||
moreActionsDropdownShow,
|
||||
gridSize,
|
||||
sortMethod,
|
||||
onScroll,
|
||||
loadNextDir,
|
||||
loadNextDirLoading,
|
||||
canLoadNext,
|
||||
itemSize,
|
||||
cellWidth,
|
||||
dirCoverCache
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
import { ref } from 'vue'
|
||||
import { global, useHookShareState } from '.'
|
||||
import { getImageGenerationInfoBatch } from '@/api'
|
||||
import { FileNodeInfo, GenDiffInfo } from '@/api/files'
|
||||
import { parse } from '@/util/stable-diffusion-image-metadata'
|
||||
import { delay } from 'vue3-ts-util'
|
||||
import { isMediaFile } from '@/util'
|
||||
const geninfocache = new Map<string, string>()
|
||||
|
||||
export const useGenInfoDiff = () => {
|
||||
const { useEventListen, sortedFiles } = useHookShareState().toRefs()
|
||||
|
||||
const changeIndchecked = ref<boolean>(global.defaultChangeIndchecked)
|
||||
const seedChangeChecked = ref<boolean>(global.defaultSeedChangeChecked)
|
||||
|
||||
const setGenInfo = async ({ files }: { files: FileNodeInfo[], startIdx: number }) => {
|
||||
await delay(100)
|
||||
if (!changeIndchecked.value) return
|
||||
files = files.filter(v => isMediaFile(v.fullpath) && !v.gen_info_obj)
|
||||
if (!files.length) return
|
||||
const geninfos = await getImageGenerationInfoBatch(files.map(v => v.fullpath).filter(v => !geninfocache.has(v)))
|
||||
|
||||
files.forEach(item => {
|
||||
const info = geninfos[item.fullpath] || geninfocache.get(item.fullpath) || ''
|
||||
geninfocache.set(item.fullpath, info)
|
||||
item.gen_info_obj = parse(info)
|
||||
item.gen_info_raw = info
|
||||
})
|
||||
}
|
||||
|
||||
useEventListen.value('viewableAreaFilesChange',setGenInfo)
|
||||
|
||||
|
||||
const getGenDiffWatchDep = (idx: number) => {
|
||||
const fs = sortedFiles.value
|
||||
return [idx, seedChangeChecked.value , fs[idx-1], fs[idx], fs[idx+1] ]
|
||||
}
|
||||
|
||||
function getGenDiff (ownGenInfo: any, idx: any, increment: any, ownFile: FileNodeInfo) {
|
||||
//init result obj
|
||||
const result: GenDiffInfo = {
|
||||
diff: {},
|
||||
empty: true,
|
||||
ownFile: '',
|
||||
otherFile: ''
|
||||
}
|
||||
|
||||
//check for out of bounds
|
||||
if (idx + increment < 0
|
||||
|| idx + increment >= sortedFiles.value.length
|
||||
|| sortedFiles.value[idx] == undefined) {
|
||||
return result
|
||||
}
|
||||
//check for gen_info_obj existence
|
||||
if (!('gen_info_obj' in sortedFiles.value[idx])
|
||||
|| !('gen_info_obj' in sortedFiles.value[idx + increment])) {
|
||||
return result
|
||||
}
|
||||
|
||||
//diff vars init
|
||||
const gen_a = ownGenInfo
|
||||
const gen_b: any = sortedFiles.value[idx + increment].gen_info_obj
|
||||
if (gen_b == undefined) {
|
||||
return result
|
||||
}
|
||||
|
||||
//further vars
|
||||
const skip = ['hashes', 'resources']
|
||||
result.diff = {}
|
||||
result.ownFile = ownFile.name,
|
||||
result.otherFile = sortedFiles.value[idx + increment].name,
|
||||
result.empty = false
|
||||
|
||||
if (!seedChangeChecked.value) {
|
||||
skip.push('seed')
|
||||
}
|
||||
|
||||
//actual per property diff
|
||||
for (const k in gen_a) {
|
||||
//skip unwanted values
|
||||
if (skip.includes(k)) {
|
||||
continue
|
||||
}
|
||||
//for all non-identical values, compare type based
|
||||
//existence test
|
||||
if (!(k in gen_b)) {
|
||||
result.diff[k] = '+'
|
||||
continue
|
||||
}
|
||||
//content test
|
||||
if (gen_a[k] != gen_b[k]) {
|
||||
if (k.includes('rompt') && gen_a[k] != '' && gen_b[k] != '') {
|
||||
//prompt values are comma separated, handle them differently
|
||||
const tokenize_a = gen_a[k].split(',')
|
||||
const tokenize_b = gen_b[k].split(',')
|
||||
//count how many tokens are different or at a different place
|
||||
let diff_count = 0
|
||||
for (const i in tokenize_a) {
|
||||
if (tokenize_a[i] != tokenize_b[i]) {
|
||||
diff_count++
|
||||
}
|
||||
}
|
||||
result.diff[k] = diff_count
|
||||
} else {
|
||||
//all others
|
||||
result.diff[k] = [gen_a[k], gen_b[k]]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//result
|
||||
return result
|
||||
}
|
||||
return {
|
||||
getGenDiff,
|
||||
changeIndchecked,
|
||||
seedChangeChecked,
|
||||
getRawGenParams: () => setGenInfo({ files: sortedFiles.value, startIdx: 0 }),
|
||||
getGenDiffWatchDep
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,370 @@
|
|||
import { getTargetFolderFiles, FileNodeInfo } from '@/api/files'
|
||||
import { openCreateFlodersModal } from '@/components/functionalCallableComp'
|
||||
import { t } from '@/i18n'
|
||||
import { DatabaseOutlined } from '@/icon'
|
||||
import { TagSearchTabPane, FuzzySearchTabPane, FileTransferTabPane, EmptyStartTabPane } from '@/store/useGlobalStore'
|
||||
import { copy2clipboardI18n, makeAsyncFunctionSingle, useGlobalEventListen } from '@/util'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { ref, watch, onMounted, h, computed } from 'vue'
|
||||
import { delay, ok, useWatchDocument } from 'vue3-ts-util'
|
||||
import { useHookShareState, stackCache, global } from '.'
|
||||
|
||||
import * as Path from '@/util/path'
|
||||
import type Progress from 'nprogress'
|
||||
// @ts-ignore
|
||||
import NProgress from 'multi-nprogress'
|
||||
|
||||
import { cloneDeep, debounce, last, uniqueId } from 'lodash-es'
|
||||
|
||||
|
||||
/**
|
||||
* 路径栏相关
|
||||
*/
|
||||
export function useLocation () {
|
||||
const np = ref<Progress.NProgress>()
|
||||
const {
|
||||
scroller,
|
||||
stackViewEl,
|
||||
stack,
|
||||
currPage,
|
||||
currLocation,
|
||||
useEventListen,
|
||||
eventEmitter,
|
||||
getPane,
|
||||
props,
|
||||
deletedFiles,
|
||||
walker,
|
||||
sortedFiles
|
||||
} = useHookShareState().toRefs()
|
||||
|
||||
watch(
|
||||
() => stack.value.length,
|
||||
debounce((v, lv) => {
|
||||
if (v !== lv) {
|
||||
scroller.value?.scrollToItem(0)
|
||||
}
|
||||
}, 300)
|
||||
)
|
||||
|
||||
const handleWalkModeTo = async (path: string) => {
|
||||
await to(path)
|
||||
if (props.value.mode === 'walk') {
|
||||
await delay()
|
||||
await walker.value?.reset()
|
||||
eventEmitter.value.emit('loadNextDir')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (!stack.value.length) {
|
||||
// 有传入stack时直接使用传入的
|
||||
const resp = await getTargetFolderFiles('/')
|
||||
stack.value.push({
|
||||
files: resp.files,
|
||||
curr: '/'
|
||||
})
|
||||
}
|
||||
np.value = new NProgress()
|
||||
np.value!.configure({ parent: stackViewEl.value as any })
|
||||
if (props.value.path && props.value.path !== '/') {
|
||||
await handleWalkModeTo(props.value.path)
|
||||
} else {
|
||||
global.conf?.home && to(global.conf.home)
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
currLocation,
|
||||
debounce((loc) => {
|
||||
const pane = getPane.value()
|
||||
if (!pane) {
|
||||
return
|
||||
}
|
||||
pane.path = loc
|
||||
const filename = pane.path!.split('/').pop() ?? ''
|
||||
const getTitle = () => {
|
||||
const prefix = {
|
||||
walk: 'Walk',
|
||||
'scanned-fixed': 'Fixed',
|
||||
scanned: null
|
||||
}[props.value.mode ?? 'scanned']
|
||||
const wrap = (v: string) => prefix ? `${prefix}: ${v}` : v
|
||||
const np = Path.normalize(loc)
|
||||
for (const [k, v] of Object.entries(global.pathAliasMap)) {
|
||||
if (np.startsWith(v)) {
|
||||
return wrap(np.replace(v, k))
|
||||
}
|
||||
}
|
||||
return wrap(filename)
|
||||
}
|
||||
const title = getTitle()
|
||||
pane.name = h('div', { style: 'display:flex;align-items:center' }, [
|
||||
h(DatabaseOutlined),
|
||||
h('span', { class: 'line-clamp-1', style: 'max-width: 256px' }, title)
|
||||
])
|
||||
pane.nameFallbackStr = title
|
||||
global.recent = global.recent.filter((v) => v.key !== pane.key)
|
||||
global.recent.unshift({ path: loc, key: pane.key })
|
||||
if (global.recent.length > 20) {
|
||||
global.recent = global.recent.slice(0, 20)
|
||||
}
|
||||
}, 300)
|
||||
)
|
||||
|
||||
const copyLocation = () => copy2clipboardI18n(currLocation.value)
|
||||
|
||||
const openNext = async (file: FileNodeInfo) => {
|
||||
if (file.type !== 'dir') {
|
||||
return
|
||||
}
|
||||
try {
|
||||
np.value?.start()
|
||||
const { files } = await getTargetFolderFiles(file.fullpath)
|
||||
if (props.value.mode == 'scanned-fixed') {
|
||||
stack.value = [{
|
||||
files,
|
||||
curr: file.fullpath
|
||||
}]
|
||||
} else {
|
||||
stack.value.push({
|
||||
files,
|
||||
curr: file.name
|
||||
})
|
||||
}
|
||||
} finally {
|
||||
np.value?.done()
|
||||
}
|
||||
}
|
||||
|
||||
const back = (idx: number) => {
|
||||
while (idx < stack.value.length - 1) {
|
||||
stack.value.pop()
|
||||
}
|
||||
}
|
||||
|
||||
const backToLastUseTo = () => {
|
||||
const lastLevelPath = Path.join(...Path.splitPath(currLocation.value).slice(0, -1))
|
||||
to(lastLevelPath)
|
||||
}
|
||||
|
||||
const isDirNameEqual = (a: string, b: string) => {
|
||||
ok(global.conf, 'global.conf load failed')
|
||||
if (global.conf.is_win) {
|
||||
// window下忽略
|
||||
return a.toLowerCase() == b.toLowerCase()
|
||||
}
|
||||
return a == b
|
||||
}
|
||||
|
||||
const to = async (dir: string) => {
|
||||
if (props.value.mode === 'scanned-fixed') {
|
||||
return openNext({ fullpath: dir, name: dir, type: 'dir' } as FileNodeInfo)
|
||||
}
|
||||
const backup = stack.value.slice()
|
||||
try {
|
||||
if (!Path.isAbsolute(dir)) {
|
||||
// 相对路径
|
||||
dir = Path.join(global.conf?.sd_cwd ?? '/', dir)
|
||||
}
|
||||
const frags = Path.splitPath(dir)
|
||||
const currPaths = stack.value.map((v) => v.curr)
|
||||
currPaths.shift() // 是 /
|
||||
while (currPaths[0] && frags[0]) {
|
||||
if (!isDirNameEqual(currPaths[0], frags[0])) {
|
||||
break
|
||||
} else {
|
||||
currPaths.shift()
|
||||
frags.shift()
|
||||
}
|
||||
}
|
||||
for (let index = 0; index < currPaths.length; index++) {
|
||||
stack.value.pop()
|
||||
}
|
||||
if (!frags.length) {
|
||||
return refresh()
|
||||
}
|
||||
for (const frag of frags) {
|
||||
const target = currPage.value?.files.find((v) => isDirNameEqual(v.name, frag))
|
||||
if (!target) {
|
||||
console.error({ frags, frag, stack: cloneDeep(stack.value) })
|
||||
throw new Error(`${frag} not found`)
|
||||
}
|
||||
await openNext(target)
|
||||
}
|
||||
} catch (error) {
|
||||
message.error(t('moveFailedCheckPath') + (error instanceof Error ? error.message : ''))
|
||||
console.error(dir, Path.splitPath(dir), currPage.value)
|
||||
stack.value = backup
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const refresh = makeAsyncFunctionSingle(async () => {
|
||||
try {
|
||||
np.value?.start()
|
||||
if (walker.value) {
|
||||
await walker.value.reset()
|
||||
eventEmitter.value.emit('loadNextDir')
|
||||
} else {
|
||||
|
||||
const { files } = await getTargetFolderFiles(
|
||||
stack.value.length === 1 && props.value.mode !== 'scanned-fixed' ? '/' : currLocation.value
|
||||
)
|
||||
last(stack.value)!.files = files
|
||||
}
|
||||
|
||||
deletedFiles.value.clear()
|
||||
scroller.value?.scrollToItem(0)
|
||||
message.success(t('refreshCompleted'))
|
||||
} finally {
|
||||
np.value?.done()
|
||||
}
|
||||
})
|
||||
|
||||
useGlobalEventListen(
|
||||
'returnToIIB',
|
||||
makeAsyncFunctionSingle(async () => {
|
||||
if (props.value.mode === 'walk') return
|
||||
try {
|
||||
np.value?.start()
|
||||
const { files } = await getTargetFolderFiles(
|
||||
stack.value.length === 1 && props.value.mode !== 'scanned-fixed' ? '/' : currLocation.value
|
||||
)
|
||||
const currFiles = last(stack.value)!.files
|
||||
if (currFiles.map((v) => v.date).join() !== files.map((v) => v.date).join()) {
|
||||
last(stack.value)!.files = files
|
||||
message.success(t('autoUpdate'))
|
||||
}
|
||||
} finally {
|
||||
np.value?.done()
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
useEventListen.value('refresh', refresh)
|
||||
|
||||
const quickMoveTo = (path: string) => {
|
||||
// todo
|
||||
handleWalkModeTo(path)
|
||||
}
|
||||
|
||||
const normalizedScandPath = computed(() => {
|
||||
return global.quickMovePaths.map((v) => ({ ...v, path: Path.normalize(v.dir) }))
|
||||
})
|
||||
|
||||
const searchPathInfo = computed(() => {
|
||||
const c = Path.normalize(currLocation.value)
|
||||
const path = normalizedScandPath.value.find((v) => v.path === c)
|
||||
return path
|
||||
})
|
||||
|
||||
const addToSearchScanPathAndQuickMove = async () => {
|
||||
const tab = global.tabList[props.value.tabIdx]
|
||||
const pane: EmptyStartTabPane = {
|
||||
type: 'empty',
|
||||
name: t('emptyStartPage'),
|
||||
key: Date.now() + uniqueId(),
|
||||
popAddPathModal: {
|
||||
path: currLocation.value,
|
||||
type: 'scanned'
|
||||
}
|
||||
}
|
||||
tab.panes.push(pane)
|
||||
tab.key = pane.key
|
||||
}
|
||||
|
||||
const isLocationEditing = ref(false)
|
||||
const locInputValue = ref(currLocation.value)
|
||||
const onEditBtnClick = () => {
|
||||
isLocationEditing.value = true
|
||||
locInputValue.value = currLocation.value
|
||||
}
|
||||
|
||||
const onLocEditEnter = async () => {
|
||||
await to(locInputValue.value)
|
||||
isLocationEditing.value = false
|
||||
}
|
||||
|
||||
useWatchDocument('click', (e) => {
|
||||
if (!(e.target as HTMLElement)?.className?.includes?.('ant-input')) {
|
||||
isLocationEditing.value = false
|
||||
}
|
||||
})
|
||||
|
||||
const share = () => {
|
||||
const loc = parent.location
|
||||
const baseUrl = loc.href.substring(0, loc.href.length - loc.search.length)
|
||||
const params = new URLSearchParams(loc.search)
|
||||
params.set('action', 'open')
|
||||
if (walker.value) {
|
||||
params.set('walk', '1')
|
||||
}
|
||||
params.set('path', currLocation.value)
|
||||
params.set('mode', props.value.mode ?? 'scanned')
|
||||
const url = `${baseUrl}?${params.toString()}`
|
||||
copy2clipboardI18n(url, t('copyLocationUrlSuccessMsg'))
|
||||
}
|
||||
|
||||
const searchInCurrentDir = (type: (TagSearchTabPane | FuzzySearchTabPane)['type'] = 'tag-search') => {
|
||||
const tab = global.tabList[props.value.tabIdx]
|
||||
const pane = {
|
||||
type,
|
||||
key: uniqueId(),
|
||||
searchScope: currLocation.value,
|
||||
name: t(type === 'tag-search' ? 'imgSearch' : 'fuzzy-search'),
|
||||
}
|
||||
tab.panes.push(pane)
|
||||
tab.key = pane.key
|
||||
}
|
||||
|
||||
const selectAll = () => eventEmitter.value.emit('selectAll')
|
||||
|
||||
const onCreateFloderBtnClick = async () => {
|
||||
await openCreateFlodersModal(currLocation.value)
|
||||
await refresh()
|
||||
}
|
||||
|
||||
const onWalkBtnClick = () => {
|
||||
const path = currLocation.value
|
||||
stackCache.set(path, stack.value)
|
||||
const tab = global.tabList[props.value.tabIdx]
|
||||
const pane: FileTransferTabPane = {
|
||||
type: 'local',
|
||||
key: uniqueId(),
|
||||
path: path,
|
||||
name: t('local'),
|
||||
stackKey: path,
|
||||
mode: 'walk'
|
||||
}
|
||||
tab.panes.push(pane)
|
||||
tab.key = pane.key
|
||||
}
|
||||
|
||||
const showWalkButton = computed(() => !walker.value && sortedFiles.value.some(v => v.type === 'dir'))
|
||||
|
||||
return {
|
||||
locInputValue,
|
||||
isLocationEditing,
|
||||
onLocEditEnter,
|
||||
onEditBtnClick,
|
||||
addToSearchScanPathAndQuickMove,
|
||||
searchPathInfo,
|
||||
refresh,
|
||||
copyLocation,
|
||||
back,
|
||||
openNext,
|
||||
currPage,
|
||||
currLocation,
|
||||
to,
|
||||
stack,
|
||||
scroller,
|
||||
share,
|
||||
selectAll,
|
||||
quickMoveTo,
|
||||
onCreateFloderBtnClick,
|
||||
onWalkBtnClick,
|
||||
showWalkButton,
|
||||
searchInCurrentDir,
|
||||
backToLastUseTo
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
import { t } from '@/i18n'
|
||||
import { isImageFile } from '@/util'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { useWatchDocument, delay } from 'vue3-ts-util'
|
||||
import { useHookShareState, useEventListen } from '.'
|
||||
|
||||
/**
|
||||
* 全屏查看
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
export function usePreview () {
|
||||
const {
|
||||
previewIdx,
|
||||
eventEmitter,
|
||||
canLoadNext,
|
||||
previewing,
|
||||
sortedFiles: files,
|
||||
scroller,
|
||||
props
|
||||
} = useHookShareState().toRefs()
|
||||
const { state } = useHookShareState()
|
||||
let waitScrollTo = null as number | null
|
||||
const onPreviewVisibleChange = (v: boolean, lv: boolean) => {
|
||||
previewing.value = v
|
||||
if (waitScrollTo != null && !v && lv) {
|
||||
// 关闭预览时滚动过去
|
||||
scroller.value?.scrollToItem(waitScrollTo)
|
||||
waitScrollTo = null
|
||||
}
|
||||
}
|
||||
|
||||
const loadNextIfNeeded = () => {
|
||||
if (props.value.mode === 'walk') {
|
||||
if (!canPreview('next') && canLoadNext) {
|
||||
message.info(t('loadingNextFolder'))
|
||||
eventEmitter.value.emit('loadNextDir', true) // 如果在全屏查看时外面scroller可能还停留在很久之前,使用全屏查看的索引
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useWatchDocument('keydown', (e) => {
|
||||
if (previewing.value) {
|
||||
let next = previewIdx.value
|
||||
if (['ArrowDown', 'ArrowRight'].includes(e.key)) {
|
||||
next++
|
||||
while (files.value[next] && !isImageFile(files.value[next].name)) {
|
||||
next++
|
||||
}
|
||||
} else if (['ArrowUp', 'ArrowLeft'].includes(e.key)) {
|
||||
next--
|
||||
while (files.value[next] && !isImageFile(files.value[next].name)) {
|
||||
next--
|
||||
}
|
||||
}
|
||||
if (isImageFile(files.value[next]?.name) ?? '') {
|
||||
previewIdx.value = next
|
||||
const s = scroller.value
|
||||
if (s && !(next >= s.$_startIndex && next <= s.$_endIndex)) {
|
||||
waitScrollTo = next // 关闭预览时滚动过去
|
||||
}
|
||||
}
|
||||
loadNextIfNeeded()
|
||||
}
|
||||
})
|
||||
const previewImgMove = (type: 'next' | 'prev') => {
|
||||
let next = previewIdx.value
|
||||
if (type === 'next') {
|
||||
next++
|
||||
while (files.value[next] && !isImageFile(files.value[next].name)) {
|
||||
next++
|
||||
}
|
||||
} else if (type === 'prev') {
|
||||
next--
|
||||
while (files.value[next] && !isImageFile(files.value[next].name)) {
|
||||
next--
|
||||
}
|
||||
}
|
||||
if (isImageFile(files.value[next]?.name) ?? '') {
|
||||
previewIdx.value = next
|
||||
const s = scroller.value
|
||||
if (s && !(next >= s.$_startIndex && next <= s.$_endIndex)) {
|
||||
waitScrollTo = next // 关闭预览时滚动过去
|
||||
}
|
||||
}
|
||||
loadNextIfNeeded()
|
||||
}
|
||||
const canPreview = (type: 'next' | 'prev') => {
|
||||
let next = previewIdx.value
|
||||
if (type === 'next') {
|
||||
next++
|
||||
while (files.value[next] && !isImageFile(files.value[next].name)) {
|
||||
next++
|
||||
}
|
||||
} else if (type === 'prev') {
|
||||
next--
|
||||
while (files.value[next] && !isImageFile(files.value[next].name)) {
|
||||
next--
|
||||
}
|
||||
}
|
||||
return isImageFile(files.value[next]?.name) ?? ''
|
||||
}
|
||||
|
||||
useEventListen('removeFiles', async () => {
|
||||
if (previewing.value && !state.sortedFiles[previewIdx.value]) {
|
||||
message.info(t('manualExitFullScreen'), 5)
|
||||
await delay(500)
|
||||
; (
|
||||
document.querySelector(
|
||||
'.ant-image-preview-operations-operation .anticon-close'
|
||||
) as HTMLDivElement
|
||||
)?.click()
|
||||
previewIdx.value = -1
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
previewIdx,
|
||||
onPreviewVisibleChange,
|
||||
previewing,
|
||||
previewImgMove,
|
||||
canPreview
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup lang="ts">
|
||||
import { DownOutlined, LeftCircleOutlined, RightCircleOutlined } from '@/icon'
|
||||
import { DownOutlined, LeftCircleOutlined, RightCircleOutlined, ArrowLeftOutlined } from '@/icon'
|
||||
import { useGlobalStore } from '@/store/useGlobalStore'
|
||||
import {
|
||||
useFileTransfer,
|
||||
|
|
@ -10,7 +10,9 @@ import {
|
|||
useFileItemActions,
|
||||
useMobileOptimization,
|
||||
stackCache,
|
||||
useKeepMultiSelect
|
||||
useKeepMultiSelect,
|
||||
Props,
|
||||
useGenInfoDiff
|
||||
} from './hook'
|
||||
import { SearchSelect } from 'vue3-ts-util'
|
||||
import { toRawFileUrl } from '@/util/file'
|
||||
|
|
@ -24,12 +26,9 @@ import FileItem from '@/components/FileItem.vue'
|
|||
import fullScreenContextMenu from './fullScreenContextMenu.vue'
|
||||
import BaseFileListInfo from '@/components/BaseFileListInfo.vue'
|
||||
import { copy2clipboardI18n } from '@/util'
|
||||
import { openFolder, getImageGenerationInfoBatch } from '@/api'
|
||||
import { openFolder } from '@/api'
|
||||
import { sortMethods } from './fileSort'
|
||||
import { isTauri } from '@/util/env'
|
||||
import { parse } from '@/util/stable-diffusion-image-metadata'
|
||||
import { ref } from 'vue'
|
||||
import type { FileNodeInfo, GenDiffInfo } from '@/api/files'
|
||||
import MultiSelectKeep from '@/components/MultiSelectKeep.vue'
|
||||
|
||||
const global = useGlobalStore()
|
||||
|
|
@ -40,7 +39,7 @@ const props = defineProps<{
|
|||
* 初始打开路径
|
||||
*/
|
||||
path?: string
|
||||
walkModePath?: string
|
||||
mode?: Props['mode']
|
||||
/**
|
||||
* 页面栈,跳过不必要的api请求
|
||||
*/
|
||||
|
|
@ -54,9 +53,9 @@ const {
|
|||
spinning
|
||||
} = useHookShareState().toRefs()
|
||||
const { currLocation, currPage, refresh, copyLocation, back, openNext, stack, quickMoveTo,
|
||||
addToSearchScanPathAndQuickMove, searchPathInfo, locInputValue, isLocationEditing,
|
||||
addToSearchScanPathAndQuickMove, locInputValue, isLocationEditing,
|
||||
onLocEditEnter, onEditBtnClick, share, selectAll, onCreateFloderBtnClick, onWalkBtnClick,
|
||||
showWalkButton, searchInCurrentDir
|
||||
showWalkButton, searchInCurrentDir, backToLastUseTo
|
||||
} = useLocation()
|
||||
const {
|
||||
gridItems,
|
||||
|
|
@ -77,6 +76,7 @@ const { onFileItemClick, onContextMenuClick, showGenInfo, imageGenInfo, q } = us
|
|||
const { previewIdx, onPreviewVisibleChange, previewing, previewImgMove, canPreview } = usePreview()
|
||||
const { showMenuIdx } = useMobileOptimization()
|
||||
const { onClearAllSelected, onReverseSelect, onSelectAll } = useKeepMultiSelect()
|
||||
const { getGenDiff, changeIndchecked, seedChangeChecked, getRawGenParams, getGenDiffWatchDep } = useGenInfoDiff()
|
||||
|
||||
watch(
|
||||
() => props,
|
||||
|
|
@ -90,109 +90,6 @@ watch(
|
|||
{ immediate: true }
|
||||
)
|
||||
|
||||
watch(sortedFiles, async (newList, oldList) => {
|
||||
//check files in newList if it is an image-only list
|
||||
if (newList.length > 0 && newList.length !== oldList.length) {
|
||||
getRawGenParams()
|
||||
}
|
||||
})
|
||||
|
||||
const changeIndchecked = ref<boolean>(global.defaultChangeIndchecked)
|
||||
const seedChangeChecked = ref<boolean>(global.defaultSeedChangeChecked)
|
||||
|
||||
function getRawGenParams () {
|
||||
//extract fullpaths of all files from sortedfiles to array, but only if it's an actual file (not a folder or something else)
|
||||
let paths: string[] = []
|
||||
const allowedExtensions = ['.png', '.jpg', '.jpeg']
|
||||
for (let f in sortedFiles.value) {
|
||||
if (sortedFiles.value[f].type == 'file' && allowedExtensions.includes(sortedFiles.value[f].fullpath.slice(-4).toLowerCase())) {
|
||||
paths.push(sortedFiles.value[f].fullpath)
|
||||
}
|
||||
}
|
||||
q.pushAction(() => getImageGenerationInfoBatch(paths)).res.then((v) => {
|
||||
//result is a json object with fullpath as key and gen_info_raw as value
|
||||
for (let f in sortedFiles.value) {
|
||||
sortedFiles.value[f].gen_info_raw = v[sortedFiles.value[f].fullpath]
|
||||
sortedFiles.value[f].gen_info_obj = parse(v[sortedFiles.value[f].fullpath])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getGenDiff (ownGenInfo: any, idx: any, increment: any, ownFile: FileNodeInfo) {
|
||||
//init result obj
|
||||
let result: GenDiffInfo = {
|
||||
diff: {},
|
||||
empty: true,
|
||||
ownFile: '',
|
||||
otherFile: ''
|
||||
}
|
||||
|
||||
//check for out of bounds
|
||||
if (idx + increment < 0
|
||||
|| idx + increment >= sortedFiles.value.length
|
||||
|| sortedFiles.value[idx] == undefined) {
|
||||
return result
|
||||
}
|
||||
//check for gen_info_obj existence
|
||||
if (!('gen_info_obj' in sortedFiles.value[idx])
|
||||
|| !('gen_info_obj' in sortedFiles.value[idx + increment])) {
|
||||
return result
|
||||
}
|
||||
|
||||
//diff vars init
|
||||
let gen_a = ownGenInfo
|
||||
let gen_b: any = sortedFiles.value[idx + increment].gen_info_obj
|
||||
if (gen_b == undefined) {
|
||||
return result
|
||||
}
|
||||
|
||||
//further vars
|
||||
let skip = ['hashes', 'resources']
|
||||
result.diff = {}
|
||||
result.ownFile = ownFile.name,
|
||||
result.otherFile = sortedFiles.value[idx + increment].name,
|
||||
result.empty = false
|
||||
|
||||
if (!seedChangeChecked.value) {
|
||||
skip.push('seed')
|
||||
}
|
||||
|
||||
//actual per property diff
|
||||
for (let k in gen_a) {
|
||||
//skip unwanted values
|
||||
if (skip.includes(k)) {
|
||||
continue
|
||||
}
|
||||
//for all non-identical values, compare type based
|
||||
//existence test
|
||||
if (!(k in gen_b)) {
|
||||
result.diff[k] = '+'
|
||||
continue
|
||||
}
|
||||
//content test
|
||||
if (gen_a[k] != gen_b[k]) {
|
||||
if (k.includes('rompt') && gen_a[k] != '' && gen_b[k] != '') {
|
||||
//prompt values are comma separated, handle them differently
|
||||
let tokenize_a = gen_a[k].split(',')
|
||||
let tokenize_b = gen_b[k].split(',')
|
||||
//count how many tokens are different or at a different place
|
||||
let diff_count = 0
|
||||
for (let i in tokenize_a) {
|
||||
if (tokenize_a[i] != tokenize_b[i]) {
|
||||
diff_count++
|
||||
}
|
||||
}
|
||||
result.diff[k] = diff_count
|
||||
} else {
|
||||
//all others
|
||||
result.diff[k] = [gen_a[k], gen_b[k]]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//result
|
||||
return result
|
||||
}
|
||||
|
||||
</script>
|
||||
<template>
|
||||
|
|
@ -232,6 +129,7 @@ function getGenDiff (ownGenInfo: any, idx: any, increment: any, ownFile: FileNod
|
|||
|
||||
<AButton size="small" v-if="isLocationEditing" @click="onLocEditEnter" type="primary">{{ $t('go') }}</AButton>
|
||||
<div v-else class="location-act">
|
||||
<a @click.prevent="backToLastUseTo" style="margin: 0 8px 16px 0;" v-if="mode === 'scanned-fixed'"><ArrowLeftOutlined /></a>
|
||||
<a @click.prevent="copyLocation" class="copy">{{ $t('copy') }}</a>
|
||||
<a @click.prevent.stop="onEditBtnClick">{{ $t('edit') }}</a>
|
||||
</div>
|
||||
|
|
@ -302,10 +200,8 @@ function getGenDiff (ownGenInfo: any, idx: any, increment: any, ownFile: FileNod
|
|||
<a-switch v-model:checked="seedChangeChecked" :disabled="!changeIndchecked" />
|
||||
</a-form-item>
|
||||
<div style="padding: 4px;">
|
||||
<a @click.prevent="addToSearchScanPathAndQuickMove" v-if="!searchPathInfo">{{
|
||||
<a @click.prevent="addToSearchScanPathAndQuickMove" >{{
|
||||
$t('addToSearchScanPathAndQuickMove') }}</a>
|
||||
<a @click.prevent="addToSearchScanPathAndQuickMove" v-else-if="searchPathInfo.can_delete">{{
|
||||
$t('removeFromSearchScanPathAndQuickMove') }}</a>
|
||||
</div>
|
||||
<div style="padding: 4px;">
|
||||
<a @click.prevent="openFolder(currLocation + '/')">{{ $t('openWithLocalFileBrowser') }}</a>
|
||||
|
|
@ -331,14 +227,15 @@ function getGenDiff (ownGenInfo: any, idx: any, increment: any, ownFile: FileNod
|
|||
@file-item-click="onFileItemClick" @dragstart="onFileDragStart" @dragend="onFileDragEnd"
|
||||
@preview-visible-change="onPreviewVisibleChange" @context-menu-click="onContextMenuClick"
|
||||
:is-selected-mutil-files="multiSelectedIdxs.length > 1"
|
||||
:gen-diff-to-next="getGenDiff(file.gen_info_obj, idx, 1, file)"
|
||||
:gen-diff-to-previous="getGenDiff(file.gen_info_obj, idx, -1, file)"
|
||||
:enable-change-indicator="changeIndchecked"
|
||||
:enable-change-indicator="changeIndchecked"
|
||||
:seed-change-checked="seedChangeChecked"
|
||||
:get-gen-diff="getGenDiff"
|
||||
:get-gen-diff-watch-dep="getGenDiffWatchDep"
|
||||
:cover-files="dirCoverCache.get(file.fullpath)"/>
|
||||
</template>
|
||||
<template #after>
|
||||
<div style="padding: 16px 0 512px;">
|
||||
<AButton v-if="props.walkModePath" @click="loadNextDir" :loading="loadNextDirLoading" block type="primary"
|
||||
<AButton v-if="props.mode === 'walk'" @click="loadNextDir" :loading="loadNextDirLoading" block type="primary"
|
||||
:disabled="!canLoadNext" ghost>
|
||||
{{ $t('loadNextPage') }}</AButton>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export const resolveQueryActions = async (g: ReturnType<typeof useGlobalStore>)
|
|||
path,
|
||||
key: uniqueId(),
|
||||
name: '',
|
||||
walkModePath: params.get('walk') ? path : undefined
|
||||
mode: params.get('walk') ? 'walk' : 'scanned'
|
||||
}
|
||||
tab.panes.unshift(pane)
|
||||
tab.key = pane.key
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import type { GlobalConf } from '@/api'
|
||||
import type { MatchImageByTagsReq, Tag } from '@/api/db'
|
||||
import type { ExtraPathType, MatchImageByTagsReq, Tag } from '@/api/db'
|
||||
import { FileNodeInfo } from '@/api/files'
|
||||
import { i18n, t } from '@/i18n'
|
||||
import { getPreferredLang } from '@/i18n'
|
||||
import { SortMethod } from '@/page/fileTransfer/fileSort'
|
||||
import { Props as FileTransferProps } from '@/page/fileTransfer/hooks'
|
||||
import type { getQuickMovePaths } from '@/page/taskRecord/autoComplete'
|
||||
import { type Dict, type ReturnTypeAsync } from '@/util'
|
||||
import { AnyFn, usePreferredDark } from '@vueuse/core'
|
||||
|
|
@ -20,7 +21,15 @@ interface TabPaneBase {
|
|||
}
|
||||
|
||||
interface OtherTabPane extends TabPaneBase {
|
||||
type: 'empty' | 'global-setting' | 'tag-search' | 'batch-download'
|
||||
type: 'global-setting' | 'tag-search' | 'batch-download'
|
||||
}
|
||||
|
||||
export interface EmptyStartTabPane extends TabPaneBase {
|
||||
type: 'empty'
|
||||
popAddPathModal?: {
|
||||
path: string
|
||||
type: ExtraPathType
|
||||
}
|
||||
}
|
||||
|
||||
export type GridViewFileTag = WithRequired<Partial<Tag>, 'name'>;
|
||||
|
|
@ -88,7 +97,7 @@ export interface ImgSliTabPane extends TabPaneBase {
|
|||
export interface FileTransferTabPane extends TabPaneBase {
|
||||
type: 'local'
|
||||
path?: string
|
||||
walkModePath?: string
|
||||
mode?: FileTransferProps['mode']
|
||||
stackKey?: string
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +111,7 @@ export interface FuzzySearchTabPane extends TabPaneBase {
|
|||
searchScope?: string
|
||||
}
|
||||
|
||||
export type TabPane = FileTransferTabPane | OtherTabPane | TagSearchMatchedImageGridTabPane | ImgSliTabPane | TagSearchTabPane | FuzzySearchTabPane| GridViewTabPane
|
||||
export type TabPane = EmptyStartTabPane | FileTransferTabPane | OtherTabPane | TagSearchMatchedImageGridTabPane | ImgSliTabPane | TagSearchTabPane | FuzzySearchTabPane| GridViewTabPane
|
||||
|
||||
/**
|
||||
* This interface represents a tab, which contains an array of panes, an ID, and a key
|
||||
|
|
|
|||
Loading…
Reference in New Issue