- 檔案刪除前顯示確認視窗
+
+ 檔案刪除前顯示確認視窗
-
- 偵測到檔案新增時,插入於
+
+ 偵測到檔案新增時,插入於
+
+
顯示 Civitai Resources
+
+
+
+
+
圖片預設狀態
+
+
+
+
+
圖片數量
+
+
+
+
+
允許 NSFW 圖片
+
+
+
+
+
+
diff --git a/Www/lang/langData.js b/Www/lang/langData.js
index 2033e1a2..7c0debb8 100644
--- a/Www/lang/langData.js
+++ b/Www/lang/langData.js
@@ -624,8 +624,28 @@ var langData = {
"zh-TW": `自動找出相同檔名的檔案。
例如 "dog.jpg", "dog.txt", "dog.preview.png"`,
"en": `Automatically find files with the same file name.
For example, "dog.jpg", "dog.txt", "dog.preview.png"`,
"ja": `同じファイル名のファイルを自動的に見つけます。
例えば、"dog.jpg", "dog.txt", "dog.preview.png"`
- }
+ },
+ civitaiResourcesEnabled: {
+ "zh-TW": `顯示 Civitai Resources`,
+ "en": `Display Civitai Resources`,
+ "ja": `Civitai Resources を表示する`
+ },
+ civitaiResourcesDefault : {
+ "zh-TW": `圖片預設狀態`,
+ "en": `Default image state`,
+ "ja": `画像のデフォルト状態`,
+ },
+ civitaiResourcesImgNumber : {
+ "zh-TW": `圖片數量`,
+ "en": `Number of images`,
+ "ja": `画像の数`,
+ },
+ civitaiResourcesNsfwLevel : {
+ "zh-TW": `允許 NSFW 圖片`,
+ "en": `Allow NSFW images`,
+ "ja": `NSFW 画像を許可する`,
+ },
},
// 工具列
diff --git a/Www/scss/MainWindow/_MainExif.scss b/Www/scss/MainWindow/_MainExif.scss
index 5ef740dc..e1e238cd 100644
--- a/Www/scss/MainWindow/_MainExif.scss
+++ b/Www/scss/MainWindow/_MainExif.scss
@@ -263,7 +263,7 @@
//資訊
.mainExifList {
- padding: 0 5px;
+ padding-left: 5px;
padding-bottom: 20px;
overflow-y: auto;
overflow-x: hidden;
@@ -273,17 +273,28 @@
flex-wrap: wrap;
flex-direction: row;
align-items: center;
- border-top: 1px solid var(--color-blue20);
padding: 3px 0;
+ // 水平線
+ &::before {
+ content: "";
+ height: 1px;
+ background-color: var(--color-blue20);
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 5px;
+ }
+ // 第一個項目不顯示水平線
+ &:first-child::before {
+ display: none;
+ }
+
word-wrap: break-word;
//word-break: break-all;
font-size: 14px;
position: relative;
}
- .mainExifItem:first-child {
- border-top: none;
- }
// 匯出
.btnExport {
@@ -336,8 +347,52 @@
// 允許顯示空白跟換行
white-space: pre-wrap;
}
+ // 不顯示空白跟換行
+ .mainExifValue__nowrap {
+ white-space: normal;
+ }
+
+ // Civitai resources 的圖片
+ .mainExifImgList {
+ display: none;
+
+ &[active="true"] {
+ display: flex;
+ }
+
+ .mainExifImgItem {
+ height: 150px;
+ max-width: 150px;
+ width: 100%;
+ margin-right: 4px;
+ //text-align: center;
+ border-radius: 4px;
+
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: center;
+
+ overflow: hidden;
+ img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ pointer-events: none;
+ }
+
+ &:last-child {
+ margin-right: 0;
+ }
+
+ &:hover {
+ cursor: pointer;
+ outline: 1px solid var(--color-blue40);
+ outline-offset: -1px; // outline 內縮
+ }
+ }
+ }
- //google map
+ // google map
.mainExifMap {
position: relative;
width: 100%;
@@ -375,7 +430,7 @@
.mainExifBtns {
position: absolute;
top: 0;
- right: -5px;
+ right: 0;
display: flex;
flex-direction: row;
diff --git a/Www/scss/SettingWindow/SettingWindow.scss b/Www/scss/SettingWindow/SettingWindow.scss
index 9c7f050e..6190e233 100644
--- a/Www/scss/SettingWindow/SettingWindow.scss
+++ b/Www/scss/SettingWindow/SettingWindow.scss
@@ -187,6 +187,18 @@ input::-webkit-inner-spin-button {
max-width: 250px;
display: flex;
}
+
+ .collapseBox {
+ border-left: 1px solid var(--color-white40);
+ padding-left: 15px;
+ padding-bottom: 5px;
+ margin-left: 15px;
+
+ display: none;
+ &[active="true"] {
+ display: block;
+ }
+ }
}
.text-input {
@@ -201,7 +213,8 @@ input::-webkit-inner-spin-button {
flex: 1;
}
/******************/
-//擴充套件清單
+
+// 擴充套件清單
.pluginLiet {
margin-top: 5px;
max-width: 800px;
@@ -350,7 +363,7 @@ input::-webkit-inner-spin-button {
//#region tippy (提示方塊)
-//問號
+// 問號
.img-help {
display: inline-block;
cursor: pointer;
@@ -367,7 +380,7 @@ input::-webkit-inner-spin-button {
}
.tippy-box[data-theme~="tippyMyTheme"] {
- //自訂主題
+ // 自訂主題
box-sizing: content-box;
transition: all 0.15s cubic-bezier(0.49, 0.37, 0.45, 1.4) !important;
background-color: var(--color-black);
@@ -386,7 +399,7 @@ input::-webkit-inner-spin-button {
}
//#endregion
-//自訂工具列
+// 自訂工具列
.toolbarList {
margin-left: 30px;
@@ -494,7 +507,7 @@ input::-webkit-inner-spin-button {
//---------------------
-//如果是商店APP版,就隱藏區塊
+// 如果是商店APP版,就隱藏區塊
body[showType="storeApp"] [show-not="storeApp"] {
display: none;
}
diff --git a/Www/ts/Config.ts b/Www/ts/Config.ts
index 0450522b..275dfacd 100644
--- a/Www/ts/Config.ts
+++ b/Www/ts/Config.ts
@@ -160,7 +160,7 @@ class Config {
public settings = {
theme: {
/** 是否啟用毛玻璃 */
- "aeroType": "none", //none / win7 / win10
+ "aeroType": "none", // none / win7 / win10
/** 視窗縮放比例 */
"zoomFactor": 1.0,
/** 文字粗細 */
@@ -204,6 +204,12 @@ class Config {
/** 佈局 */
layout: {
+
+ /** 啟用 工具列 */
+ mainToolbarEnabled: true,
+ /** 工具列對齊。 [lef, center] */
+ mainToolbarAlign: "left",
+
/** 啟用 檔案預覽視窗 */
fileListEnabled: true,
/** 顯示編號 */
@@ -225,7 +231,7 @@ class Config {
dirListImgNumber: 3,
/** 啟用 詳細資料視窗 */
- mainExifEnabled: false,
+ mainExifEnabled: true,
/** 寬度 */
mainExifShowWidth: 150,
/** 顯示的最大行數 */
@@ -239,10 +245,14 @@ class Config {
/** 啟用 相關檔案 */
relatedFilesEnabled: true,
- /** 啟用 工具列 */
- mainToolbarEnabled: true,
- /** 工具列對齊。 [lef, center] */
- mainToolbarAlign: "left",
+ /** 啓用 Civitai Resources */
+ civitaiResourcesEnabled: true,
+ /** 圖片預設狀態 true=展開、false=折疊 */
+ civitaiResourcesDefault: true,
+ /** 圖片數量 */
+ civitaiResourcesImgNumber: 2,
+ /** 允許 NSFW 圖片 */
+ civitaiResourcesNsfwLevel: 3,
/** 大型切換按鈕。 [leftRight, bottom, none] */
largeBtn: "bottom",
@@ -368,7 +378,7 @@ class Config {
/** 其他 */
other: {
/** 開啟 RAW 圖片時,顯示內嵌的預覽圖 */
- rawImageThumbnail : true,
+ rawImageThumbnail: true,
/** 刪除前顯示詢問視窗 */
fileDeletingShowCheckMsg: true,
/** 偵測到檔案新增時,插入於。 [auto, start, end] */
@@ -440,7 +450,7 @@ class Config {
{ ext: "tiff", type: "vips", vipsType: "tif" },
{ ext: "dds", type: "vips", vipsType: "wpf,magick" },
{ ext: "jxr", type: "vips", vipsType: "wpf" },
-
+
{ ext: "psd", type: "vips", vipsType: "magick" },
{ ext: "psb", type: "vips", vipsType: "magick" },
{ ext: "pcx", type: "vips", vipsType: "magick" },
diff --git a/Www/ts/Lib.ts b/Www/ts/Lib.ts
index ab01949a..5880c716 100644
--- a/Www/ts/Lib.ts
+++ b/Www/ts/Lib.ts
@@ -374,18 +374,24 @@ class Lib {
/**
* 送出 GET 的 http 請求
*/
- public static async sendGet(type: ("text" | "json" | "base64"), url: string) {
+ public static async sendGet(type: ("text" | "json" | "base64"), url: string, timeout = 30000) {
+
+ const controller = new AbortController();
+
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
if (type === "text") {
let txt = "";
await fetch(url, {
"method": "get",
+ signal: controller.signal,
}).then((response) => {
return response.text();
}).then((html) => {
txt = html;
}).catch((err) => {
console.log("error: ", err);
+ throw err;
});
return txt;
}
@@ -394,12 +400,14 @@ class Lib {
let json: any = {};
await fetch(url, {
"method": "get",
+ signal: controller.signal,
}).then((response) => {
return response.json();
}).then((html) => {
json = html;
}).catch((err) => {
console.log("error: ", err);
+ throw err;
});
return json;
}
@@ -472,7 +480,7 @@ class Lib {
* @param domTitle 標題區塊
* @param type "init-true" | "init-false" | "toggle"
*/
- public static async collapse(domBox: HTMLElement, type: string, funcChange?: (type: string) => void) {
+ public static async collapse(domBox: HTMLElement, type: string, funcChange?: (type: boolean) => void) {
let domContent = domBox.querySelector(".collapse-content") as HTMLElement;
if (domContent === null) { return; }
@@ -481,7 +489,7 @@ class Lib {
if (div === null) { return; }
if (funcChange === undefined) {
- funcChange = (type: string) => { };
+ funcChange = (type: boolean) => { };
}
// 自動
@@ -500,7 +508,7 @@ class Lib {
}
domContent.style.maxHeight = 0 + "px";
domBox.setAttribute("open", "false");
- funcChange(type);
+ funcChange(false);
}
if (type === "true") {
@@ -513,19 +521,19 @@ class Lib {
domContent.style.maxHeight = "";
}
}, 300);
- funcChange(type);
+ funcChange(true);
}
// 無動畫,用於初始化
if (type === "init-false") {
domContent.style.maxHeight = 0 + "px";
domBox.setAttribute("open", "false");
- funcChange("false");
+ funcChange(false);
}
if (type === "init-true") {
domContent.style.maxHeight = "";
domBox.setAttribute("open", "true");
- funcChange("true");
+ funcChange(true);
}
}
@@ -775,3 +783,65 @@ class Throttle {
}, timeout);
}
}
+
+/**
+ * 限制最大同時連線數。Chrome最大連線數為6
+ */
+class RequestLimiter {
+ private queue: [HTMLImageElement, string][];
+ private inProgress: number;
+ private maxRequests: number;
+
+ constructor(maxRequests: number) {
+ this.queue = [];
+ this.inProgress = 0;
+ this.maxRequests = maxRequests;
+ }
+
+ public addRequest(img: HTMLImageElement, url: string) {
+
+ // 檢查 img 元素是否仍然存在於文檔中
+ if (!document.body.contains(img)) {
+ return;
+ }
+
+ // 檢查佇列中是否已經存在相同的 img 元素和網址
+ const index = this.queue.findIndex(([i, u]) => i === img && u === url);
+ if (index !== -1) { // 如果存在,則忽略這個請求
+ return;
+ }
+
+ // 檢查佇列中是否存在相同的 img 元素但不同的網址
+ const index2 = this.queue.findIndex(([i, u]) => i === img && u !== url);
+ if (index2 !== -1) { // 如果存在,則將舊的請求從佇列中移除
+ this.queue.splice(index2, 1);
+ }
+
+ // 添加新的請求
+ this.queue.push([img, url]);
+ this.processQueue();
+ }
+
+ private processQueue() {
+ while (this.inProgress < this.maxRequests && this.queue.length > 0) {
+ this.inProgress++;
+ const [img, url] = this.queue.shift()!;
+ this.loadImage(img, url).then(() => {
+ this.inProgress--;
+ this.processQueue();
+ });
+ }
+ }
+
+ private loadImage(img: HTMLImageElement, url: string) {
+ return new Promise
((resolve) => {
+ if (!document.body.contains(img)) { // 檢查 img 元素是否仍然存在於文檔中
+ resolve();
+ return;
+ }
+ img.addEventListener("load", () => resolve(), { once: true });
+ img.addEventListener("error", () => resolve(), { once: true });
+ img.src = url;
+ });
+ }
+}
diff --git a/Www/ts/MainWindow/AiDrawingPrompt.ts b/Www/ts/MainWindow/AiDrawingPrompt.ts
index ffdc9722..056180ab 100644
--- a/Www/ts/MainWindow/AiDrawingPrompt.ts
+++ b/Www/ts/MainWindow/AiDrawingPrompt.ts
@@ -464,12 +464,14 @@ class AiDrawingPrompt {
let loraName = intputs["lora_name_" + i];
if (loraName === undefined) { break; }
+ if (modelStr === 0 && clipStr === 0) { continue; } // 如果都是 0,則略過
if (loraName === "None" || loraName === "none") { continue; } // 如果是 None,則略過
let jsonFormat = Lib.stringifyWithNewlines({
"Model Strength": modelStr,
"Clip Strength": clipStr
}, true, true);
arLora.push({ title: loraName, text: jsonFormat });
+
}
}
@@ -510,6 +512,7 @@ class AiDrawingPrompt {
let clipWeight = intputs["clip_weight_" + i];
if (clipWeight === undefined) { break; }
+ if (modelWeight === 0 && clipWeight === 0) { continue; } // 如果都是 0,則略過
if (loraName === "None" || loraName === "none") { continue; } // 如果是 None,則略過
let jsonFormat = Lib.stringifyWithNewlines({
"Model Strength": modelWeight,
@@ -540,6 +543,9 @@ class AiDrawingPrompt {
let value = intputs[key];
let type = typeof value;
if (type === "number" || type === "string") {
+
+ if (value === 0) { return; } // 如果是 0,則略過
+
if (key === "strength_model" || key === "lora_model_strength") {
arStrength["Model Strength"] = value;
}
diff --git a/Www/ts/MainWindow/BulkView.ts b/Www/ts/MainWindow/BulkView.ts
index 3edcf2f7..09b70ba9 100644
--- a/Www/ts/MainWindow/BulkView.ts
+++ b/Www/ts/MainWindow/BulkView.ts
@@ -1320,65 +1320,3 @@ class BulkView {
}
}
-
-/**
- * 限制最大同時連線數。Chrome最大連線數為6
- */
-class RequestLimiter {
- private queue: [HTMLImageElement, string][];
- private inProgress: number;
- private maxRequests: number;
-
- constructor(maxRequests: number) {
- this.queue = [];
- this.inProgress = 0;
- this.maxRequests = maxRequests;
- }
-
- addRequest(img: HTMLImageElement, url: string) {
-
- // 檢查 img 元素是否仍然存在於文檔中
- if (!document.body.contains(img)) {
- return;
- }
-
- // 檢查佇列中是否已經存在相同的 img 元素和網址
- const index = this.queue.findIndex(([i, u]) => i === img && u === url);
- if (index !== -1) { // 如果存在,則忽略這個請求
- return;
- }
-
- // 檢查佇列中是否存在相同的 img 元素但不同的網址
- const index2 = this.queue.findIndex(([i, u]) => i === img && u !== url);
- if (index2 !== -1) { // 如果存在,則將舊的請求從佇列中移除
- this.queue.splice(index2, 1);
- }
-
- // 添加新的請求
- this.queue.push([img, url]);
- this.processQueue();
- }
-
- private processQueue() {
- while (this.inProgress < this.maxRequests && this.queue.length > 0) {
- this.inProgress++;
- const [img, url] = this.queue.shift()!;
- this.loadImage(img, url).then(() => {
- this.inProgress--;
- this.processQueue();
- });
- }
- }
-
- private loadImage(img: HTMLImageElement, url: string) {
- return new Promise((resolve) => {
- if (!document.body.contains(img)) { // 檢查 img 元素是否仍然存在於文檔中
- resolve();
- return;
- }
- img.addEventListener("load", () => resolve(), { once: true });
- img.addEventListener("error", () => resolve(), { once: true });
- img.src = url;
- });
- }
-}
diff --git a/Www/ts/MainWindow/IndexedDBManager.ts b/Www/ts/MainWindow/IndexedDBManager.ts
index ea5a1dd6..af6bf739 100644
--- a/Www/ts/MainWindow/IndexedDBManager.ts
+++ b/Www/ts/MainWindow/IndexedDBManager.ts
@@ -2,12 +2,17 @@
* 資料表名稱
*/
var DbStoreName = {
+ /** Civitai Resources 的暫存資料 */
civitaiResources: "civitaiResources",
+ /** 詳細資訊面板內的項目折疊狀態 */
+ infoPanelCollapse: "infoPanelCollapse",
}
class IndexedDBManager {
- private storeNames: string[] = [DbStoreName.civitaiResources]; // 資料表名稱
+ // 資料表名稱
+ private storeNames: string[] = Object.values(DbStoreName);
+
private dbPromise: Promise;
constructor(private dbName: string, private version: number) {
this.dbPromise = this.init();
@@ -41,7 +46,7 @@ class IndexedDBManager {
public async getData(storeName: string, id: string): Promise {
const db = await this.dbPromise;
return new Promise((resolve, reject) => {
- const transaction = db.transaction([storeName]);
+ const transaction = db.transaction(storeName);
const objectStore = transaction.objectStore(storeName);
const request = objectStore.get(id);
@@ -61,9 +66,16 @@ class IndexedDBManager {
* @param data 必須是物件,且有 id 屬性,例如 {id:123, name:"abc"}
*/
public async saveData(storeName: string, data: any): Promise {
+ const db = await this.dbPromise;
+ const transaction = db.transaction(storeName, "readwrite");
+ const objectStore = transaction.objectStore(storeName);
+ objectStore.put(data);
+ }
+
+ public async deleteData(storeName: string, id: string): Promise {
const db = await this.dbPromise;
const transaction = db.transaction([storeName], "readwrite");
const objectStore = transaction.objectStore(storeName);
- objectStore.add(data);
+ objectStore.delete(id);
}
}
diff --git a/Www/ts/MainWindow/MainExif.ts b/Www/ts/MainWindow/MainExif.ts
index 77c6a2e1..b002da1c 100644
--- a/Www/ts/MainWindow/MainExif.ts
+++ b/Www/ts/MainWindow/MainExif.ts
@@ -33,6 +33,9 @@ class MainExif {
var isHide = false; // 暫時隱藏
var isEnabled = true; // 啟用 檔案預覽視窗
+ /** 請求限制器 */
+ const limiter = new RequestLimiter(3);
+
/** 頁籤的類型 */
var TabType = {
/** 資訊 */
@@ -534,7 +537,7 @@ class MainExif {
const data = cdata[i].data;
// 折疊面板
- let collapseDom = getCollapseDom(node, true);
+ let collapseDom = await getCollapseDom(node, true);
data.forEach(item => {
collapseDom.domContent.appendChild(getItemDom(item.title, item.text));
});
@@ -546,14 +549,14 @@ class MainExif {
// 把 ComfyScript 放在最下面,並預設展開
if (comfyScript !== undefined) {
// 折疊面板
- let collapseDom = getCollapseDom("ComfyScript", true);
+ let collapseDom = await getCollapseDom("ComfyScript", true);
collapseDom.domContent.appendChild(getItemDom("ComfyScript", comfyScript));
domTabContentInfo.appendChild(collapseDom.domBox);
}
// 把 ComfyUI 的原始資料放在最下面,並預設折疊
if (comfyuiPrompt !== undefined || comfyuiWorkflow !== undefined) {
// 折疊面板
- let collapseDom = getCollapseDom("ComfyUI Data", false);
+ let collapseDom = await getCollapseDom("ComfyUI Data", false);
if (comfyuiGenerationData !== undefined) {
let jsonF = Lib.jsonStrFormat(comfyuiGenerationData);
@@ -711,14 +714,14 @@ class MainExif {
let tempId: any[] = [];
// 折疊面板
- let collapseDom = getCollapseDom("Civitai Resources", true, "civitaiResources");
+ let collapseDom = await getCollapseDom("Civitai Resources", true, "civitaiResources", (type) => { });
for (let i = 0; i < data.length; i++) {
const item = data[i];
let hash = item.hash;
let modelVersionId = item.modelVersionId;
let dbKey = modelVersionId || hash;
- if (hash === undefined && modelVersionId === undefined) {
+ if (dbKey === undefined || dbKey === null) {
console.warn("Civitai 未預期的格式");
console.warn(item);
continue;
@@ -728,15 +731,31 @@ class MainExif {
let modelType: any = item.type;
let modelName: any;
let name: any;
+ let images: any[];
let error: any;
// 先嘗試從 indexedDB 取得資料
let dbData = await M.db?.getData(DbStoreName.civitaiResources, dbKey);
+
+ if (dbData !== undefined) {
+ // 如果發生過錯誤,且時間超過 1 天,就重新下載
+ if (dbData.error !== undefined) {
+ let time = new Date(dbData.time).getTime();
+ let timeNow = new Date().getTime();
+ //if (isNaN(time) || timeNow - time > 20 * 1000) {
+ if (isNaN(time) || timeNow - time > 24 * 60 * 60 * 1000) {
+ dbData = undefined;
+ }
+ }
+ }
+
if (dbData !== undefined) {
+
modelId = dbData.modelId;
modelType = dbData.modelType;
modelName = dbData.modelName;
name = dbData.name;
+ images = dbData.images;
modelVersionId = dbData.modelVersionId;
error = dbData.error;
// console.log("indexedDB 取得資料", modelId, modelName, error);
@@ -750,46 +769,75 @@ class MainExif {
if (path !== fileInfo2.FullPath) { return; }
// 曾經下載過,且資料是 error,就不再載入
- if (error) { continue; }
+ if (error === "Model not found") { continue; }
// 先產生一個空的 dom 項目,待資料載入完畢後,再替換
let oldDom = getItemDom(dbKey, "Loading" + "\n" + "-");
collapseDom.domContent.appendChild(oldDom);
+ let ompleteCount = 0;
+ // 項目完成時呼叫的函數,用與判斷是否全部都已經完成
+ let completeFunc = () => {
+ ompleteCount++;
+ if (ompleteCount === data.length) {
+ // 如果移除項目後,折疊面板沒有任何項目,則移除折疊面板
+ if (collapseDom.domContent.children.length === 0) {
+ collapseDom.domBox.parentNode?.removeChild(collapseDom.domBox);
+ }
+ }
+ }
+
setTimeout(async () => {
// 如果 indexedDB 沒有資料,則從 Civitai 取得資料
if (error === undefined &&
- (modelId === undefined || modelName === undefined)) {
- let url: string;
- let result
- if (hash) {
+ (modelId === undefined || modelName === undefined || images === undefined)) {
+ let url: string = "";
+ let result;
+ let timeout = 10 * 1000;
- url = `https://civitai.com/api/v1/model-versions/by-hash/` + hash;
- result = await Lib.sendGet("json", url);
+ try {
+ if (hash) {
- // 如果 hash 超過 10 個字,且找不到資源,則只取前 10 個字再試一次
- if (result.error && hash.length > 10) {
- hash = hash.substring(0, 10);
url = `https://civitai.com/api/v1/model-versions/by-hash/` + hash;
- result = await Lib.sendGet("json", url);
- console.log("Civitai 重新請求資料", url);
+ result = await Lib.sendGet("json", url, timeout);
+
+ // 如果 hash 超過 10 個字,且找不到資源,則只取前 10 個字再試一次
+ if (result.error && hash.length > 10) {
+ hash = hash.substring(0, 10);
+ url = `https://civitai.com/api/v1/model-versions/by-hash/` + hash;
+ result = await Lib.sendGet("json", url, timeout);
+ // console.log("Civitai 重新請求資料", url);
+ }
+ } else {
+ url = `https://civitai.com/api/v1/model-versions/` + modelVersionId;
+ result = await Lib.sendGet("json", url, timeout);
+ }
+ } catch (e) {
+
+ console.error("Civitai 請求失敗", e);
+ let exifValue = oldDom.querySelector(".mainExifValue") as HTMLElement
+ exifValue.innerHTML = "Error";
+ result = {
+ error: "Request error"
}
- } else {
- url = `https://civitai.com/api/v1/model-versions/` + modelVersionId;
- result = await Lib.sendGet("json", url);
}
+
// console.log("Civitai 請求資料", url);
modelId = result.modelId;
modelName = result.model?.name;
modelType = result.model?.type;
name = result.name;
+ images = result.images;
modelVersionId = result.id;
error = result.error;
if (result.error) {
- console.warn("Civitai 返回 error", url, result);
+ console.log("Civitai 返回 error", url, result);
+ let exifValue = oldDom.querySelector(".mainExifValue") as HTMLElement
+ exifValue.innerHTML = "Error";
+
// 如果找不到資源,則一樣存到 indexedDB
M.db?.saveData(DbStoreName.civitaiResources, {
id: dbKey,
@@ -798,10 +846,18 @@ class MainExif {
});
} else {
if (modelId === undefined || modelName === undefined) {
- console.warn("Civitai 返回資料解析失敗", url, result);
+ console.log("Civitai 返回資料解析失敗", url, result);
}
+
// 存到 indexedDB
- M.db?.saveData(DbStoreName.civitaiResources, {
+ let images2 = result.images.map((item: any) => {
+ return {
+ url: item.url,
+ nsfwLevel: item.nsfwLevel,
+ type: item.type
+ };
+ });
+ await M.db?.saveData(DbStoreName.civitaiResources, {
id: dbKey,
time: new Date().format("yyyy-MM-dd hh:mm:ss"),
modelId: modelId,
@@ -809,16 +865,16 @@ class MainExif {
modelName: modelName,
modelType: modelType,
name: name,
+ images: images2
});
+ console.log("Civitai 成功", url);
+
}
// 重複的項目
- if (tempId.includes(modelVersionId)) {
+ if (modelVersionId !== undefined && tempId.includes(modelVersionId)) {
oldDom.parentNode?.removeChild(oldDom);
- // 如果移除項目後,折疊面板沒有任何項目,則移除折疊面板
- if (collapseDom.domContent.children.length === 0) {
- collapseDom.domBox.parentNode?.removeChild(collapseDom.domBox);
- }
+ completeFunc();
return;
}
tempId.push(modelVersionId);
@@ -826,43 +882,158 @@ class MainExif {
if (error) {
oldDom.parentNode?.removeChild(oldDom);
- // 如果移除項目後,折疊面板沒有任何項目,則移除折疊面板
- if (collapseDom.domContent.children.length === 0) {
- collapseDom.domBox.parentNode?.removeChild(collapseDom.domBox);
- }
+ completeFunc();
return;
}
if (modelId === undefined || modelName === undefined) {
+ completeFunc();
return;
}
// 檢查 oldDom 是否還存在
if (oldDom.parentNode !== null) {
+ if (baseWindow.appInfo === undefined) { return; }
+
// 產生新的 dom
let newItem = Lib.newDom(`
-
-
${modelType}
-
${modelName}
${name}
-
-
${SvgList["tool-civitai.svg"]}
-
-
`);
+
+
${Lib.escape(modelType)}
+
+ ${Lib.escape(modelName)}
+ ${Lib.escape(name)}
+
+
+
+
+
${SvgList["expand.svg"]}
+
${SvgList["collapse.svg"]}
+
${SvgList["tool-civitai.svg"]}
+
+
`);
+ domTabContentInfo.appendChild(newItem);
+
+ let btnExpand = newItem.querySelector(".mainExifBtnExpand") as HTMLElement; // 折疊
+ let btnCollapse = newItem.querySelector(".mainExifBtnCollapse") as HTMLElement; // 折疊
let btnCivitai = newItem.querySelector(".mainExifBtnCivitai") as HTMLElement;
+ let divImgList = newItem.querySelector(".mainExifImgList") as HTMLElement;
+
+ // 產生預覽圖
+ let imgCount = M.config.settings.layout.civitaiResourcesImgNumber;
+ let nsfwLevel = M.config.settings.layout.civitaiResourcesNsfwLevel;
+
+ // 判斷是否有圖片
+ let isHaveImg = false;
+ // 載入圖片
+ let funcLoadImg: (() => void)[] = [];
+
+ for (let i = 0; i < images.length; i++) {
+ const item = images[i];
+ if (item.type === "image" && item.nsfwLevel <= nsfwLevel) {
+
+ isHaveImg = true;
+
+ // 開啟網址時下載圖片,並回傳其 icon
+ let name = `Civitai\\${dbKey}-${i + 1}.jpg`;
+ let imgPath = Lib.Combine([baseWindow.appInfo.tempDirWebFile, name]);
+ let imgUrl = WebAPI.Img.webIcon(item.url, name);
+
+ let imgItem = Lib.newDom(`
+
+
+
+ `);
+ divImgList.appendChild(imgItem);
+ imgItem.setAttribute("data-path", imgPath);
+
+ const domImg = imgItem.querySelector("img") as HTMLImageElement;
+
+ // 展開時,載入圖片,已經載入過的就不再載入
+ let isLoad = false;
+ funcLoadImg.push(() => {
+ if (isLoad) { return; }
+ isLoad = true;
+ limiter.addRequest(domImg, imgUrl); // 發出請求
+ })
+
+ // 圖片載入失敗時,顯示錯誤圖示
+ domImg.onerror = () => {
+ domImg.src = "./img/error.svg";
+ domImg.style.objectFit = "contain";
+ }
+
+ // 雙擊左鍵
+ Lib.addEventDblclick(imgItem, async (e: MouseEvent) => { // 圖片物件
+ let imgPath = imgItem.getAttribute("data-path");
+ if (imgPath === null) { return; }
+ M.script.open.openNewWindow(imgPath);
+ });
+
+ imgCount--;
+ if (imgCount <= 0) { break; }
+ }
+ }
+
btnCivitai.addEventListener("click", async () => {
let url = "https://civitai.com/models/" + modelId + "?modelVersionId=" + modelVersionId;
WV_RunApp.OpenUrl(url);
});
- domTabContentInfo.appendChild(newItem);
+
+ // 套用狀態。 true=展開 、 false=折疊、 null=不顯示
+ function setCollapse(t: boolean | null) {
+ if (t) { // 狀態是展開,顯示折疊按鈕
+ btnExpand.style.display = "none";
+ btnCollapse.style.display = "";
+ divImgList.setAttribute("active", "true");
+
+ // 展開時,載入圖片
+ funcLoadImg.forEach(func => {
+ func();
+ })
+
+ } else if (t === false) {
+ btnExpand.style.display = "";
+ btnCollapse.style.display = "none";
+ divImgList.setAttribute("active", "");
+ } else {
+ // 什麼都不顯示
+ btnExpand.style.display = "none";
+ btnCollapse.style.display = "none";
+ divImgList.removeAttribute("active");
+ }
+ }
+ btnExpand.addEventListener("click", async () => { // 展開
+ setCollapse(true);
+ // 儲存折疊狀態
+ M.db?.saveData(DbStoreName.infoPanelCollapse, { id: "civitaiImg-" + dbKey, collapse: true });
+ });
+ btnCollapse.addEventListener("click", async () => { // 折疊
+ setCollapse(false);
+ // 儲存折疊狀態
+ M.db?.saveData(DbStoreName.infoPanelCollapse, { id: "civitaiImg-" + dbKey, collapse: false });
+ });
+
+ // 從 db 讀取折疊狀態
+ let dbCollapse = await M.db?.getData(DbStoreName.infoPanelCollapse, "civitaiImg-" + dbKey);
+ if (dbCollapse !== undefined) {
+ setCollapse(dbCollapse.collapse);
+ } else {
+ setCollapse(M.config.settings.layout.civitaiResourcesDefault);
+ }
+ // 如果沒有圖片,就不顯示展開按鈕
+ if (isHaveImg === false) {
+ setCollapse(null);
+ }
// 把新的 dom 插到原有的 dom 後面,然後刪除原有的 dom
oldDom.insertAdjacentElement("afterend", newItem);
oldDom.parentNode?.removeChild(oldDom);
+
+ completeFunc();
}
}, 1);
-
}
if (collapseDom.domContent.children.length > 0) {
@@ -1090,8 +1261,8 @@ class MainExif {
}
Lib.collapse(domBox, "toggle", (type) => { //切換折疊狀態
- let t = (type === "true");
- M.config.settings.layout.mainExifCollapse[title] = t; //更改狀態後,儲存折疊狀態
+
+ M.config.settings.layout.mainExifCollapse[title] = type; //更改狀態後,儲存折疊狀態
});
})
@@ -1104,11 +1275,11 @@ class MainExif {
* @param type 初始狀態
* @param key 儲存設定值的key
*/
- function getCollapseDom(title: string, type: boolean, key?: string) {
+ async function getCollapseDom(title: string, type: boolean, key?: string, funcChange?: (type: boolean) => void) {
// 外框物件
let domBox = Lib.newDom(`
-
+
`);
@@ -1133,10 +1304,14 @@ class MainExif {
// 從設定讀取折疊狀態
let open;
+ // 從 db 讀取折疊狀態
if (key !== undefined) {
- open = M.config.settings.layout.mainExifCollapse[key];
+ let dbCollapse = await M.db?.getData(DbStoreName.infoPanelCollapse, key);
+ if (dbCollapse !== undefined) { open = dbCollapse.collapse; }
+ }
+ if (open === undefined) {
+ open = type;
}
- if (open === undefined) { open = type; }
// 初始化 折疊面板
Lib.collapse(domBox, "init-" + open); // 不使用動畫直接初始化狀態
@@ -1146,12 +1321,12 @@ class MainExif {
if (target !== null && target.classList.contains("mainExifRelatedTitleBtn")) {
return;
}
- Lib.collapse(domBox, "toggle", (type) => {
+ Lib.collapse(domBox, "toggle", async (type) => {
if (key !== undefined) {
- let t = (type === "true");
- M.config.settings.layout.mainExifCollapse[key] = t; // 更改狀態後,儲存折疊狀態
+ funcChange?.(type);
+ // 更改狀態後,儲存折疊狀態
+ await M.db?.saveData(DbStoreName.infoPanelCollapse, { id: key, collapse: type });
}
-
}); // 切換折疊狀態
});
diff --git a/Www/ts/MainWindow/MainWindow.ts b/Www/ts/MainWindow/MainWindow.ts
index 8b46c42e..9c878466 100644
--- a/Www/ts/MainWindow/MainWindow.ts
+++ b/Www/ts/MainWindow/MainWindow.ts
@@ -95,7 +95,7 @@ class MainWindow {
(async () => {
- db = await new IndexedDBManager("tiefseeDB", 1);
+ db = await new IndexedDBManager("tiefseeDB", 2);
this.db = db;
fileShow.openNone(); // 不顯示任何東西
diff --git a/Www/ts/SettingWindow/SettingWindow.ts b/Www/ts/SettingWindow/SettingWindow.ts
index 8736588c..47447023 100644
--- a/Www/ts/SettingWindow/SettingWindow.ts
+++ b/Www/ts/SettingWindow/SettingWindow.ts
@@ -898,7 +898,7 @@ class SettingWindow {
addLoadEvent(() => {
// 顯示 詳細資料面板
var switch_mainExifEnabled = getDom("#switch-mainExifEnabled") as HTMLInputElement;
- switch_mainExifEnabled.checked = config.settings["layout"]["mainExifEnabled"]; //
+ switch_mainExifEnabled.checked = config.settings["layout"]["mainExifEnabled"];
switch_mainExifEnabled.addEventListener("change", () => {
let val = switch_mainExifEnabled.checked;
config.settings["layout"]["mainExifEnabled"] = val;
@@ -935,6 +935,52 @@ class SettingWindow {
appleSettingOfMain();
});
+ // 顯示 Civitai Resources
+ var divCivitaiBox = getDom("#civitaiBox") as HTMLElement;
+ function updateCivitaiBox() {
+ if (switch_civitaiResourcesEnabled.checked) {
+ divCivitaiBox.setAttribute("active", "true");
+ } else {
+ divCivitaiBox.removeAttribute("active");
+ }
+ }
+ var switch_civitaiResourcesEnabled = getDom("#switch-civitaiResourcesEnabled") as HTMLInputElement;
+ switch_civitaiResourcesEnabled.checked = config.settings["layout"]["civitaiResourcesEnabled"];
+ updateCivitaiBox();
+ switch_civitaiResourcesEnabled.addEventListener("change", () => {
+ let val = switch_civitaiResourcesEnabled.checked;
+ config.settings["layout"]["civitaiResourcesEnabled"] = val;
+ appleSettingOfMain();
+ updateCivitaiBox();
+ });
+
+ // 圖片預設狀態
+ var select_civitaiResourcesDefault = getDom("#select-civitaiResourcesDefault") as HTMLSelectElement;
+ select_civitaiResourcesDefault.value = config.settings.layout.civitaiResourcesDefault.toString();
+ select_civitaiResourcesDefault.addEventListener("change", () => {
+ let val = select_civitaiResourcesDefault.value;
+ config.settings.layout.civitaiResourcesDefault = val === "true";
+ appleSettingOfMain();
+ });
+
+ // 圖片數量
+ var select_civitaiResourcesImgNumber = getDom("#select-civitaiResourcesImgNumber") as HTMLSelectElement;
+ select_civitaiResourcesImgNumber.value = config.settings.layout.civitaiResourcesImgNumber.toString();
+ select_civitaiResourcesImgNumber.addEventListener("change", () => {
+ let val = Number(select_civitaiResourcesImgNumber.value);
+ config.settings.layout.civitaiResourcesImgNumber = val;
+ appleSettingOfMain();
+ });
+
+ // 允許 NSFW 圖片
+ var switch_civitaiResourcesNsfwLevel = getDom("#switch-civitaiResourcesNsfwLevel") as HTMLInputElement;
+ switch_civitaiResourcesNsfwLevel.checked = config.settings.layout.civitaiResourcesNsfwLevel == 99;
+ switch_civitaiResourcesNsfwLevel.addEventListener("change", () => {
+ let val = switch_civitaiResourcesNsfwLevel.checked;
+ config.settings.layout.civitaiResourcesNsfwLevel = val ? 99 : 3;
+ appleSettingOfMain();
+ });
+
})
// 大型切換按鈕
diff --git a/Www/ts/WebAPI.ts b/Www/ts/WebAPI.ts
index 48f123f6..4652fcc3 100644
--- a/Www/ts/WebAPI.ts
+++ b/Www/ts/WebAPI.ts
@@ -85,6 +85,16 @@ class WebAPI {
return APIURL + "/api/getFileIcon?size=256&path=" + encodeURIComponent(path);
}
+ /**
+ * 從網路下載圖片後,返回圖片的 icon
+ */
+ static webIcon(url: string, path: string) {
+ let encodePath = encodeURIComponent(path);
+ let encodeUrl = encodeURIComponent(url);
+ let r = 0; // Math.random(); // 避免快取
+ return APIURL + `/api/getWebIcon?size=256&url=${encodeUrl}&path=${encodePath}&r=${r}`;
+ }
+
/**
* 取得圖片網址
*/
diff --git a/Www/ts/d/NetAPI.d.ts b/Www/ts/d/NetAPI.d.ts
index 6a0eb3c3..9b293f03 100644
--- a/Www/ts/d/NetAPI.d.ts
+++ b/Www/ts/d/NetAPI.d.ts
@@ -1,6 +1,6 @@
interface WebWindow {
- /** 運行js */
+ /** 運行 js */
RunJs(js: string): string;
/** 視窗取得焦點 */
@@ -41,13 +41,13 @@ interface WebWindow {
interface WV_Window {
- /** 清理webview2的暫存 */
+ /** 清理 webview2 的暫存 */
ClearBrowserCache(): void;
/** 儲存到 start.ini */
SetStartIni(startPort: number, startType: number)
- /** 取得 AppInfo*/
+ /** 取得 AppInfo */
GetAppInfo(): string;
/** 網頁載入完成後,呼叫此函數才會顯示視窗 */
@@ -79,10 +79,10 @@ interface WV_Window {
/** 傳入 webWindow,將其設為目前視窗的子視窗*/
SetOwner(webwindow: WebWindow);
- /** 在父親視窗運行js */
+ /** 在父視窗執行 js */
RunJsOfParent(js: string): string;
- /** 啟用AERO毛玻璃效果 */
+ /** 啟用 AERO 毛玻璃效果 */
SetAERO(type: ("win7" | "win10")): void;
/** 設定縮放倍率,預設 1.0 */
@@ -547,6 +547,9 @@ interface AppInfo {
/** AppData(使用者資料) */
appDataPath: string;
+ /** 暫存資料夾 - 從網路下載的檔案 */
+ tempDirWebFile: string;
+
/** 目前使用的port */
mainPort: number;
@@ -583,4 +586,4 @@ interface FileWatcherData {
OldFullPath: string;
ChangeType: "changed" | "created" | "deleted" | "renamed";
FileType: "file" | "dir";
-}
\ No newline at end of file
+}