Application sidebar
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { ListIcon as List, ImageIcon as Image, SettingsIcon as Settings } from "svelte-feather-icons";
|
||||
import { onMount } from "svelte";
|
||||
import { get, writable, type Writable } from "svelte/store";
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes';
|
||||
@@ -17,6 +18,8 @@
|
||||
import LightboxModal from "./LightboxModal.svelte";
|
||||
import ComfyQueue from "./ComfyQueue.svelte";
|
||||
import ComfyProperties from "./ComfyProperties.svelte";
|
||||
import Sidebar from "./Sidebar.svelte";
|
||||
import SidebarItem from "./SidebarItem.svelte";
|
||||
import queueState from "$lib/stores/queueState";
|
||||
import ComfyUnlockUIButton from "./ComfyUnlockUIButton.svelte";
|
||||
import ComfyGraphView from "./ComfyGraphView.svelte";
|
||||
@@ -26,6 +29,7 @@
|
||||
import ComfyBoxStdPrompt from "$lib/ComfyBoxStdPrompt";
|
||||
import A1111PromptDisplay from "./A1111PromptDisplay.svelte";
|
||||
import type { A1111ParsedInfotext } from "$lib/parseA1111";
|
||||
import { TabItem, Tabs } from "@gradio/tabs";
|
||||
|
||||
export let app: ComfyApp = undefined;
|
||||
let alreadySetup: Writable<boolean> = writable(false);
|
||||
@@ -200,6 +204,8 @@
|
||||
let showModal: boolean = false;
|
||||
|
||||
$: showModal = $a1111Prompt != null
|
||||
|
||||
let selectedTab
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@@ -222,6 +228,9 @@
|
||||
|
||||
<div id="main" class:dark={uiTheme === "gradio-dark"}>
|
||||
<div id="container" bind:this={containerElem}>
|
||||
<Sidebar selected="generate">
|
||||
<SidebarItem id="generate" name="Generate" icon={Image}>
|
||||
<div id="comfy-content">
|
||||
<Splitpanes theme="comfy" on:resize={refreshView}>
|
||||
<Pane bind:size={propsSidebarSize}>
|
||||
<div class="sidebar-wrapper pane-wrapper">
|
||||
@@ -244,8 +253,26 @@
|
||||
</div>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
<div id="workflow-tabs">
|
||||
<div class="workflow-tab selected">
|
||||
txt2img
|
||||
<!-- <Image /> -->
|
||||
</div>
|
||||
<div class="workflow-tab">
|
||||
img2img
|
||||
<!-- <Image /> -->
|
||||
</div>
|
||||
<div class="workflow-tab">
|
||||
asdflkj
|
||||
<!-- <Image /> -->
|
||||
</div>
|
||||
<div class="workflow-tab">
|
||||
asdkajw
|
||||
<!-- <Image /> -->
|
||||
</div>
|
||||
</div>
|
||||
<div id="bottombar">
|
||||
<div class="bottombar-content">
|
||||
<div class="left">
|
||||
{#if $layoutState.attrs.queuePromptButtonName != ""}
|
||||
<Button variant="primary" disabled={!$alreadySetup} on:click={queuePrompt}>
|
||||
@@ -303,43 +330,136 @@
|
||||
<ComfyUnlockUIButton bind:toggled={$uiState.uiUnlocked} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SidebarItem>
|
||||
<SidebarItem id="settings" name="Settings" icon={Settings}>
|
||||
</SidebarItem>
|
||||
</Sidebar>
|
||||
</div>
|
||||
<LightboxModal />
|
||||
<input bind:this={fileInput} id="comfy-file-input" type="file" accept=".json" on:change={loadWorkflow} />
|
||||
</div>
|
||||
<SvelteToast options={toastOptions} />
|
||||
|
||||
<style lang="scss">
|
||||
$bottom-bar-height: 70px;
|
||||
$top-bar-height: 3.5rem;
|
||||
$workflow-tabs-height: 2.5rem;
|
||||
$bottom-bar-height: 5rem;
|
||||
|
||||
#container {
|
||||
height: calc(100vh - $bottom-bar-height);
|
||||
height: 100vh;
|
||||
max-width: 100vw;
|
||||
display: grid;
|
||||
display: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#comfy-content {
|
||||
grid-area: content;
|
||||
height: 100vh;
|
||||
height: calc(100vh - $bottom-bar-height - $workflow-tabs-height);
|
||||
}
|
||||
|
||||
#bottombar {
|
||||
padding-top: 0.5em;
|
||||
#workflow-tabs {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: var(--layout-gap);
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
margin-top: auto;
|
||||
overflow-x: auto;
|
||||
}
|
||||
/*
|
||||
#topbar {
|
||||
background: var(--neutral-900);
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
float: left;
|
||||
position: sticky;
|
||||
top: 0px;
|
||||
padding: 0;
|
||||
width: 4rem;
|
||||
|
||||
.topbar-button {
|
||||
background: var(--neutral-800);
|
||||
color: var(--neutral-500);
|
||||
padding: 0.5rem;
|
||||
border-right: 3px solid transparent;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
&:last-child {
|
||||
border-right: 1px solid var(--neutral-600);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--neutral-700);
|
||||
color: var(--neutral-300);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: var(--neutral-700);
|
||||
color: var(--neutral-300);
|
||||
border-right-color: var(--primary-500);
|
||||
}
|
||||
}
|
||||
} */
|
||||
|
||||
#workflow-tabs {
|
||||
background: var(--neutral-800);
|
||||
.workflow-tab {
|
||||
background: var(--neutral-800);
|
||||
color: var(--neutral-500);
|
||||
padding: 0.5rem 1rem;
|
||||
border-top: 3px solid transparent;
|
||||
border-left: 1px solid var(--neutral-600);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
&:last-child {
|
||||
border-right: 1px solid var(--neutral-600);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--neutral-700);
|
||||
color: var(--neutral-300);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: var(--neutral-700);
|
||||
color: var(--neutral-300);
|
||||
border-top-color: var(--primary-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#bottombar {
|
||||
background: var(--neutral-900);
|
||||
border-left: 2px solid var(--neutral-700);
|
||||
display: flex;
|
||||
width: 100%;
|
||||
gap: var(--layout-gap);
|
||||
overflow-x: hidden;
|
||||
flex-wrap: nowrap;
|
||||
height: $bottom-bar-height;
|
||||
|
||||
> .bottombar-content {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
margin: auto 0;
|
||||
padding-left: 1rem;
|
||||
|
||||
> .left {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
> .right {
|
||||
margin-left: auto
|
||||
margin-left: auto;
|
||||
margin-right: 1rem;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
154
src/lib/components/Sidebar.svelte
Normal file
154
src/lib/components/Sidebar.svelte
Normal file
@@ -0,0 +1,154 @@
|
||||
<script context="module">
|
||||
export const TABS = {};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { setContext, createEventDispatcher } from "svelte";
|
||||
import { writable } from "svelte/store";
|
||||
import type { SelectData } from "@gradio/utils";
|
||||
import { SvelteComponentDev } from "svelte/internal";
|
||||
|
||||
interface Tab {
|
||||
name: string;
|
||||
id: object;
|
||||
icon: typeof SvelteComponentDev | null;
|
||||
}
|
||||
|
||||
export let visible: boolean = true;
|
||||
export let elem_id: string = "id";
|
||||
export let elem_classes: Array<string> = [];
|
||||
export let selected: number | string | object;
|
||||
|
||||
let tabs: Array<Tab> = [];
|
||||
|
||||
const selected_tab = writable<false | object | number | string>(false);
|
||||
const selected_tab_index = writable<number>(0);
|
||||
const dispatch = createEventDispatcher<{
|
||||
change: undefined;
|
||||
select: SelectData;
|
||||
}>();
|
||||
|
||||
setContext(TABS, {
|
||||
register_tab: (tab: Tab) => {
|
||||
tabs.push({ name: tab.name, id: tab.id, icon: tab.icon });
|
||||
selected_tab.update((current) => current ?? tab.id);
|
||||
tabs = tabs;
|
||||
return tabs.length - 1;
|
||||
},
|
||||
unregister_tab: (tab: Tab) => {
|
||||
const i = tabs.findIndex((t) => t.id === tab.id);
|
||||
tabs.splice(i, 1);
|
||||
selected_tab.update((current) =>
|
||||
current === tab.id ? tabs[i]?.id || tabs[tabs.length - 1]?.id : current
|
||||
);
|
||||
},
|
||||
selected_tab,
|
||||
selected_tab_index
|
||||
});
|
||||
|
||||
function change_tab(id: object | string | number) {
|
||||
selected = id;
|
||||
$selected_tab = id;
|
||||
$selected_tab_index = tabs.findIndex((t) => t.id === id);
|
||||
dispatch("change");
|
||||
}
|
||||
|
||||
$: selected !== null && change_tab(selected);
|
||||
</script>
|
||||
|
||||
<div class="sidebar {elem_classes.join(' ')}" class:hide={!visible} id={elem_id}>
|
||||
<div class="sidebar-nav scroll-hide">
|
||||
{#each tabs as t, i (t.id)}
|
||||
{#if t.id === $selected_tab}
|
||||
<button class="selected">
|
||||
{#if t.icon !== null}
|
||||
<svelte:component this={t.icon} size="100%" strokeWidth={1.5} />
|
||||
{:else}
|
||||
{t.name}
|
||||
{/if}
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
on:click={() => {
|
||||
change_tab(t.id);
|
||||
dispatch("select", { value: t.name, index: i });
|
||||
}}
|
||||
>
|
||||
{#if t.icon !== null}
|
||||
<svelte:component this={t.icon} size="100%" strokeWidth={1.5} />
|
||||
{:else}
|
||||
{t.name}
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
{/each}
|
||||
<div class="sidebar-rest"/>
|
||||
</div>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.sidebar {
|
||||
background: var(--neutral-900);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
float: left;
|
||||
top: 0px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-nav, .sidebar-rest {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-wrap: wrap;
|
||||
white-space: nowrap;
|
||||
width: 4rem;
|
||||
height: 100%;
|
||||
background: var(--neutral-800);
|
||||
|
||||
> button {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
padding: 0.5rem;
|
||||
color: var(--neutral-600);
|
||||
border-right: 3px solid transparent;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
background: var(--neutral-700);
|
||||
color: var(--neutral-500);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: var(--neutral-700);
|
||||
color: var(--neutral-300);
|
||||
border-right-color: var(--primary-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-rest {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.bar {
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
background: var(--background-fill-primary);
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
content: "";
|
||||
}
|
||||
</style>
|
||||
45
src/lib/components/SidebarItem.svelte
Normal file
45
src/lib/components/SidebarItem.svelte
Normal file
@@ -0,0 +1,45 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount, createEventDispatcher, tick } from "svelte";
|
||||
import { TABS } from "./Sidebar.svelte";
|
||||
import Column from "$lib/components/gradio/app/Column.svelte"
|
||||
import type { SelectData } from "@gradio/utils";
|
||||
import { SvelteComponentDev } from "svelte/internal";
|
||||
|
||||
export let elem_id: string = "";
|
||||
export let elem_classes: Array<string> = [];
|
||||
export let name: string;
|
||||
export let id: string | number | object = {};
|
||||
export let icon: typeof SvelteComponentDev | null = null;
|
||||
|
||||
const dispatch = createEventDispatcher<{ select: SelectData }>();
|
||||
|
||||
const { register_tab, unregister_tab, selected_tab, selected_tab_index } =
|
||||
getContext(TABS) as any;
|
||||
|
||||
let tab_index = register_tab({ name, id, icon });
|
||||
|
||||
onMount(() => {
|
||||
return () => unregister_tab({ name, id, icon });
|
||||
});
|
||||
|
||||
$: $selected_tab_index === tab_index &&
|
||||
tick().then(() => dispatch("select", { value: name, index: tab_index }));
|
||||
</script>
|
||||
|
||||
<div
|
||||
id={elem_id}
|
||||
class="tabitem {elem_classes.join(' ')}"
|
||||
style:display={$selected_tab === id ? "block" : "none"}
|
||||
>
|
||||
<Column>
|
||||
<slot />
|
||||
</Column>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
display: flex;
|
||||
position: relative;
|
||||
width: calc(100% - 4rem);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user