Add more isolation mechanisms and export functions for easier use as a library
parent
6efef97594
commit
6d181ef8ba
39
app.py
39
app.py
|
|
@ -1,11 +1,16 @@
|
|||
import codecs
|
||||
from typing import List
|
||||
from fastapi import FastAPI
|
||||
from fastapi import FastAPI, Response
|
||||
from fastapi.responses import FileResponse
|
||||
import uvicorn
|
||||
import os
|
||||
from scripts.iib.api import infinite_image_browsing_api, index_html_path
|
||||
from scripts.iib.tool import get_sd_webui_conf, get_valid_img_dirs, sd_img_dirs, normalize_paths
|
||||
from scripts.iib.api import infinite_image_browsing_api, index_html_path, DEFAULT_BASE
|
||||
from scripts.iib.tool import (
|
||||
get_sd_webui_conf,
|
||||
get_valid_img_dirs,
|
||||
sd_img_dirs,
|
||||
normalize_paths,
|
||||
)
|
||||
from scripts.iib.db.datamodel import DataBase, Image
|
||||
from scripts.iib.db.update_image_data import update_image_data
|
||||
import argparse
|
||||
|
|
@ -70,6 +75,8 @@ class AppUtils:
|
|||
allow_cors=False,
|
||||
enable_shutdown=False,
|
||||
sd_webui_dir: Optional[str] = None,
|
||||
base="",
|
||||
export_fe_fn=False,
|
||||
):
|
||||
"""
|
||||
Parameter definitions can be found by running the `python app.py -h `command or by examining the setup_parser() function.
|
||||
|
|
@ -81,6 +88,8 @@ class AppUtils:
|
|||
self.allow_cors = allow_cors
|
||||
self.enable_shutdown = enable_shutdown
|
||||
self.sd_webui_dir = sd_webui_dir
|
||||
self.base = base
|
||||
self.export_fe_fn = export_fe_fn
|
||||
if sd_webui_dir:
|
||||
DataBase.path = os.path.join(
|
||||
sd_webui_dir, "extensions/sd-webui-infinite-image-browsing/iib.db"
|
||||
|
|
@ -93,7 +102,9 @@ class AppUtils:
|
|||
self.__init__(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def async_run(app: FastAPI, port: int = default_port, host = default_host) -> Coroutine:
|
||||
def async_run(
|
||||
app: FastAPI, port: int = default_port, host=default_host
|
||||
) -> Coroutine:
|
||||
"""
|
||||
用于从异步运行的 FastAPI,在 Jupyter Notebook 环境中非常有用
|
||||
"""
|
||||
|
|
@ -127,6 +138,8 @@ class AppUtils:
|
|||
allow_cors=self.allow_cors,
|
||||
enable_shutdown=self.enable_shutdown,
|
||||
launch_mode="server",
|
||||
base=self.base,
|
||||
export_fe_fn=self.export_fe_fn,
|
||||
)
|
||||
|
||||
def get_root_browser_app(self) -> FastAPI:
|
||||
|
|
@ -138,6 +151,10 @@ class AppUtils:
|
|||
# 用于在首页显示
|
||||
@app.get("/")
|
||||
def index():
|
||||
if self.base and self.base != DEFAULT_BASE:
|
||||
with open(index_html_path, "r", encoding="utf-8") as file:
|
||||
content = file.read().replace(DEFAULT_BASE, self.base)
|
||||
return Response(content=content, media_type="text/html")
|
||||
return FileResponse(index_html_path)
|
||||
|
||||
self.wrap_app(app)
|
||||
|
|
@ -187,10 +204,18 @@ def setup_parser() -> argparse.ArgumentParser:
|
|||
default=None,
|
||||
help="The path to the sd_webui folder. When specified, the sd_webui's configuration will be used and the extension must be installed within the sd_webui. Data will be shared between the two.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--export_fe_fn",
|
||||
action="store_true",
|
||||
help="Export front-end functions to enable external access through iframe.",
|
||||
)
|
||||
parser.add_argument("--base", type=str, help="The base URL for the IIB Api.")
|
||||
return parser
|
||||
|
||||
|
||||
def launch_app(port: int = default_port, host: str = default_host, *args, **kwargs: dict) -> None:
|
||||
def launch_app(
|
||||
port: int = default_port, host: str = default_host, *args, **kwargs: dict
|
||||
) -> None:
|
||||
"""
|
||||
Launches the application on the specified port.
|
||||
|
||||
|
|
@ -203,7 +228,9 @@ def launch_app(port: int = default_port, host: str = default_host, *args, **kwar
|
|||
uvicorn.run(app, host=host, port=port)
|
||||
|
||||
|
||||
async def async_launch_app(port: int = default_port, host: str = default_host, *args, **kwargs: dict) -> None:
|
||||
async def async_launch_app(
|
||||
port: int = default_port, host: str = default_host, *args, **kwargs: dict
|
||||
) -> None:
|
||||
"""
|
||||
Asynchronously launches the application on the specified port.
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ Promise.resolve().then(async () => {
|
|||
<title>Infinite Image Browsing</title>
|
||||
<script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-594b377e.js"></script>
|
||||
<link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-4cc43e92.css">
|
||||
<script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-8897613b.js"></script>
|
||||
<link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-6369b2e2.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ from scripts.iib.tool import (
|
|||
to_abs_path,
|
||||
is_secret_key_required
|
||||
)
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi import FastAPI, HTTPException, Response
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
import asyncio
|
||||
from typing import List, Optional
|
||||
|
|
@ -94,7 +94,7 @@ async def verify_secret(request: Request):
|
|||
if mem["secret_key_hash"] != token:
|
||||
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||
|
||||
|
||||
DEFAULT_BASE = "/infinite_image_browsing"
|
||||
def infinite_image_browsing_api(app: FastAPI, **kwargs):
|
||||
pre = "/infinite_image_browsing"
|
||||
|
||||
|
|
@ -133,6 +133,7 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
|
|||
f"An exception occurred while processing {request.method} {request.url}: {exc}"
|
||||
)
|
||||
|
||||
pre = kwargs.get("base") or DEFAULT_BASE
|
||||
if kwargs.get("allow_cors"):
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
|
|
@ -145,7 +146,7 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
|
|||
try:
|
||||
return get_valid_img_dirs(get_sd_webui_conf(**kwargs))
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print(e)
|
||||
return []
|
||||
|
||||
def update_all_scanned_paths():
|
||||
|
|
@ -174,7 +175,9 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
|
|||
)
|
||||
else:
|
||||
paths = (
|
||||
get_img_search_dirs() + mem["extra_paths"] + kwargs.get("extra_paths_cli", [])
|
||||
get_img_search_dirs()
|
||||
+ mem["extra_paths"]
|
||||
+ kwargs.get("extra_paths_cli", [])
|
||||
)
|
||||
mem["all_scanned_paths"] = unique_by(paths)
|
||||
|
||||
|
|
@ -235,12 +238,15 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
|
|||
return [x for x in files if is_path_trusted(x["fullpath"])]
|
||||
|
||||
static_dir = f"{cwd}/vue/dist"
|
||||
if os.path.exists(static_dir):
|
||||
app.mount(
|
||||
f"{pre}/fe-static",
|
||||
StaticFiles(directory=static_dir),
|
||||
name="infinite_image_browsing-fe-static",
|
||||
)
|
||||
@app.get(pre + "/fe-static/{file_path:path}")
|
||||
async def serve_static_file(file_path: str):
|
||||
file_full_path = f"{static_dir}/{file_path}"
|
||||
if file_path.endswith(".js"):
|
||||
with open(file_full_path, "r", encoding="utf-8") as file:
|
||||
content = file.read().replace(DEFAULT_BASE, pre)
|
||||
return Response(content=content, media_type="text/javascript")
|
||||
else:
|
||||
return FileResponse(file_full_path)
|
||||
|
||||
@app.get(f"{pre}/hello")
|
||||
async def greeting():
|
||||
|
|
@ -271,6 +277,7 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
|
|||
"extra_paths": extra_paths,
|
||||
"enable_access_control": enable_access_control,
|
||||
"launch_mode": kwargs.get("launch_mode", "sd"),
|
||||
"export_fe_fn": bool(kwargs.get("export_fe_fn")),
|
||||
}
|
||||
|
||||
class DeleteFilesReq(BaseModel):
|
||||
|
|
@ -565,6 +572,10 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
|
|||
|
||||
@app.get(pre)
|
||||
def index_bd():
|
||||
if pre != DEFAULT_BASE:
|
||||
with open(index_html_path, "r", encoding="utf-8") as file:
|
||||
content = file.read().replace(DEFAULT_BASE, pre)
|
||||
return Response(content=content, media_type="text/html")
|
||||
return FileResponse(index_html_path)
|
||||
|
||||
class PathsReq(BaseModel):
|
||||
|
|
@ -638,7 +649,9 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
|
|||
img_count = DbImg.count(conn)
|
||||
update_extra_paths(conn)
|
||||
dirs = (
|
||||
get_img_search_dirs() if img_count == 0 else Folder.get_expired_dirs(conn)
|
||||
get_img_search_dirs()
|
||||
if img_count == 0
|
||||
else Floder.get_expired_dirs(conn)
|
||||
) + mem["extra_paths"]
|
||||
|
||||
update_image_data(dirs)
|
||||
|
|
@ -746,10 +759,10 @@ def infinite_image_browsing_api(app: FastAPI, **kwargs):
|
|||
)
|
||||
img = DbImg.get(conn, path)
|
||||
if not img:
|
||||
if DbImg.count(conn):
|
||||
if DbImg.count(conn):
|
||||
update_image_data([os.path.dirname(path)])
|
||||
img = DbImg.get(conn, path)
|
||||
else:
|
||||
else:
|
||||
raise HTTPException(
|
||||
400,
|
||||
"你需要先通过图像搜索页生成索引"
|
||||
|
|
|
|||
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{d as t,o as a,m as r,b$ as n}from"./index-8897613b.js";const p=t({__name:"ImgSliPagePane",props:{paneIdx:{},tabIdx:{},left:{},right:{}},setup(o){return(e,s)=>(a(),r(n,{left:e.left,right:e.right},null,8,["left","right"]))}});export{p as default};
|
||||
|
|
@ -0,0 +1 @@
|
|||
import{d as q,l as Q,ax as W,o as r,y as _,c as s,n as a,r as e,s as h,p as y,t as X,v as b,x as j,m as M,L as H,C as m,N as S,Q as J,R as K,X as Y}from"./index-8897613b.js";import{L as Z,R as ee,f as te,S as ie}from"./fullScreenContextMenu-2b4c635b.js";import{g as le,F as se}from"./FileItem-3695dcaf.js";import{g as ne}from"./db-afd72581.js";import{u as ae}from"./hook-40f49198.js";import"./shortcut-902028a8.js";const oe={class:"hint"},re={key:1,class:"preview-switch"},de=q({__name:"MatchedImageGrid",props:{tabIdx:{},paneIdx:{},selectedTagIds:{},id:{}},setup(T){const u=T,{queue:p,images:i,onContextMenuClickU:g,stackViewEl:V,previewIdx:n,previewing:v,onPreviewVisibleChange:D,previewImgMove:f,canPreview:w,itemSize:I,gridItems:F,showGenInfo:o,imageGenInfo:k,q:z,multiSelectedIdxs:$,onFileItemClick:B,scroller:x,showMenuIdx:d,onFileDragStart:G,onFileDragEnd:N,cellWidth:R,onScroll:A,updateImageTag:E}=ae();return Q(()=>u.selectedTagIds,async()=>{const{res:c}=p.pushAction(()=>ne(u.selectedTagIds));i.value=await c,await W(),E(),x.value.scrollToItem(0)},{immediate:!0}),(c,t)=>{const P=J,U=K,L=ie;return r(),_("div",{class:"container",ref_key:"stackViewEl",ref:V},[s(L,{size:"large",spinning:!e(p).isIdle},{default:a(()=>[s(U,{visible:e(o),"onUpdate:visible":t[1]||(t[1]=l=>h(o)?o.value=l:null),width:"70vw","mask-closable":"",onOk:t[2]||(t[2]=l=>o.value=!1)},{cancelText:a(()=>[]),default:a(()=>[s(P,{active:"",loading:!e(z).isIdle},{default:a(()=>[y("div",{style:{width:"100%","word-break":"break-all","white-space":"pre-line","max-height":"70vh",overflow:"auto"},onDblclick:t[0]||(t[0]=l=>e(X)(e(k)))},[y("div",oe,b(c.$t("doubleClickToCopy")),1),j(" "+b(e(k)),1)],32)]),_:1},8,["loading"])]),_:1},8,["visible"]),e(i)?(r(),M(e(le),{key:0,ref_key:"scroller",ref:x,class:"file-list",items:e(i),"item-size":e(I).first,"key-field":"fullpath","item-secondary-size":e(I).second,gridItems:e(F),onScroll:e(A)},{default:a(({item:l,index:C})=>[s(se,{idx:C,file:l,"cell-width":e(R),"show-menu-idx":e(d),"onUpdate:showMenuIdx":t[3]||(t[3]=O=>h(d)?d.value=O:null),onDragstart:e(G),onDragend:e(N),onFileItemClick:e(B),"full-screen-preview-image-url":e(i)[e(n)]?e(H)(e(i)[e(n)]):"",selected:e($).includes(C),onContextMenuClick:e(g),onPreviewVisibleChange:e(D)},null,8,["idx","file","cell-width","show-menu-idx","onDragstart","onDragend","onFileItemClick","full-screen-preview-image-url","selected","onContextMenuClick","onPreviewVisibleChange"])]),_:1},8,["items","item-size","item-secondary-size","gridItems","onScroll"])):m("",!0),e(v)?(r(),_("div",re,[s(e(Z),{onClick:t[4]||(t[4]=l=>e(f)("prev")),class:S({disable:!e(w)("prev")})},null,8,["class"]),s(e(ee),{onClick:t[5]||(t[5]=l=>e(f)("next")),class:S({disable:!e(w)("next")})},null,8,["class"])])):m("",!0)]),_:1},8,["spinning"]),e(v)&&e(i)&&e(i)[e(n)]?(r(),M(te,{key:0,file:e(i)[e(n)],idx:e(n),onContextMenuClick:e(g)},null,8,["file","idx","onContextMenuClick"])):m("",!0)],512)}}});const fe=Y(de,[["__scopeId","data-v-d698e678"]]);export{fe as default};
|
||||
|
|
@ -0,0 +1 @@
|
|||
import{d as Y,$,aw as Z,bQ as ee,bP as B,o,y as k,c as r,r as e,bT as ne,m,n as u,x as w,v,C as g,s as V,p as F,t as te,L as ae,N as A,ax as se,ar as le,ai as ie,U as oe,V as re,Q as ue,R as de,X as ce}from"./index-8897613b.js";import{L as pe,R as me,f as ve,S as ge}from"./fullScreenContextMenu-2b4c635b.js";/* empty css */import{g as fe,F as ke}from"./FileItem-3695dcaf.js";import{b as T,c as we,f as be,u as ye}from"./db-afd72581.js";import{u as Ie}from"./hook-40f49198.js";import"./shortcut-902028a8.js";const xe={key:0,class:"search-bar"},Ce={class:"hint"},_e={key:1,class:"preview-switch"},he=Y({__name:"SubstrSearch",setup(Se){const{queue:l,images:a,onContextMenuClickU:b,stackViewEl:U,previewIdx:d,previewing:y,onPreviewVisibleChange:E,previewImgMove:I,canPreview:x,itemSize:C,gridItems:R,showGenInfo:c,imageGenInfo:_,q:N,multiSelectedIdxs:P,onFileItemClick:L,scroller:h,showMenuIdx:f,onFileDragStart:q,onFileDragEnd:G,cellWidth:K,onScroll:O,updateImageTag:Q}=Ie(),p=$(""),t=$();Z(async()=>{t.value=await T(),t.value.img_count&&t.value.expired&&S()});const S=ee(()=>l.pushAction(async()=>(await ye(),t.value=await T(),t.value)).res),M=async()=>{a.value=await l.pushAction(()=>be(p.value)).res,await se(),Q(),h.value.scrollToItem(0),a.value.length||le.info(ie("fuzzy-search-noResults"))};return B("returnToIIB",async()=>{const i=await l.pushAction(we).res;t.value.expired=i.expired}),B("searchIndexExpired",()=>t.value&&(t.value.expired=!0)),(i,n)=>{const H=oe,z=re,W=ue,X=de,j=ge;return o(),k("div",{class:"container",ref_key:"stackViewEl",ref:U},[t.value?(o(),k("div",xe,[r(H,{value:p.value,"onUpdate:value":n[0]||(n[0]=s=>p.value=s),placeholder:i.$t("fuzzy-search-placeholder"),disabled:!e(l).isIdle,onKeydown:ne(M,["enter"])},null,8,["value","placeholder","disabled","onKeydown"]),t.value.expired||!t.value.img_count?(o(),m(z,{key:0,onClick:e(S),loading:!e(l).isIdle,type:"primary"},{default:u(()=>[w(v(t.value.img_count===0?i.$t("generateIndexHint"):i.$t("UpdateIndex")),1)]),_:1},8,["onClick","loading"])):(o(),m(z,{key:1,type:"primary",onClick:M,loading:!e(l).isIdle,disabled:!p.value},{default:u(()=>[w(v(i.$t("search")),1)]),_:1},8,["loading","disabled"]))])):g("",!0),r(j,{size:"large",spinning:!e(l).isIdle},{default:u(()=>[r(X,{visible:e(c),"onUpdate:visible":n[2]||(n[2]=s=>V(c)?c.value=s:null),width:"70vw","mask-closable":"",onOk:n[3]||(n[3]=s=>c.value=!1)},{cancelText:u(()=>[]),default:u(()=>[r(W,{active:"",loading:!e(N).isIdle},{default:u(()=>[F("div",{style:{width:"100%","word-break":"break-all","white-space":"pre-line","max-height":"70vh",overflow:"auto"},onDblclick:n[1]||(n[1]=s=>e(te)(e(_)))},[F("div",Ce,v(i.$t("doubleClickToCopy")),1),w(" "+v(e(_)),1)],32)]),_:1},8,["loading"])]),_:1},8,["visible"]),e(a)?(o(),m(e(fe),{key:0,ref_key:"scroller",ref:h,class:"file-list",items:e(a),"item-size":e(C).first,"key-field":"fullpath","item-secondary-size":e(C).second,gridItems:e(R),onScroll:e(O)},{default:u(({item:s,index:D})=>[r(ke,{idx:D,file:s,"show-menu-idx":e(f),"onUpdate:showMenuIdx":n[4]||(n[4]=J=>V(f)?f.value=J:null),onFileItemClick:e(L),"full-screen-preview-image-url":e(a)[e(d)]?e(ae)(e(a)[e(d)]):"","cell-width":e(K),selected:e(P).includes(D),onContextMenuClick:e(b),onDragstart:e(q),onDragend:e(G),onPreviewVisibleChange:e(E)},null,8,["idx","file","show-menu-idx","onFileItemClick","full-screen-preview-image-url","cell-width","selected","onContextMenuClick","onDragstart","onDragend","onPreviewVisibleChange"])]),_:1},8,["items","item-size","item-secondary-size","gridItems","onScroll"])):g("",!0),e(y)?(o(),k("div",_e,[r(e(pe),{onClick:n[5]||(n[5]=s=>e(I)("prev")),class:A({disable:!e(x)("prev")})},null,8,["class"]),r(e(me),{onClick:n[6]||(n[6]=s=>e(I)("next")),class:A({disable:!e(x)("next")})},null,8,["class"])])):g("",!0)]),_:1},8,["spinning"]),e(y)&&e(a)&&e(a)[e(d)]?(o(),m(ve,{key:1,file:e(a)[e(d)],idx:e(d),onContextMenuClick:e(b)},null,8,["file","idx","onContextMenuClick"])):g("",!0)],512)}}});const Ae=ce(he,[["__scopeId","data-v-bb005cb9"]]);export{Ae as default};
|
||||
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{d as v,c0 as C,bO as I,o as i,y as _,p as f,c,n as r,x as h,v as d,r as e,m as F,L as x,c1 as z,c2 as B,V as $,X as R}from"./index-8897613b.js";import{u as S,b as V,k as E,F as A,g as L}from"./FileItem-3695dcaf.js";import"./db-afd72581.js";import"./shortcut-902028a8.js";const T={class:"actions-panel actions"},N={key:0,class:"file-list"},U={class:"hint"},H=v({__name:"batchDownload",props:{tabIdx:{},paneIdx:{},id:{}},setup(O){const{stackViewEl:w}=S().toRefs(),{itemSize:p,gridItems:k,cellWidth:b}=V(),l=E(),{selectdFiles:n}=C(l),u=I(),y=async t=>{const s=z(t);s&&l.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=$;return i(),_("div",{class:"container",ref_key:"stackViewEl",ref:w,onDrop:y},[f("div",T,[c(a,{onClick:s[0]||(s[0]=o=>e(l).selectdFiles=[])},{default:r(()=>[h(d(t.$t("clear")),1)]),_:1}),c(a,{onClick:D,type:"primary",loading:!e(u).isIdle},{default:r(()=>[h(d(t.$t("zipDownload")),1)]),_:1},8,["loading"])]),e(n).length?(i(),F(e(L),{key:1,ref:"scroller",class:"file-list",items:e(n).slice(),"item-size":e(p).first,"key-field":"fullpath","item-secondary-size":e(p).second,gridItems:e(k)},{default:r(({item:o,index:m})=>[c(A,{idx:m,file:o,"cell-width":e(b),"enable-close-icon":"",onCloseIconClick:j=>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"])):(i(),_("div",N,[f("p",U,d(t.$t("batchDownloaDDragAndDropHint")),1)]))],544)}}});const G=R(H,[["__scopeId","data-v-aab31da2"]]);export{G as default};
|
||||
|
|
@ -0,0 +1 @@
|
|||
import{c2 as t}from"./index-8897613b.js";const o=async()=>(await t.value.get("/db/basic_info")).data,c=async()=>(await t.value.get("/db/expired_dirs")).data,r=async()=>{await t.value.post("/db/update_image_data",{},{timeout:1/0})},d=async a=>(await t.value.post("/db/match_images_by_tags",a)).data,g=async a=>(await t.value.post("/db/add_custom_tag",a)).data,u=async a=>(await t.value.post("/db/toggle_custom_tag_to_img",a)).data,p=async a=>{await t.value.post("/db/remove_custom_tag",a)},i=async a=>(await t.value.get("/db/search_by_substr",{params:{substr:a}})).data,e="/db/scanned_paths",m=async a=>{await t.value.post(e,{path:a})},_=async a=>{await t.value.delete(e,{data:{path:a}})},b=async a=>(await t.value.post("/db/get_image_tags",{paths:a})).data;export{m as a,o as b,c,g as d,p as e,i as f,d as g,b as h,_ as r,u as t,r as u};
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
.ant-alert{box-sizing:border-box;margin:0;color:#000000d9;font-size:14px;font-variant:tabular-nums;line-height:1.5715;list-style:none;font-feature-settings:"tnum";position:relative;display:flex;align-items:center;padding:8px 15px;word-wrap:break-word;border-radius:2px}.ant-alert-content{flex:1;min-width:0}.ant-alert-icon{margin-right:8px}.ant-alert-description{display:none;font-size:14px;line-height:22px}.ant-alert-success{background-color:#f6ffed;border:1px solid #b7eb8f}.ant-alert-success .ant-alert-icon{color:#52c41a}.ant-alert-info{background-color:#fff1e6;border:1px solid #f7ae83}.ant-alert-info .ant-alert-icon{color:#d03f0a}.ant-alert-warning{background-color:#fffbe6;border:1px solid #ffe58f}.ant-alert-warning .ant-alert-icon{color:#faad14}.ant-alert-error{background-color:#fff2f0;border:1px solid #ffccc7}.ant-alert-error .ant-alert-icon{color:#ff4d4f}.ant-alert-error .ant-alert-description>pre{margin:0;padding:0}.ant-alert-action{margin-left:8px}.ant-alert-close-icon{margin-left:8px;padding:0;overflow:hidden;font-size:12px;line-height:12px;background-color:transparent;border:none;outline:none;cursor:pointer}.ant-alert-close-icon .anticon-close{color:#00000073;transition:color .3s}.ant-alert-close-icon .anticon-close:hover{color:#000000bf}.ant-alert-close-text{color:#00000073;transition:color .3s}.ant-alert-close-text:hover{color:#000000bf}.ant-alert-with-description{align-items:flex-start;padding:15px 15px 15px 24px}.ant-alert-with-description.ant-alert-no-icon{padding:15px}.ant-alert-with-description .ant-alert-icon{margin-right:15px;font-size:24px}.ant-alert-with-description .ant-alert-message{display:block;margin-bottom:4px;color:#000000d9;font-size:16px}.ant-alert-message{color:#000000d9}.ant-alert-with-description .ant-alert-description{display:block}.ant-alert.ant-alert-motion-leave{overflow:hidden;opacity:1;transition:max-height .3s cubic-bezier(.78,.14,.15,.86),opacity .3s cubic-bezier(.78,.14,.15,.86),padding-top .3s cubic-bezier(.78,.14,.15,.86),padding-bottom .3s cubic-bezier(.78,.14,.15,.86),margin-bottom .3s cubic-bezier(.78,.14,.15,.86)}.ant-alert.ant-alert-motion-leave-active{max-height:0;margin-bottom:0!important;padding-top:0;padding-bottom:0;opacity:0}.ant-alert-banner{margin-bottom:0;border:0;border-radius:0}.ant-alert.ant-alert-rtl{direction:rtl}.ant-alert-rtl .ant-alert-icon{margin-right:auto;margin-left:8px}.ant-alert-rtl .ant-alert-action,.ant-alert-rtl .ant-alert-close-icon{margin-right:8px;margin-left:auto}.ant-alert-rtl.ant-alert-with-description{padding-right:24px;padding-left:15px}.ant-alert-rtl.ant-alert-with-description .ant-alert-icon{margin-right:auto;margin-left:15px}.access-mode-message[data-v-f96eb6be]{display:flex;flex-direction:row;align-items:center}.access-mode-message a[data-v-f96eb6be]{margin-left:16px}.container[data-v-f96eb6be]{padding:20px;background-color:var(--zp-secondary-background);height:100%;overflow:auto}.header[data-v-f96eb6be]{display:flex;justify-content:space-between;align-items:center}.header h1[data-v-f96eb6be]{font-size:28px;font-weight:700;color:var(--zp-primary);margin:0}.last-record[data-v-f96eb6be]{margin-left:16px;font-size:14px;color:var(--zp-secondary)}.last-record a[data-v-f96eb6be]{text-decoration:none;color:var(--zp-secondary)}.last-record a[data-v-f96eb6be]:hover{color:var(--zp-primary)}.content[data-v-f96eb6be]{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));grid-gap:20px;margin-top:16px}.feature-item[data-v-f96eb6be]{background-color:var(--zp-primary-background);border-radius:8px;box-shadow:0 1px 2px #0000001a;padding:20px}.feature-item ul[data-v-f96eb6be]{list-style:none;padding:4px;max-height:70vh;overflow-y:auto}.feature-item.recent .title[data-v-f96eb6be]{display:flex;align-items:center;justify-content:space-between;margin-bottom:20px}.feature-item.recent .title h2[data-v-f96eb6be]{margin:0}.feature-item .item[data-v-f96eb6be]{margin-bottom:10px;padding:4px 8px;display:flex;align-items:center}.feature-item .item.rem[data-v-f96eb6be]{display:flex;align-items:center;justify-content:space-between}.feature-item .item[data-v-f96eb6be]:hover{background:var(--zp-secondary-background);border-radius:4px;color:var(--primary-color);cursor:pointer}.feature-item .icon[data-v-f96eb6be]{margin-right:8px}.feature-item h2[data-v-f96eb6be]{margin-top:0;margin-bottom:20px;font-size:20px;font-weight:700;color:var(--zp-primary)}.text[data-v-f96eb6be]{flex:1;font-size:16px}
|
||||
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 v,b as w,F as y,g as b}from"./FileItem-3695dcaf.js";import{d as k,k as h,$ as x,b0 as D,b4 as F,o as I,y as C,c,n as E,r as s,L as V,c1 as z,X as B}from"./index-8897613b.js";import"./db-afd72581.js";import"./shortcut-902028a8.js";const S=k({__name:"gridView",props:{tabIdx:{},paneIdx:{},id:{},removable:{type:Boolean},allowDragAndDrop:{type:Boolean},files:{},paneKey:{}},setup(r){const l=r,p=h(),{stackViewEl:d}=v().toRefs(),{itemSize:i,gridItems:m,cellWidth:u}=w(),t=x(l.files??[]),f=async e=>{const o=z(e);l.allowDragAndDrop&&o&&t.value.push(...o.nodes)},_=e=>{t.value.splice(e,1)},g=e=>({id:e.name,count:0,display_name:null,type:"temp",...e});return D(()=>{p.pageFuncExportMap.set(l.paneKey,{getFiles:()=>F(t.value),setFiles:e=>t.value=e})}),(e,o)=>(I(),C("div",{class:"container",ref_key:"stackViewEl",ref:d,onDrop:f},[c(s(b),{ref:"scroller",class:"file-list",items:t.value.slice(),"item-size":s(i).first,"key-field":"fullpath","item-secondary-size":s(i).second,gridItems:s(m)},{default:E(({item:a,index:n})=>[c(y,{idx:n,file:a,"cell-width":s(u),"enable-close-icon":l.removable,onCloseIconClick:R=>_(n),"full-screen-preview-image-url":s(V)(a),tags:a==null?void 0:a.tags.map(g),"enable-right-click-menu":!1},null,8,["idx","file","cell-width","enable-close-icon","onCloseIconClick","full-screen-preview-image-url","tags"])]),_:1},8,["items","item-size","item-secondary-size","gridItems"])],544))}});const H=B(S,[["__scopeId","data-v-f022c26b"]]);export{H as default};
|
||||
|
|
@ -0,0 +1 @@
|
|||
.container[data-v-f022c26b]{background:var(--zp-secondary-background);height:100%;overflow:auto;display:flex;flex-direction:column}.container .actions-panel[data-v-f022c26b]{padding:8px;background-color:var(--zp-primary-background)}.container .file-list[data-v-f022c26b]{flex:1;list-style:none;padding:8px;height:var(--pane-max-height);width:100%}.container .file-list .hint[data-v-f022c26b]{text-align:center;font-size:2em;padding:30vh 128px 0}
|
||||
|
|
@ -0,0 +1 @@
|
|||
import{$ as q,bO as D,bd as E,aC as P}from"./index-8897613b.js";import{h as $,u as z,b as G,f as O,c as Q,d as R,e as V,i as _}from"./FileItem-3695dcaf.js";const L=()=>{const e=q(),c=D(),l=$(),{stackViewEl:u,multiSelectedIdxs:r,stack:m,scroller:n}=z({images:e}).toRefs(),{itemSize:v,gridItems:d,cellWidth:g}=G(),{showMenuIdx:f}=O(),{onFileDragStart:I,onFileDragEnd:p}=Q(),{showGenInfo:h,imageGenInfo:w,q:x,onContextMenuClick:o,onFileItemClick:S}=R({openNext:E}),{previewIdx:C,previewing:F,onPreviewVisibleChange:b,previewImgMove:k,canPreview:M}=V(),y=async(s,t,a)=>{m.value=[{curr:"",files:e.value}],await o(s,t,a)};_("removeFiles",async({paths:s})=>{var t;e.value=(t=e.value)==null?void 0:t.filter(a=>!s.includes(a.fullpath))});const i=()=>{const s=n.value;if(s&&e.value){const t=e.value.slice(Math.max(s.$_startIndex-10,0),s.$_endIndex+10).map(a=>a.fullpath);l.fetchImageTags(t)}},T=P(i,300);return{scroller:n,queue:c,images:e,onContextMenuClickU:y,stackViewEl:u,previewIdx:C,previewing:F,onPreviewVisibleChange:b,previewImgMove:k,canPreview:M,itemSize:v,gridItems:d,showGenInfo:h,imageGenInfo:w,q:x,onContextMenuClick:o,onFileItemClick:S,showMenuIdx:f,multiSelectedIdxs:r,onFileDragStart:I,onFileDragEnd:p,cellWidth:g,onScroll:T,updateImageTag:i}};export{L as u};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
import{cL as s}from"./index-8897613b.js";var r=1/0,i=17976931348623157e292;function e(t){if(!t)return t===0?t:0;if(t=s(t),t===r||t===-r){var n=t<0?-1:1;return n*i}return t===t?t:0}function f(t){var n=t==null?0:t.length;return n?t[n-1]:void 0}const h=t=>{const n=[];return t.shiftKey&&n.push("Shift"),t.ctrlKey&&n.push("Ctrl"),t.metaKey&&n.push("Cmd"),(t.code.startsWith("Key")||t.code.startsWith("Digit"))&&n.push(t.code),n.join(" + ")};export{h as g,f as l,e as t};
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -9,6 +9,8 @@
|
|||
<title>Infinite Image Browsing</title>
|
||||
<script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-594b377e.js"></script>
|
||||
<link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-4cc43e92.css">
|
||||
<script type="module" crossorigin src="/infinite_image_browsing/fe-static/assets/index-8897613b.js"></script>
|
||||
<link rel="stylesheet" href="/infinite_image_browsing/fe-static/assets/index-6369b2e2.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { refreshTauriConf, tauriConf } from './util/tauriAppConf'
|
|||
import { openModal } from './taurilaunchModal'
|
||||
import { isTauri } from './util/env'
|
||||
import { delay } from 'vue3-ts-util'
|
||||
import { exportFn } from './defineExportFunc'
|
||||
|
||||
const globalStore = useGlobalStore()
|
||||
const queue = createReactiveQueue()
|
||||
|
|
@ -21,6 +22,7 @@ useGlobalEventListen('updateGlobalSetting', async () => {
|
|||
globalStore.conf = resp
|
||||
const r = await getQuickMovePaths(resp)
|
||||
globalStore.quickMovePaths = r.filter((v) => v?.dir?.trim?.())
|
||||
exportFn(globalStore)
|
||||
resolveQueryActions(globalStore)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { PageCursor } from 'vue3-ts-util'
|
|||
|
||||
export interface Tag {
|
||||
name: string
|
||||
id: number
|
||||
id: number | string
|
||||
display_name: string | null
|
||||
type: string
|
||||
count: number
|
||||
|
|
@ -31,12 +31,12 @@ export const getExpiredDirs = async () => {
|
|||
export const updateImageData = async () => {
|
||||
await axiosInst.value.post('/db/update_image_data', {}, { timeout: Infinity })
|
||||
}
|
||||
|
||||
type TagId = number | string
|
||||
export interface MatchImageByTagsReq {
|
||||
and_tags: number[]
|
||||
or_tags: number[]
|
||||
not_tags: number[]
|
||||
folder_paths_str?: string
|
||||
and_tags: TagId[]
|
||||
or_tags: TagId[]
|
||||
not_tags: TagId[]
|
||||
}
|
||||
|
||||
export const getImagesByTags = async (req: MatchImageByTagsReq, cursor: string) => {
|
||||
|
|
@ -56,16 +56,16 @@ export const addCustomTag = async (req: { tag_name: string }) => {
|
|||
return resp.data as Tag
|
||||
}
|
||||
|
||||
export const toggleCustomTagToImg = async (req: { tag_id: number; img_path: string }) => {
|
||||
export const toggleCustomTagToImg = async (req: { tag_id: TagId; img_path: string }) => {
|
||||
const resp = await axiosInst.value.post('/db/toggle_custom_tag_to_img', req)
|
||||
return resp.data as { is_remove: boolean }
|
||||
}
|
||||
|
||||
export const removeCustomTag = async (req: { tag_id: number }) => {
|
||||
export const removeCustomTag = async (req: { tag_id: TagId }) => {
|
||||
await axiosInst.value.post('/db/remove_custom_tag', req)
|
||||
}
|
||||
|
||||
export const removeCustomTagToImg = async (req: { tag_id: number; img_id: number }) => {
|
||||
export const removeCustomTagToImg = async (req: { tag_id: TagId; img_id: TagId }) => {
|
||||
await axiosInst.value.post('/db/add_custom_tag_from_img', req)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ export interface GlobalConf {
|
|||
extra_paths: ExtraPathModel[]
|
||||
enable_access_control: boolean
|
||||
launch_mode: 'server' | 'sd'
|
||||
export_fe_fn: boolean
|
||||
}
|
||||
|
||||
export const getGlobalSetting = async () => {
|
||||
|
|
@ -111,7 +112,8 @@ export const genInfoCompleted = async () => {
|
|||
}
|
||||
|
||||
export const getImageGenerationInfo = async (path: string) => {
|
||||
return (await axiosInst.value.get(`/image_geninfo?path=${encodeURIComponent(path)}`)).data as string
|
||||
return (await axiosInst.value.get(`/image_geninfo?path=${encodeURIComponent(path)}`))
|
||||
.data as string
|
||||
}
|
||||
|
||||
export const getImageGenerationInfoBatch = async (paths: string[]) => {
|
||||
|
|
|
|||
|
|
@ -26,13 +26,14 @@ const props = withDefaults(
|
|||
showMenuIdx?: number
|
||||
cellWidth: number
|
||||
fullScreenPreviewImageUrl?: string
|
||||
enableRightClickMenu?: boolean,
|
||||
enableCloseIcon?: boolean
|
||||
isSelectedMutilFiles?: boolean
|
||||
genDiffToPrevious?: GenDiffInfo
|
||||
genDiffToNext?: GenDiffInfo
|
||||
genInfo?: string
|
||||
enableChangeIndicator?: boolean
|
||||
enableRightClickMenu: boolean,
|
||||
enableCloseIcon: boolean,
|
||||
tags?: Tag[]
|
||||
}>(),
|
||||
{ selected: false, enableRightClickMenu: true, enableCloseIcon: false, genDiffToNext: () => ({
|
||||
empty: true,
|
||||
|
|
@ -131,7 +132,7 @@ const taggleLikeTag = () => {
|
|||
onVisibleChange: (v: boolean, lv: boolean) => emit('previewVisibleChange', v, lv)
|
||||
}" />
|
||||
<div class="tags-container" v-if="customTags && cellWidth > 128">
|
||||
<a-tag v-for="tag in customTags" :key="tag.id" :color="tagStore.getColor(tag.name)">
|
||||
<a-tag v-for="tag in tags ?? customTags" :key="tag.id" :color="tagStore.getColor(tag.name)">
|
||||
{{ tag.name }}
|
||||
</a-tag>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
import { GridViewFile, TabPane, useGlobalStore } from './store/useGlobalStore'
|
||||
import { Dict, globalEvents } from './util'
|
||||
import { uniqueId } from 'lodash-es'
|
||||
|
||||
|
||||
export const exportFn = async (g: ReturnType<typeof useGlobalStore>) => {
|
||||
if (!g.conf?.export_fe_fn) {
|
||||
return
|
||||
}
|
||||
const insertTabPane = ({
|
||||
tabIdx = 0,
|
||||
paneIdx = 0,
|
||||
pane
|
||||
}: {
|
||||
tabIdx?: number
|
||||
paneIdx?: number
|
||||
pane: TabPane
|
||||
}) => {
|
||||
const tab = g.tabList[tabIdx]
|
||||
if (!pane.key) {
|
||||
;(pane as any).key = uniqueId()
|
||||
}
|
||||
tab.panes.splice(paneIdx, 0, pane)
|
||||
tab.key = pane.key
|
||||
return {
|
||||
key: pane.key,
|
||||
ref: getPageRef(pane.key)
|
||||
}
|
||||
}
|
||||
|
||||
define({
|
||||
insertTabPane,
|
||||
getTabList: () => g.tabList,
|
||||
getPageRef,
|
||||
createGridViewFile(path: string, tags:string[] = []): GridViewFile {
|
||||
return {
|
||||
name: path.split(/[/\\]/).pop() ?? '',
|
||||
size: '-',
|
||||
bytes: 0,
|
||||
type: 'file',
|
||||
created_time: '',
|
||||
date: '',
|
||||
fullpath: path,
|
||||
tags: tags.map(v => ({ name: v })),
|
||||
is_under_scanned_path: true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
function getPageRef (key: string) {
|
||||
return new Proxy({}, {
|
||||
get(_target, p, _receiver) {
|
||||
if (p === 'close') {
|
||||
const tabIdx = g.tabList.findIndex(v => v.panes.some(v => v.key === key))
|
||||
return () => globalEvents.emit('closeTabPane', tabIdx , key)
|
||||
}
|
||||
return g.pageFuncExportMap.get(key)?.[p as string]
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function define(funcs: Dict<(...args: any[]) => any>) {
|
||||
const w = window as any
|
||||
for (const key in funcs) {
|
||||
w[key] = (...args: any) => {
|
||||
return funcs[key](...args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import { Splitpanes, Pane } from 'splitpanes'
|
|||
import 'splitpanes/dist/splitpanes.css'
|
||||
import { useGlobalStore, type TabPane } from '@/store/useGlobalStore'
|
||||
import { defineAsyncComponent, watch, ref, nextTick } from 'vue'
|
||||
import { globalEvents, asyncCheck } from '@/util'
|
||||
import { globalEvents, asyncCheck, useGlobalEventListen } from '@/util'
|
||||
import { debounce, uniqueId } from 'lodash-es'
|
||||
import edgeTrigger from './edgeTrigger.vue'
|
||||
import { t } from '@/i18n'
|
||||
|
|
@ -24,7 +24,8 @@ const compMap: Record<TabPane['type'], ReturnType<typeof defineAsyncComponent>>
|
|||
'tag-search': defineAsyncComponent(() => import('@/page/TagSearch/TagSearch.vue')),
|
||||
'fuzzy-search': defineAsyncComponent(() => import('@/page/TagSearch/SubstrSearch.vue')),
|
||||
'img-sli': defineAsyncComponent(() => import('@/page/ImgSli/ImgSliPagePane.vue')),
|
||||
'batch-download': defineAsyncComponent(() => import('@/page/batchDownload/batchDownload.vue'))
|
||||
'batch-download': defineAsyncComponent(() => import('@/page/batchDownload/batchDownload.vue')),
|
||||
'grid-view': defineAsyncComponent(() => import('@/page/gridView/gridView.vue'))
|
||||
}
|
||||
const onEdit = (idx: number, targetKey: any, action: string) => {
|
||||
const tab = global.tabList[idx]
|
||||
|
|
@ -48,6 +49,8 @@ const onEdit = (idx: number, targetKey: any, action: string) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
useGlobalEventListen('closeTabPane', (tabIdx, key) => onEdit(tabIdx, key, 'del'))
|
||||
const container = ref<HTMLDivElement>()
|
||||
watch(
|
||||
() => global.tabList,
|
||||
|
|
@ -102,7 +105,7 @@ watch(useDocumentVisibility(), v => v && emitReturnToIIB())
|
|||
<edge-trigger :tabIdx="tabIdx">
|
||||
<a-tabs type="editable-card" v-model:activeKey="tab.key" @edit="(key:any, act:any) => onEdit(tabIdx, key, act)">
|
||||
<a-tab-pane v-for="(pane, paneIdx) in tab.panes" :key="pane.key" :tab="pane.name" class="pane">
|
||||
<component :is="compMap[pane.type]" :tabIdx="tabIdx" :paneIdx="paneIdx" v-bind="pane" />
|
||||
<component :is="compMap[pane.type]" :tabIdx="tabIdx" :paneKey="pane.key" :paneIdx="paneIdx" v-bind="pane" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</edge-trigger>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ const compCnMap: Partial<Record<TabPane['type'], string>> = {
|
|||
const createPane = (type: TabPane['type'], path?: string, walkMode = false) => {
|
||||
let pane: TabPane
|
||||
switch (type) {
|
||||
case 'grid-view':
|
||||
case 'tag-search-matched-image-grid':
|
||||
case 'img-sli':
|
||||
return
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ const onAddTagBtnSubmit = async () => {
|
|||
addTagName.value = ''
|
||||
addInputing.value = false
|
||||
}
|
||||
const onTagRemoveClick = (tagId: number) => {
|
||||
const onTagRemoveClick = (tagId: number|string) => {
|
||||
Modal.confirm({
|
||||
title: t('confirmDelete'),
|
||||
async onOk () {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
<script lang="ts" setup>
|
||||
// @ts-ignore
|
||||
import { RecycleScroller } from '@zanllp/vue-virtual-scroller'
|
||||
import '@zanllp/vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
||||
import FileItem from '@/components/FileItem.vue'
|
||||
import { useFilesDisplay, useHookShareState } from '@/page/fileTransfer/hook'
|
||||
import { getFileTransferDataFromDragEvent, toRawFileUrl } from '@/util/file'
|
||||
import { ref, watchEffect, toRaw } from 'vue'
|
||||
import { GridViewFile, GridViewFileTag, useGlobalStore } from '@/store/useGlobalStore'
|
||||
import { Tag } from '@/api/db'
|
||||
|
||||
const g = useGlobalStore()
|
||||
const { stackViewEl } = useHookShareState().toRefs()
|
||||
const { itemSize, gridItems, cellWidth } = useFilesDisplay()
|
||||
|
||||
const props = defineProps<{
|
||||
tabIdx: number
|
||||
paneIdx: number
|
||||
id: string,
|
||||
removable?: boolean
|
||||
allowDragAndDrop?: boolean,
|
||||
files: GridViewFile[]
|
||||
paneKey: string
|
||||
}>()
|
||||
|
||||
|
||||
const files = ref(props.files ?? [])
|
||||
const onDrop = async (e: DragEvent) => {
|
||||
const data = getFileTransferDataFromDragEvent(e)
|
||||
if (props.allowDragAndDrop && data) {
|
||||
files.value.push(...data.nodes)
|
||||
}
|
||||
}
|
||||
|
||||
const onDeleteClick = (idx: number) => {
|
||||
files.value.splice(idx, 1)
|
||||
}
|
||||
|
||||
const tagConvert = (tag: GridViewFileTag): Tag => ({ id: tag.name, count: 0, display_name: null, type: 'temp', ...tag })
|
||||
|
||||
watchEffect(() => {
|
||||
g.pageFuncExportMap.set(props.paneKey, {
|
||||
getFiles: () => toRaw(files.value),
|
||||
setFiles: (_files: GridViewFile[]) => files.value = _files
|
||||
})
|
||||
})
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="container" ref="stackViewEl" @drop="onDrop">
|
||||
<RecycleScroller ref="scroller" class="file-list" :items="files.slice()" :item-size="itemSize.first"
|
||||
key-field="fullpath" :item-secondary-size="itemSize.second" :gridItems="gridItems">
|
||||
<template v-slot="{ item: file, index: idx }">
|
||||
<file-item :idx="idx" :file="file" :cell-width="cellWidth" :enable-close-icon="props.removable"
|
||||
@close-icon-click="onDeleteClick(idx)" :full-screen-preview-image-url="toRawFileUrl(file)"
|
||||
:tags="file?.tags.map(tagConvert)" :enable-right-click-menu="false" />
|
||||
</template>
|
||||
</RecycleScroller>
|
||||
</div>
|
||||
</template>
|
||||
<style scoped lang="scss">
|
||||
.container {
|
||||
background: var(--zp-secondary-background);
|
||||
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.actions-panel {
|
||||
padding: 8px;
|
||||
background-color: var(--zp-primary-background);
|
||||
}
|
||||
|
||||
.file-list {
|
||||
flex: 1;
|
||||
list-style: none;
|
||||
padding: 8px;
|
||||
height: var(--pane-max-height);
|
||||
width: 100%;
|
||||
|
||||
.hint {
|
||||
text-align: center;
|
||||
font-size: 2em;
|
||||
padding: 30vh 128px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
import type { GlobalConf } from '@/api'
|
||||
import type { MatchImageByTagsReq } from '@/api/db'
|
||||
import type { 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 type { getQuickMovePaths } from '@/page/taskRecord/autoComplete'
|
||||
import { type Dict, type ReturnTypeAsync } from '@/util'
|
||||
import { usePreferredDark } from '@vueuse/core'
|
||||
import { cloneDeep, uniqueId } from 'lodash-es'
|
||||
import { defineStore } from 'pinia'
|
||||
import { VNode, computed, onMounted, reactive, toRaw, watch } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { WithRequired } from 'vue3-ts-util'
|
||||
|
||||
interface TabPaneBase {
|
||||
name: string | VNode
|
||||
|
|
@ -21,7 +21,32 @@ interface TabPaneBase {
|
|||
interface OtherTabPane extends TabPaneBase {
|
||||
type: 'empty' | 'global-setting' | 'tag-search' | 'batch-download'
|
||||
}
|
||||
// logDetailId
|
||||
|
||||
export type GridViewFileTag = WithRequired<Partial<Tag>, 'name'>;
|
||||
|
||||
export interface GridViewFile extends FileNodeInfo {
|
||||
/**
|
||||
* Tags for displaying the file. The 'name' property is required,
|
||||
* while the other properties are optional.
|
||||
*/
|
||||
tags?: GridViewFileTag[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A tab pane that displays files in a grid view.
|
||||
*/
|
||||
interface GridViewTabPane extends TabPaneBase {
|
||||
type: 'grid-view'
|
||||
/**
|
||||
* Indicates whether the files in the grid view can be deleted.
|
||||
*/
|
||||
removable?: boolean
|
||||
/**
|
||||
* Indicates whether files can be dragged and dropped from other pages into the grid view.
|
||||
*/
|
||||
allowDragAndDrop?: boolean,
|
||||
files: GridViewFile[]
|
||||
}
|
||||
|
||||
interface TagSearchMatchedImageGridTabPane extends TabPaneBase {
|
||||
type: 'tag-search-matched-image-grid'
|
||||
|
|
@ -41,17 +66,7 @@ export interface FileTransferTabPane extends TabPaneBase {
|
|||
stackKey?: string
|
||||
}
|
||||
|
||||
export interface TagSearchTabPane extends TabPaneBase {
|
||||
type: 'tag-search'
|
||||
searchScope?: string
|
||||
}
|
||||
|
||||
export interface FuzzySearchTabPane extends TabPaneBase {
|
||||
type: 'fuzzy-search'
|
||||
searchScope?: string
|
||||
}
|
||||
|
||||
export type TabPane = FileTransferTabPane | OtherTabPane | TagSearchMatchedImageGridTabPane | ImgSliTabPane | TagSearchTabPane | FuzzySearchTabPane
|
||||
export type TabPane = FileTransferTabPane | OtherTabPane | TagSearchMatchedImageGridTabPane | ImgSliTabPane
|
||||
|
||||
/**
|
||||
* This interface represents a tab, which contains an array of panes, an ID, and a key
|
||||
|
|
@ -94,7 +109,7 @@ export const useGlobalStore = defineStore(
|
|||
() => {
|
||||
const conf = ref<GlobalConf>()
|
||||
const quickMovePaths = ref([] as ReturnTypeAsync<typeof getQuickMovePaths>)
|
||||
|
||||
|
||||
const enableThumbnail = ref(true)
|
||||
const gridThumbnailResolution = ref(512)
|
||||
const defaultSortingMethod = ref(SortMethod.CREATED_TIME_DESC)
|
||||
|
|
@ -158,7 +173,6 @@ export const useGlobalStore = defineStore(
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
const lang = ref(getPreferredLang())
|
||||
watch(lang, (v) => (i18n.global.locale.value = v as any))
|
||||
|
||||
|
|
@ -170,27 +184,17 @@ export const useGlobalStore = defineStore(
|
|||
})
|
||||
|
||||
const pathAliasMap = computed((): Dict<string> => {
|
||||
const keys = ['outdir_extras_samples','outdir_save','outdir_txt2img_samples',
|
||||
'outdir_img2img_samples','outdir_img2img_grids','outdir_txt2img_grids']
|
||||
const res = quickMovePaths.value.filter((v) => keys.includes(v.key)).map(v => [v.zh, v.dir])
|
||||
const keys = [
|
||||
'outdir_extras_samples',
|
||||
'outdir_save',
|
||||
'outdir_txt2img_samples',
|
||||
'outdir_img2img_samples',
|
||||
'outdir_img2img_grids',
|
||||
'outdir_txt2img_grids'
|
||||
]
|
||||
const res = quickMovePaths.value.filter((v) => keys.includes(v.key)).map((v) => [v.zh, v.dir])
|
||||
return Object.fromEntries(res)
|
||||
})
|
||||
|
||||
const ignoredConfirmActions = reactive<Record<ActionConfirmRequired, boolean>>({ deleteOneOnly: false })
|
||||
|
||||
const dark = usePreferredDark()
|
||||
|
||||
const computedTheme = computed(() => {
|
||||
const getParDark = () => {
|
||||
try {
|
||||
return parent.location.search.includes('theme=dark') // sd-webui的
|
||||
} catch (error) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
const isDark = darkModeControl.value === 'auto' ? (dark.value || getParDark()) : (darkModeControl.value === 'dark')
|
||||
return isDark ? 'dark' : 'light'
|
||||
})
|
||||
return {
|
||||
computedTheme,
|
||||
darkModeControl,
|
||||
|
|
@ -213,6 +217,7 @@ export const useGlobalStore = defineStore(
|
|||
onlyFoldersAndImages: ref(true),
|
||||
fullscreenPreviewInitialUrl: ref(''),
|
||||
shortcut,
|
||||
pageFuncExportMap,
|
||||
dontShowAgain: ref(false),
|
||||
dontShowAgainNewImgOpts: ref(false),
|
||||
ignoredConfirmActions
|
||||
|
|
|
|||
|
|
@ -100,6 +100,7 @@ export const { useEventListen: useGlobalEventListen, eventEmitter: globalEvents
|
|||
returnToIIB(): void
|
||||
updateGlobalSetting(): void
|
||||
searchIndexExpired(): void
|
||||
closeTabPane(tabIdx: number, key: string): void
|
||||
}>()
|
||||
|
||||
type AsyncFunction<T> = (...args: any[]) => Promise<T>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
```python
|
||||
AppUtils(base = "/foo", export_fn_fe=True).wrap_app(app)
|
||||
```
|
||||
Add a container for IIB
|
||||
```diff
|
||||
+ gr.HTML("error", elem_id="bar_iib_container")
|
||||
- gr.HTML("error", elem_id="infinite_image_browsing_container_wrapper")
|
||||
```
|
||||
|
||||
Load index.js on the browser side.
|
||||
```js
|
||||
const jscodeResp = await fetch("/file?path=/path/to/your/submodue-iib/index.js") // fake api
|
||||
const jsText = await jscodeResp.text()
|
||||
const js = jsText
|
||||
.replace("__iib_root_container__", "'#bar_iib_container'")
|
||||
.replace("__iib_should_maximize__", "false")
|
||||
.replace(/\/infinite_image_browsing/g, "/foo")
|
||||
eval(js)
|
||||
```
|
||||
|
||||
|
||||
|
||||
```js
|
||||
const iib = gradioApp().querySelector('#bar_iib_container iframe').contentWindow
|
||||
|
||||
const { insertTabPane, getTabList, getPageRef, createGridViewFile: f } = iib
|
||||
// The createGridViewFile function is a helper function that simplifies the creation of a FileNodeInfo object.
|
||||
const files = [
|
||||
// Create an array of files with their corresponding tags.
|
||||
f('/path/to/img/1', ['tag1', 'tag2']),
|
||||
f('/path/to/img/2', ['tag3', 'tag4', 'tag6']),
|
||||
f('/path/to/img/3', ['tag2', 'tag5']),
|
||||
f('/path/to/img/4', ['tag1', 'tag2'])
|
||||
]
|
||||
|
||||
// Insert a new tab pane of grid view type and assign it to the gridView variable.
|
||||
const gridView = insertTabPane({
|
||||
// Optional parameters for tab index and pane index.
|
||||
tabIdx: 0,
|
||||
paneIdx: 0,
|
||||
pane: {
|
||||
type: 'grid-view', // Other types are also available, see https://github.com/zanllp/sd-webui-infinite-image-browsing/tree/main/vue/src/store/useGlobalStore.ts#L15
|
||||
name: 'Grid View 1',
|
||||
removable: true, // Optional parameter to allow the files to be removed, default is false.
|
||||
allowDragAndDrop: true, // Optional parameter to allow drag and drop, default is false.
|
||||
files // Use the files array created earlier for this pane.
|
||||
}
|
||||
})
|
||||
|
||||
// Retrieve the files from the gridView pane and set them back to the same pane.
|
||||
const files = gridView.ref.getFiles()
|
||||
gridView.ref.setFiles(files)
|
||||
|
||||
// Get the tab list
|
||||
const tabList = getTabList()
|
||||
tabList[0].panes.key
|
||||
|
||||
// Get the file list from the first pane of the first tab.
|
||||
const pane = tabList[0].panes[0]
|
||||
getPageRef(pane.key).getFiles()
|
||||
|
||||
// Insert a new tab pane of local type with the specified directory path.
|
||||
const localDirPane = insertTabPane({
|
||||
pane: {
|
||||
type: 'local',
|
||||
path: 'E:/_归档/green'
|
||||
}
|
||||
})
|
||||
localDirPane.ref.close() // Closes the newly created tab pane
|
||||
```
|
||||
|
||||
To learn more information, you can refer to the type definition in the following file: https://github.com/zanllp/sd-webui-infinite-image-browsing/tree/main/vue/src/store/useGlobalStore.ts#L15 and this file: [define](./src//defineExportFunc.ts).
|
||||
Loading…
Reference in New Issue