mirror of
https://github.com/open-webui/open-webui
synced 2025-01-01 08:42:14 +00:00
refac: sidebar styling
This commit is contained in:
parent
24c3f7a664
commit
27d2fbbe33
@ -1,4 +1,4 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import { getContext, createEventDispatcher, onMount, onDestroy } from 'svelte';
|
import { getContext, createEventDispatcher, onMount, onDestroy } from 'svelte';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
@ -7,12 +7,15 @@
|
|||||||
import ChevronDown from '../icons/ChevronDown.svelte';
|
import ChevronDown from '../icons/ChevronDown.svelte';
|
||||||
import ChevronRight from '../icons/ChevronRight.svelte';
|
import ChevronRight from '../icons/ChevronRight.svelte';
|
||||||
import Collapsible from './Collapsible.svelte';
|
import Collapsible from './Collapsible.svelte';
|
||||||
|
import Tooltip from './Tooltip.svelte';
|
||||||
|
import Plus from '../icons/Plus.svelte';
|
||||||
|
|
||||||
export let open = true;
|
export let open = true;
|
||||||
|
|
||||||
export let id = '';
|
export let id = '';
|
||||||
export let name = '';
|
export let name = '';
|
||||||
export let collapsible = true;
|
export let collapsible = true;
|
||||||
|
export let onCreateFolder: null | Function = null;
|
||||||
|
|
||||||
export let className = '';
|
export let className = '';
|
||||||
|
|
||||||
@ -113,10 +116,10 @@
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div class="w-full">
|
<div
|
||||||
<button
|
class="w-full group rounded-md relative flex items-center justify-between hover:bg-gray-100 dark:hover:bg-gray-900 text-gray-500 dark:text-gray-500 transition"
|
||||||
class="w-full py-1.5 px-2 rounded-md flex items-center gap-1.5 text-xs text-gray-500 dark:text-gray-500 font-medium hover:bg-gray-100 dark:hover:bg-gray-900 transition"
|
>
|
||||||
>
|
<button class="w-full py-1.5 pl-2 flex items-center gap-1.5 text-xs font-medium">
|
||||||
<div class="text-gray-300 dark:text-gray-600">
|
<div class="text-gray-300 dark:text-gray-600">
|
||||||
{#if open}
|
{#if open}
|
||||||
<ChevronDown className=" size-3" strokeWidth="2.5" />
|
<ChevronDown className=" size-3" strokeWidth="2.5" />
|
||||||
@ -129,6 +132,25 @@
|
|||||||
{name}
|
{name}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{#if onCreateFolder}
|
||||||
|
<button
|
||||||
|
class="absolute z-10 right-2 self-center flex items-center"
|
||||||
|
on:pointerup={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onCreateFolder();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip content={$i18n.t('New folder')}>
|
||||||
|
<button
|
||||||
|
class="p-0.5 dark:hover:bg-gray-850 rounded-lg touch-auto"
|
||||||
|
on:click={(e) => {}}
|
||||||
|
>
|
||||||
|
<Plus className=" size-3" strokeWidth="2.5" />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div slot="content" class="w-full">
|
<div slot="content" class="w-full">
|
||||||
|
@ -519,19 +519,6 @@
|
|||||||
on:input={searchDebounceHandler}
|
on:input={searchDebounceHandler}
|
||||||
placeholder={$i18n.t('Search')}
|
placeholder={$i18n.t('Search')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="absolute z-40 right-3.5 top-1">
|
|
||||||
<Tooltip content={$i18n.t('New folder')}>
|
|
||||||
<button
|
|
||||||
class="p-1 rounded-lg bg-gray-50 hover:bg-gray-100 dark:bg-gray-950 dark:hover:bg-gray-900 transition"
|
|
||||||
on:click={() => {
|
|
||||||
createFolder();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Plus />
|
|
||||||
</button>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@ -539,156 +526,157 @@
|
|||||||
? 'opacity-20'
|
? 'opacity-20'
|
||||||
: ''}"
|
: ''}"
|
||||||
>
|
>
|
||||||
{#if $temporaryChatEnabled}
|
<Folder
|
||||||
<div class="absolute z-40 w-full h-full flex justify-center"></div>
|
collapsible={!search}
|
||||||
{/if}
|
className="px-2 mt-0.5"
|
||||||
|
name={$i18n.t('Chats')}
|
||||||
|
onCreateFolder={createFolder}
|
||||||
|
on:import={(e) => {
|
||||||
|
importChatHandler(e.detail);
|
||||||
|
}}
|
||||||
|
on:drop={async (e) => {
|
||||||
|
const { type, id, item } = e.detail;
|
||||||
|
|
||||||
{#if !search && $pinnedChats.length > 0}
|
if (type === 'chat') {
|
||||||
<div class="flex flex-col space-y-1 rounded-xl">
|
let chat = await getChatById(localStorage.token, id).catch((error) => {
|
||||||
<Folder
|
return null;
|
||||||
className="px-2"
|
});
|
||||||
bind:open={showPinnedChat}
|
if (!chat && item) {
|
||||||
on:change={(e) => {
|
chat = await importChat(localStorage.token, item.chat, item?.meta ?? {});
|
||||||
localStorage.setItem('showPinnedChat', e.detail);
|
}
|
||||||
console.log(e.detail);
|
|
||||||
}}
|
|
||||||
on:import={(e) => {
|
|
||||||
importChatHandler(e.detail, true);
|
|
||||||
}}
|
|
||||||
on:drop={async (e) => {
|
|
||||||
const { type, id, item } = e.detail;
|
|
||||||
|
|
||||||
if (type === 'chat') {
|
if (chat) {
|
||||||
let chat = await getChatById(localStorage.token, id).catch((error) => {
|
console.log(chat);
|
||||||
return null;
|
if (chat.folder_id) {
|
||||||
});
|
const res = await updateChatFolderIdById(localStorage.token, chat.id, null).catch(
|
||||||
if (!chat && item) {
|
(error) => {
|
||||||
chat = await importChat(localStorage.token, item.chat, item?.meta ?? {});
|
toast.error(error);
|
||||||
}
|
return null;
|
||||||
|
|
||||||
if (chat) {
|
|
||||||
console.log(chat);
|
|
||||||
if (chat.folder_id) {
|
|
||||||
const res = await updateChatFolderIdById(
|
|
||||||
localStorage.token,
|
|
||||||
chat.id,
|
|
||||||
null
|
|
||||||
).catch((error) => {
|
|
||||||
toast.error(error);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
if (!chat.pinned) {
|
}
|
||||||
const res = await toggleChatPinnedStatusById(localStorage.token, chat.id);
|
|
||||||
}
|
if (chat.pinned) {
|
||||||
|
const res = await toggleChatPinnedStatusById(localStorage.token, chat, id);
|
||||||
initChatList();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}}
|
|
||||||
name={$i18n.t('Pinned')}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="ml-3 pl-1 mt-[1px] flex flex-col overflow-y-auto scrollbar-hidden border-s border-gray-100 dark:border-gray-900"
|
|
||||||
>
|
|
||||||
{#each $pinnedChats as chat, idx}
|
|
||||||
<ChatItem
|
|
||||||
className=""
|
|
||||||
id={chat.id}
|
|
||||||
title={chat.title}
|
|
||||||
{shiftKey}
|
|
||||||
selected={selectedChatId === chat.id}
|
|
||||||
on:select={() => {
|
|
||||||
selectedChatId = chat.id;
|
|
||||||
}}
|
|
||||||
on:unselect={() => {
|
|
||||||
selectedChatId = null;
|
|
||||||
}}
|
|
||||||
on:change={async () => {
|
|
||||||
initChatList();
|
|
||||||
}}
|
|
||||||
on:tag={(e) => {
|
|
||||||
const { type, name } = e.detail;
|
|
||||||
tagEventHandler(type, name, chat.id);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</Folder>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class=" flex-1 flex flex-col overflow-y-auto scrollbar-hidden">
|
|
||||||
{#if !search && folders}
|
|
||||||
<Folders
|
|
||||||
{folders}
|
|
||||||
on:import={(e) => {
|
|
||||||
const { folderId, items } = e.detail;
|
|
||||||
importChatHandler(items, false, folderId);
|
|
||||||
}}
|
|
||||||
on:update={async (e) => {
|
|
||||||
initChatList();
|
initChatList();
|
||||||
}}
|
}
|
||||||
on:change={async () => {
|
} else if (type === 'folder') {
|
||||||
initChatList();
|
if (folders[id].parent_id === null) {
|
||||||
}}
|
return;
|
||||||
/>
|
}
|
||||||
|
|
||||||
|
const res = await updateFolderParentIdById(localStorage.token, id, null).catch(
|
||||||
|
(error) => {
|
||||||
|
toast.error(error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
await initFolders();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#if $temporaryChatEnabled}
|
||||||
|
<div class="absolute z-40 w-full h-full flex justify-center"></div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Folder
|
{#if !search && $pinnedChats.length > 0}
|
||||||
collapsible={!search}
|
<div class="flex flex-col space-y-1 rounded-xl">
|
||||||
className="px-2 mt-0.5"
|
<Folder
|
||||||
name={$i18n.t('All chats')}
|
className="pl-1"
|
||||||
on:import={(e) => {
|
bind:open={showPinnedChat}
|
||||||
importChatHandler(e.detail);
|
on:change={(e) => {
|
||||||
}}
|
localStorage.setItem('showPinnedChat', e.detail);
|
||||||
on:drop={async (e) => {
|
console.log(e.detail);
|
||||||
const { type, id, item } = e.detail;
|
}}
|
||||||
|
on:import={(e) => {
|
||||||
|
importChatHandler(e.detail, true);
|
||||||
|
}}
|
||||||
|
on:drop={async (e) => {
|
||||||
|
const { type, id, item } = e.detail;
|
||||||
|
|
||||||
if (type === 'chat') {
|
if (type === 'chat') {
|
||||||
let chat = await getChatById(localStorage.token, id).catch((error) => {
|
let chat = await getChatById(localStorage.token, id).catch((error) => {
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
if (!chat && item) {
|
if (!chat && item) {
|
||||||
chat = await importChat(localStorage.token, item.chat, item?.meta ?? {});
|
chat = await importChat(localStorage.token, item.chat, item?.meta ?? {});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chat) {
|
if (chat) {
|
||||||
console.log(chat);
|
console.log(chat);
|
||||||
if (chat.folder_id) {
|
if (chat.folder_id) {
|
||||||
const res = await updateChatFolderIdById(localStorage.token, chat.id, null).catch(
|
const res = await updateChatFolderIdById(
|
||||||
(error) => {
|
localStorage.token,
|
||||||
toast.error(error);
|
chat.id,
|
||||||
return null;
|
null
|
||||||
|
).catch((error) => {
|
||||||
|
toast.error(error);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chat.pinned) {
|
if (!chat.pinned) {
|
||||||
const res = await toggleChatPinnedStatusById(localStorage.token, chat, id);
|
const res = await toggleChatPinnedStatusById(localStorage.token, chat.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initChatList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
name={$i18n.t('Pinned')}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="ml-3 pl-1 mt-[1px] flex flex-col overflow-y-auto scrollbar-hidden border-s border-gray-100 dark:border-gray-900"
|
||||||
|
>
|
||||||
|
{#each $pinnedChats as chat, idx}
|
||||||
|
<ChatItem
|
||||||
|
className=""
|
||||||
|
id={chat.id}
|
||||||
|
title={chat.title}
|
||||||
|
{shiftKey}
|
||||||
|
selected={selectedChatId === chat.id}
|
||||||
|
on:select={() => {
|
||||||
|
selectedChatId = chat.id;
|
||||||
|
}}
|
||||||
|
on:unselect={() => {
|
||||||
|
selectedChatId = null;
|
||||||
|
}}
|
||||||
|
on:change={async () => {
|
||||||
|
initChatList();
|
||||||
|
}}
|
||||||
|
on:tag={(e) => {
|
||||||
|
const { type, name } = e.detail;
|
||||||
|
tagEventHandler(type, name, chat.id);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Folder>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class=" flex-1 flex flex-col overflow-y-auto scrollbar-hidden">
|
||||||
|
{#if !search && folders}
|
||||||
|
<Folders
|
||||||
|
{folders}
|
||||||
|
on:import={(e) => {
|
||||||
|
const { folderId, items } = e.detail;
|
||||||
|
importChatHandler(items, false, folderId);
|
||||||
|
}}
|
||||||
|
on:update={async (e) => {
|
||||||
initChatList();
|
initChatList();
|
||||||
}
|
}}
|
||||||
} else if (type === 'folder') {
|
on:change={async () => {
|
||||||
if (folders[id].parent_id === null) {
|
initChatList();
|
||||||
return;
|
}}
|
||||||
}
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
const res = await updateFolderParentIdById(localStorage.token, id, null).catch(
|
|
||||||
(error) => {
|
|
||||||
toast.error(error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (res) {
|
|
||||||
await initFolders();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div class="pt-1.5">
|
<div class="pt-1.5">
|
||||||
{#if $chats}
|
{#if $chats}
|
||||||
{#each $chats as chat, idx}
|
{#each $chats as chat, idx}
|
||||||
@ -701,23 +689,23 @@
|
|||||||
>
|
>
|
||||||
{$i18n.t(chat.time_range)}
|
{$i18n.t(chat.time_range)}
|
||||||
<!-- localisation keys for time_range to be recognized from the i18next parser (so they don't get automatically removed):
|
<!-- localisation keys for time_range to be recognized from the i18next parser (so they don't get automatically removed):
|
||||||
{$i18n.t('Today')}
|
{$i18n.t('Today')}
|
||||||
{$i18n.t('Yesterday')}
|
{$i18n.t('Yesterday')}
|
||||||
{$i18n.t('Previous 7 days')}
|
{$i18n.t('Previous 7 days')}
|
||||||
{$i18n.t('Previous 30 days')}
|
{$i18n.t('Previous 30 days')}
|
||||||
{$i18n.t('January')}
|
{$i18n.t('January')}
|
||||||
{$i18n.t('February')}
|
{$i18n.t('February')}
|
||||||
{$i18n.t('March')}
|
{$i18n.t('March')}
|
||||||
{$i18n.t('April')}
|
{$i18n.t('April')}
|
||||||
{$i18n.t('May')}
|
{$i18n.t('May')}
|
||||||
{$i18n.t('June')}
|
{$i18n.t('June')}
|
||||||
{$i18n.t('July')}
|
{$i18n.t('July')}
|
||||||
{$i18n.t('August')}
|
{$i18n.t('August')}
|
||||||
{$i18n.t('September')}
|
{$i18n.t('September')}
|
||||||
{$i18n.t('October')}
|
{$i18n.t('October')}
|
||||||
{$i18n.t('November')}
|
{$i18n.t('November')}
|
||||||
{$i18n.t('December')}
|
{$i18n.t('December')}
|
||||||
-->
|
-->
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@ -766,8 +754,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</Folder>
|
</div>
|
||||||
</div>
|
</Folder>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="px-2">
|
<div class="px-2">
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
{#each folderList as folderId (folderId)}
|
{#each folderList as folderId (folderId)}
|
||||||
<RecursiveFolder
|
<RecursiveFolder
|
||||||
className="px-2"
|
className="pl-1"
|
||||||
{folders}
|
{folders}
|
||||||
{folderId}
|
{folderId}
|
||||||
on:import={(e) => {
|
on:import={(e) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user