Polish asset sidebar UI

This commit is contained in:
Ewald
2026-06-30 09:27:05 +02:00
parent 5fadbbbeb1
commit 8e9e525317
18 changed files with 1861 additions and 1795 deletions
@@ -2,12 +2,12 @@ import { Wt as e, h as t, v as n } from "./viewerRuntimeHosts-B9wQ_Nxj.js";
import { B as r, C as i, I as a, M as o, O as s, P as c, R as l, S as u, V as d, j as f, m as p, o as m, pt as h, r as g, rt as _, v, w as y, x as b } from "./events-BpkKbGZs.js";
import { a as x, i as S, o as C, s as w } from "./graphTraversal-CjIZsRsP.js";
import { a as T, i as ee } from "./mediaFps-CbdE2lHO.js";
import { a as E, c as D, i as O, n as k, o as te, r as A } from "./SidebarWorkflowSection-BkH7KoSY.js";
import { a as j, c as M, i as N, n as P, o as ne, r as F, s as re, t as ie } from "./openMajoorSettings-DDiqOHU0.js";
import { a as E, c as D, i as O, n as k, o as te, r as A } from "./SidebarWorkflowSection-Dhrg9X98.js";
import { a as j, c as M, i as N, n as P, o as ne, r as F, s as re, t as ie } from "./openMajoorSettings-CVZbL23Z.js";
import { i as I, n as L, r as R } from "./VideoControls-Dy5An_lN.js";
import { i as ae, o as z, r as oe, t as se } from "./geninfoParser-DMd9PWlT.js";
import { l as ce, o as le, p as ue, r as de, s as fe } from "./mediaPlayer-DTyhTlcJ.js";
import { t as pe } from "./genInfo-CPsL2cZc.js";
import { t as pe } from "./genInfo-B6Ymnifm.js";
//#region ui/features/viewer/floatingViewerConstants.ts
var B = Object.freeze({
SIMPLE: "simple",
@@ -1,5 +1,5 @@
import { J as e, r as t } from "./events-BpkKbGZs.js";
import { t as n } from "./floatingViewerManager-pz1ceTHv.js";
import { t as n } from "./floatingViewerManager-tOuaq4XR.js";
//#region ui/features/viewer/LiveStreamTracker.ts
var r = !1, i = null, a = null, o = null, s = null, c = null, l = 0, u = 0, d = 400, f = new Set([
".png",
File diff suppressed because it is too large Load Diff
@@ -6449,7 +6449,7 @@ function Zt() {
qt = e;
}), Jt || import("./scopes-X1iFrTle.js").then((e) => {
Jt = e;
}), Yt || import("./genInfo-CPsL2cZc.js").then((e) => e.n).then((e) => {
}), Yt || import("./genInfo-B6Ymnifm.js").then((e) => e.n).then((e) => {
Yt = e;
}), Xt || import("./frameExport-tksSZ7sb.js").then((e) => {
Xt = e;
@@ -1,9 +1,9 @@
import { a as e, i as t } from "./viewerRuntimeHosts-B9wQ_Nxj.js";
import { r as n } from "./events-BpkKbGZs.js";
import { i as r, r as i } from "./floatingViewerManager-pz1ceTHv.js";
import { i as r, r as i } from "./floatingViewerManager-tOuaq4XR.js";
import { B as a, C as o, D as s, E as c, G as l, H as u, I as d, O as f, R as p, T as m, W as h, ct as g, dt as _, j as v, k as y, lt as b, nt as x, q as S, ut as C, w, z as T } from "./mjr-primevue-n1rsQYJg.js";
import { t as E } from "./TagsEditor-DtmqPU71.js";
import { a as D, c as O, i as k, n as A, o as ee, r as j, s as M, t as N } from "./Viewer-BG31OAT8.js";
import { a as D, c as O, i as k, n as A, o as ee, r as j, s as M, t as N } from "./Viewer-CnTOrl2S.js";
//#region ui/vue/components/viewer/FloatingViewerHost.vue
var P = {
__name: "FloatingViewerHost",
@@ -98,7 +98,7 @@ var ie = /* @__PURE__ */ e({
teardownFloatingViewerManager: () => $
}), h = null, g = null;
async function ae() {
return h || (g ||= import("./FloatingViewer-BokZM7bF.js").then((e) => (h = e.FloatingViewer, h)), g);
return h || (g ||= import("./FloatingViewer-mV836Kgg.js").then((e) => (h = e.FloatingViewer, h)), g);
}
var _ = null, oe = null;
async function se() {
@@ -1,7 +1,7 @@
import { t as e } from "./rolldown-runtime-Dy4uBu1J.js";
import { m as t, o as n } from "./events-BpkKbGZs.js";
import { c as r } from "./mediaFps-CbdE2lHO.js";
import { c as i, h as a, l as o, s, t as c } from "./SidebarWorkflowSection-BkH7KoSY.js";
import { c as i, h as a, l as o, s, t as c } from "./SidebarWorkflowSection-Dhrg9X98.js";
import { B as l, D as u, E as d, O as f, T as p, ct as m, dt as h, k as g, ut as _ } from "./mjr-primevue-n1rsQYJg.js";
//#region ui/vue/components/viewer/ViewerMetadataBlock.vue
var v = { style: {
@@ -1,6 +1,6 @@
import { A as e, E as t, J as n, R as r, Y as i, b as a, j as o, m as s } from "./events-BpkKbGZs.js";
import { a as c, n as l } from "./graphTraversal-CjIZsRsP.js";
import { S as u } from "./SidebarWorkflowSection-BkH7KoSY.js";
import { S as u } from "./SidebarWorkflowSection-Dhrg9X98.js";
//#region ui/features/viewer/floatingViewerProgress.ts
var d = "progress-update", f = "__MJR_MFV_PROGRESS_SERVICE__";
function p() {
@@ -1,5 +1,5 @@
import { r as e } from "./events-BpkKbGZs.js";
import { t } from "./Viewer-BG31OAT8.js";
import { t } from "./Viewer-CnTOrl2S.js";
//#region ui/features/viewer/viewerOpenRequest.ts
function n(e) {
let t = String(e || "").trim().toLowerCase();
+634 -651
View File
File diff suppressed because one or more lines are too long
+5 -1
View File
@@ -71,7 +71,11 @@ export function createSettingsSync({
const applySidebarPosition = (nextPosition: any) => {
const pos = String(nextPosition || "right").toLowerCase() === "left" ? "left" : "right";
if (_sidebarPosition === pos) return;
const isAlreadyOrdered =
pos === "left"
? sidebar?.nextElementSibling === gridWrapper
: gridWrapper?.nextElementSibling === sidebar;
if (_sidebarPosition === pos && isAlreadyOrdered) return;
_sidebarPosition = pos;
let scrollTop = 0;
@@ -2,6 +2,7 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { mount } from "@vue/test-utils";
import { nextTick } from "vue";
const drawWorkflowMinimap = vi.fn();
const synthesizeWorkflowFromPromptGraph = vi.fn(() => null);
@@ -281,4 +282,45 @@ describe("SidebarWorkflowSection", () => {
wrapper.unmount();
});
it("shows a compact workflow category label for deep paths", async () => {
const { default: SidebarWorkflowSection } =
await import("../vue/components/panel/sidebar/SidebarWorkflowSection.vue");
const wrapper = mount(SidebarWorkflowSection, {
props: {
asset: {
filepath: "F:/workflows/demo.json",
category: "PROJECTS/PERSONAL/02_OUT/VIDEOS/260513/Atopi",
has_generation_data: true,
workflow: {
nodes: [{ id: 1, pos: [0, 0], size: [180, 80], type: "KSampler" }],
links: [],
groups: [],
extra: {},
},
},
},
attachTo: document.body,
global: {
stubs: {
MButton: MButtonStub,
MTree: MTreeStub,
},
},
});
await nextTick();
const categoryBlock = wrapper
.findAll("div")
.find((node) => node.text().includes("Category") && node.text().includes("Atopi"));
expect(categoryBlock?.text()).toContain("Atopi");
expect(categoryBlock?.text()).not.toContain("PROJECTS");
const input = wrapper.find('input[placeholder="Workflow category"]');
expect(input.element.value).toBe("PROJECTS/PERSONAL/02_OUT/VIDEOS/260513/Atopi");
wrapper.unmount();
});
});
+8
View File
@@ -1287,8 +1287,11 @@
}
.mjr-assets-manager .mjr-am-browse {
display: flex;
align-items: stretch;
flex: 1;
min-height: 0;
min-width: 0;
overflow: hidden;
border-top: 1px solid color-mix(in srgb, var(--mjr-border) 75%, transparent);
padding-top: 4px;
@@ -1296,6 +1299,8 @@
/* Scrollbars (match ComfyUI feel) */
.mjr-assets-manager .mjr-am-grid-scroll {
flex: 1 1 auto;
min-width: 0;
min-height: 0;
height: 100%;
overflow: auto;
@@ -3714,6 +3719,9 @@
/* Sidebar (can be left or right) */
.mjr-sidebar {
position: relative;
z-index: 3;
flex: 0 0 auto;
transition: all 0.3s ease;
}
+12 -1
View File
@@ -30,6 +30,10 @@ const props = defineProps({
type: Boolean,
default: false,
},
size: {
type: [Number, String],
default: 16,
},
});
const emit = defineEmits(["update:modelValue", "rating-change"]);
@@ -51,6 +55,13 @@ const starColor = (starValue) => (starValue <= displayRating.value ? "#FFD45A" :
const starTransform = (starValue) => (starValue <= displayRating.value ? "scale(1.1)" : "scale(1)");
const starSize = computed(() => {
const raw = props.size;
if (typeof raw === "number") return `${Math.max(8, raw)}px`;
const text = String(raw || "").trim();
return text || "16px";
});
const retryDelay = (attemptIndex) => Math.min(100 * 2 ** Math.max(0, attemptIndex - 1), 2000);
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -264,7 +275,7 @@ watch(
:style="{
color: starColor(i),
transform: starTransform(i),
fontSize: '16px',
fontSize: starSize,
background: 'none',
border: 'none',
cursor: disabled ? 'default' : 'pointer',
+8 -36
View File
@@ -35,6 +35,7 @@ const dataListRef = ref(null);
const similarBtnRef = ref(null);
const semanticBtnRef = ref(null);
const dataListId = createUniqueId("mjr-search-autocomplete-", 8);
const METADATA_SEARCH_MODE = "AND";
const resolveDomElement = (value) => value?.$el || value || null;
const getSearchInputEl = () => resolveDomElement(searchInputRef.value);
@@ -42,7 +43,7 @@ const getSearchInputEl = () => resolveDomElement(searchInputRef.value);
// Local semantic mode state (synced with settings, not persisted)
const semanticMode = ref(false);
const semanticEnabled = ref(true);
const metadataMode = ref(String(panelStore.metadataSearchMode || "AND").toUpperCase() === "OR" ? "OR" : "AND");
const metadataMode = ref(METADATA_SEARCH_MODE);
let metadataKeysCache = null;
let metadataKeysPromise = null;
@@ -90,12 +91,6 @@ const semanticBtnStyle = computed(() => {
};
});
const metadataModeTitle = computed(() =>
metadataMode.value === "OR"
? t("search.metadataModeOr", "Metadata prefix terms match any tag (OR)")
: t("search.metadataModeAnd", "Metadata prefix terms must all match (AND)"),
);
const syncSemanticDataset = () => {
try {
const input = getSearchInputEl();
@@ -133,24 +128,12 @@ const toggleSemanticMode = () => {
const handleSearchInput = async (e) => {
const value = e.target.value || "";
panelStore.searchQuery = value;
panelStore.metadataSearchMode = metadataMode.value;
panelStore.metadataSearchMode = METADATA_SEARCH_MODE;
syncSemanticDataset();
emitSearchChange({ query: value });
await handleAutocomplete();
};
const toggleMetadataMode = () => {
metadataMode.value = metadataMode.value === "OR" ? "AND" : "OR";
panelStore.metadataSearchMode = metadataMode.value;
syncSemanticDataset();
emitSearchChange({ metadataMode: metadataMode.value });
try {
getSearchInputEl()?.dispatchEvent?.(new Event("input", { bubbles: true }));
} catch (e) {
console.debug?.(e);
}
};
const getLastSearchToken = (value) => {
const parts = String(value || "").split(/\s+/);
return parts[parts.length - 1] || "";
@@ -261,8 +244,10 @@ watch(semanticMode, () => {
watch(
() => panelStore.metadataSearchMode,
(newVal) => {
const next = String(newVal || "AND").toUpperCase() === "OR" ? "OR" : "AND";
if (metadataMode.value !== next) metadataMode.value = next;
if (String(newVal || "").toUpperCase() !== METADATA_SEARCH_MODE) {
panelStore.metadataSearchMode = METADATA_SEARCH_MODE;
}
if (metadataMode.value !== METADATA_SEARCH_MODE) metadataMode.value = METADATA_SEARCH_MODE;
syncSemanticDataset();
},
);
@@ -273,6 +258,7 @@ onMounted(() => {
if (input) {
input.value = panelStore.searchQuery || "";
}
panelStore.metadataSearchMode = METADATA_SEARCH_MODE;
} catch (e) {
console.debug?.(e);
}
@@ -319,20 +305,6 @@ defineExpose({
<datalist ref="dataListRef" :id="dataListId" />
</div>
<div class="mjr-am-search-tools">
<div class="mjr-popover-anchor">
<MButton
type="button"
class="mjr-icon-btn mjr-ai-control"
severity="secondary"
text
rounded
:title="metadataModeTitle"
:aria-label="metadataModeTitle"
@click="toggleMetadataMode"
>
<span style="font-size:10px;font-weight:700;letter-spacing:0">{{ metadataMode }}</span>
</MButton>
</div>
<div class="mjr-popover-anchor">
<MButton
ref="semanticBtnRef"
@@ -84,6 +84,7 @@ function handleClose() {
<RatingEditor
:asset="asset"
:model-value="rating"
:size="60"
/>
</div>
<div style="display:flex;flex-direction:column;gap:4px">
@@ -215,6 +215,20 @@ function modelGroupStyle(accentColor, emphasis = true) {
};
}
function modelBranchAccent(branch) {
return "#CE6DE0";
}
function samplingBranchAccent(branch) {
const key = String(branch?.key || "").toLowerCase();
if (key.includes("high")) return "#FFC107";
if (key.includes("low")) return "#FFB300";
if (key.includes("base")) return "#FF9800";
if (key.includes("upscale") || key.includes("refine")) return "#FFCA28";
if (key.includes("pass")) return "#FDD835";
return "#FF9800";
}
function modelGroupAccent(key) {
if (key === "high_noise") return "#FF7043";
if (key === "low_noise") return "#29B6F6";
@@ -559,47 +573,6 @@ watch(
</div>
</template>
<div
v-if="modelBranches.length"
:style="boxStyle('#9C27B0', { emphasis: true, startAlpha: 0.18, endAlpha: 0.10 })"
>
<div style="font-size:11px;font-weight:600;color:#9C27B0;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:10px">
{{ t("sidebar.generation.models", "Models") }}
</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit, minmax(190px, 1fr));gap:10px">
<div
v-for="branch in modelBranches"
:key="`models-top-${branch.key}`"
:style="modelGroupStyle(branch.accent, true)"
>
<div :style="{ fontSize: '10px', fontWeight: '800', color: branch.accent, letterSpacing: '0.6px', textTransform: 'uppercase' }">
{{ branch.label }}
</div>
<div
v-for="field in branch.modelFields"
:key="`model-top-${branch.key}-${field.label}`"
style="display:flex;flex-direction:column;gap:3px;min-width:0"
>
<span style="font-size:10px;font-weight:700;color:rgba(255,255,255,0.58);text-transform:uppercase;letter-spacing:0.4px">{{ field.label }}</span>
<span style="font-size:12px;color:var(--fg-color, rgba(255,255,255,0.96));line-height:1.35;word-break:break-word;cursor:pointer" @click="copyText(field.value, $event.currentTarget)">
{{ field.value || '-' }}
</span>
</div>
<div v-if="branch.loras.length" style="display:flex;flex-direction:column;gap:5px">
<span style="font-size:10px;font-weight:700;color:rgba(255,255,255,0.58);text-transform:uppercase;letter-spacing:0.4px">LoRA</span>
<span
v-for="(lora, index) in branch.loras"
:key="`model-top-${branch.key}-lora-${index}`"
style="font-size:12px;color:var(--fg-color, rgba(255,255,255,0.92));line-height:1.35;word-break:break-word;padding:6px 8px;border-radius:6px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);cursor:pointer"
@click="copyText(lora, $event.currentTarget)"
>
{{ lora }}
</span>
</div>
</div>
</div>
</div>
<div
v-if="isCaptionSectionVisible"
style="background:linear-gradient(135deg, rgba(0, 188, 212, 0.14) 0%, rgba(33, 150, 243, 0.10) 100%);border:1px solid rgba(0, 188, 212, 0.40);border-radius:6px;padding:12px;display:flex;flex-direction:column;gap:10px"
@@ -713,6 +686,47 @@ watch(
</div>
</div>
<div
v-if="modelBranches.length"
:style="boxStyle('#9C27B0', { emphasis: true, startAlpha: 0.18, endAlpha: 0.10 })"
>
<div style="font-size:11px;font-weight:600;color:#9C27B0;text-transform:uppercase;letter-spacing:0.5px;margin-bottom:10px">
{{ t("sidebar.generation.models", "Models") }}
</div>
<div style="display:grid;grid-template-columns:repeat(auto-fit, minmax(190px, 1fr));gap:10px">
<div
v-for="branch in modelBranches"
:key="`models-top-${branch.key}`"
:style="modelGroupStyle(modelBranchAccent(branch), true)"
>
<div :style="{ fontSize: '10px', fontWeight: '800', color: modelBranchAccent(branch), letterSpacing: '0.6px', textTransform: 'uppercase' }">
{{ branch.label }}
</div>
<div
v-for="field in branch.modelFields"
:key="`model-top-${branch.key}-${field.label}`"
style="display:flex;flex-direction:column;gap:3px;min-width:0"
>
<span style="font-size:10px;font-weight:700;color:rgba(255,255,255,0.58);text-transform:uppercase;letter-spacing:0.4px">{{ field.label }}</span>
<span style="font-size:12px;color:var(--fg-color, rgba(255,255,255,0.96));line-height:1.35;word-break:break-word;cursor:pointer" @click="copyText(field.value, $event.currentTarget)">
{{ field.value || '-' }}
</span>
</div>
<div v-if="branch.loras.length" style="display:flex;flex-direction:column;gap:5px">
<span style="font-size:10px;font-weight:700;color:rgba(255,255,255,0.58);text-transform:uppercase;letter-spacing:0.4px">LoRA</span>
<span
v-for="(lora, index) in branch.loras"
:key="`model-top-${branch.key}-lora-${index}`"
style="font-size:12px;color:var(--fg-color, rgba(255,255,255,0.92));line-height:1.35;word-break:break-word;padding:6px 8px;border-radius:6px;background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.08);cursor:pointer"
@click="copyText(lora, $event.currentTarget)"
>
{{ lora }}
</span>
</div>
</div>
</div>
</div>
<div
v-if="sectionState.lyrics"
:style="boxStyle('#00BCD4', { emphasis: false })"
@@ -767,9 +781,9 @@ watch(
<div
v-for="branch in samplingBranches"
:key="`sampling-${branch.key}`"
:style="modelGroupStyle(branch.accent, true)"
:style="modelGroupStyle(samplingBranchAccent(branch), true)"
>
<div :style="{ fontSize: '10px', fontWeight: '800', color: branch.accent, letterSpacing: '0.6px', textTransform: 'uppercase' }">
<div :style="{ fontSize: '10px', fontWeight: '800', color: samplingBranchAccent(branch), letterSpacing: '0.6px', textTransform: 'uppercase' }">
{{ branch.label }}
</div>
<div
@@ -302,6 +302,8 @@ const currentCategory = computed(() => {
const categorySegments = computed(() =>
currentCategory.value ? currentCategory.value.split(/[\\/]+/).filter(Boolean) : [],
);
const categoryDisplayName = computed(() => categorySegments.value.at(-1) || currentCategory.value || "Root");
const categoryDisplaySegments = computed(() => categorySegments.value.slice(-1));
function workflowNodeLabel(node, index) {
const id = node?.id ?? node?.key ?? index + 1;
@@ -884,21 +886,25 @@ onBeforeUnmount(() => {
</div>
<div style="margin-bottom:12px;padding:10px;border-radius:10px;background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.10)">
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px">
<div>
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px;min-width:0">
<div style="min-width:0;flex:1 1 auto">
<div style="font-size:10px;font-weight:700;color:rgba(255,255,255,0.55);text-transform:uppercase;letter-spacing:0.4px">Category</div>
<div style="font-size:12px;color:rgba(255,255,255,0.8);margin-top:2px">
{{ currentCategory || 'Root' }}
<div
:title="currentCategory || 'Root'"
style="font-size:12px;color:rgba(255,255,255,0.8);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%"
>
{{ categoryDisplayName }}
</div>
</div>
<div
v-if="categorySegments.length"
style="display:flex;flex-wrap:wrap;gap:4px;justify-content:flex-end"
v-if="categoryDisplaySegments.length"
:title="currentCategory"
style="display:flex;flex-wrap:wrap;gap:4px;justify-content:flex-end;min-width:0;max-width:45%"
>
<span
v-for="segment in categorySegments"
v-for="segment in categoryDisplaySegments"
:key="segment"
style="padding:3px 7px;border-radius:999px;background:rgba(33,150,243,0.12);border:1px solid rgba(33,150,243,0.22);font-size:10px;font-weight:700;color:#90CAF9;text-transform:uppercase;letter-spacing:0.3px"
style="padding:3px 7px;border-radius:999px;background:rgba(33,150,243,0.12);border:1px solid rgba(33,150,243,0.22);font-size:10px;font-weight:700;color:#90CAF9;text-transform:uppercase;letter-spacing:0.3px;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
>
{{ segment }}
</span>