enh: update channel

This commit is contained in:
Timothy Jaeryang Baek 2024-12-22 23:09:51 -07:00
parent e9194d9524
commit 7ad8918cd9
9 changed files with 192 additions and 35 deletions

View File

@ -78,6 +78,31 @@ async def get_channel_by_id(id: str, user=Depends(get_verified_user)):
return ChannelModel(**channel.model_dump())
############################
# UpdateChannelById
############################
@router.post("/{id}/update", response_model=Optional[ChannelModel])
async def update_channel_by_id(
id: str, form_data: ChannelForm, user=Depends(get_admin_user)
):
channel = Channels.get_channel_by_id(id)
if not channel:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND
)
try:
channel = Channels.update_channel_by_id(id, form_data)
return ChannelModel(**channel.model_dump())
except Exception as e:
log.exception(e)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.DEFAULT()
)
############################
# GetChannelMessages
############################

View File

@ -102,6 +102,38 @@ export const getChannelById = async (token: string = '', channel_id: string) =>
return res;
}
export const updateChannelById = async (token: string = '', channel_id: string, channel: ChannelForm) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/channels/${channel_id}/update`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
authorization: `Bearer ${token}`
},
body: JSON.stringify({ ...channel })
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err.detail;
console.log(err);
return null;
});
if (error) {
throw error;
}
return res;
}
export const getChannelMessages = async (token: string = '', channel_id: string, page: number = 1) => {
let error = null;

View File

@ -65,7 +65,7 @@
{($settings?.widescreenMode ?? null) ? 'max-w-full' : 'max-w-5xl'} mx-auto"
>
{#if channel}
<div class="flex flex-col py-1 gap-1.5 py-5">
<div class="flex flex-col gap-1.5 py-5">
<div class="text-2xl font-medium capitalize">{channel.name}</div>
<div class=" text-gray-500">
@ -76,7 +76,7 @@
</div>
</div>
{:else}
<div class="flex justify-center py-1 text-xs items-center gap-2 py-5">
<div class="flex justify-center text-xs items-center gap-2 py-5">
<div class=" ">Start of the channel</div>
</div>
{/if}

View File

@ -70,7 +70,7 @@
{#if message.created_at}
<span
class=" self-center invisible group-hover:visible text-gray-400 text-xs font-medium capitalize ml-0.5 -mt-0.5"
class=" self-center invisible group-hover:visible text-gray-400 text-xs font-medium first-letter:capitalize ml-0.5 -mt-0.5"
>
{formatDate(message.created_at / 1000000)}
</span>

View File

@ -37,7 +37,6 @@
const confirmHandler = async () => {
show = false;
await onConfirm();
dispatch('confirm', inputValue);
};
@ -47,11 +46,15 @@
});
$: if (mounted) {
if (show) {
if (show && modalElement) {
document.body.appendChild(modalElement);
window.addEventListener('keydown', handleKeyDown);
document.body.style.overflow = 'hidden';
} else {
} else if (modalElement) {
window.removeEventListener('keydown', handleKeyDown);
document.body.removeChild(modalElement);
document.body.style.overflow = 'unset';
}
}
@ -62,7 +65,7 @@
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
bind:this={modalElement}
class=" fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full h-screen max-h-[100dvh] flex justify-center z-[99999] overflow-hidden overscroll-contain"
class=" fixed top-0 right-0 left-0 bottom-0 bg-black/60 w-full h-screen max-h-[100dvh] flex justify-center z-[99999999] overflow-hidden overscroll-contain"
in:fade={{ duration: 10 }}
on:mousedown={() => {
show = false;

View File

@ -0,0 +1,12 @@
<script lang="ts">
export let className = 'w-4 h-4';
export let strokeWidth = '1.5';
</script>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class={className}>
<path
fill-rule="evenodd"
d="M6.455 1.45A.5.5 0 0 1 6.952 1h2.096a.5.5 0 0 1 .497.45l.186 1.858a4.996 4.996 0 0 1 1.466.848l1.703-.769a.5.5 0 0 1 .639.206l1.047 1.814a.5.5 0 0 1-.14.656l-1.517 1.09a5.026 5.026 0 0 1 0 1.694l1.516 1.09a.5.5 0 0 1 .141.656l-1.047 1.814a.5.5 0 0 1-.639.206l-1.703-.768c-.433.36-.928.649-1.466.847l-.186 1.858a.5.5 0 0 1-.497.45H6.952a.5.5 0 0 1-.497-.45l-.186-1.858a4.993 4.993 0 0 1-1.466-.848l-1.703.769a.5.5 0 0 1-.639-.206l-1.047-1.814a.5.5 0 0 1 .14-.656l1.517-1.09a5.033 5.033 0 0 1 0-1.694l-1.516-1.09a.5.5 0 0 1-.141-.656L2.46 3.593a.5.5 0 0 1 .639-.206l1.703.769c.433-.36.928-.65 1.466-.848l.186-1.858Zm-.177 7.567-.022-.037a2 2 0 0 1 3.466-1.997l.022.037a2 2 0 0 1-3.466 1.997Z"
clip-rule="evenodd"
/>
</svg>

View File

@ -53,7 +53,7 @@
import Tooltip from '../common/Tooltip.svelte';
import Folders from './Sidebar/Folders.svelte';
import { getChannels, createNewChannel } from '$lib/apis/channels';
import CreateChannelModal from './Sidebar/CreateChannelModal.svelte';
import ChannelModal from './Sidebar/ChannelModal.svelte';
import ChannelItem from './Sidebar/ChannelItem.svelte';
import PencilSquare from '../icons/PencilSquare.svelte';
@ -403,10 +403,21 @@
}}
/>
<CreateChannelModal
<ChannelModal
bind:show={showCreateChannel}
onChange={async () => {
await initChannels();
onSubmit={async ({ name, access_control }) => {
const res = await createNewChannel(localStorage.token, {
name: name,
access_control: access_control
}).catch((error) => {
toast.error(error);
return null;
});
if (res) {
await initChannels();
showCreateChannel = false;
}
}}
/>
@ -642,7 +653,12 @@
onAddLabel={$i18n.t('Create Channel')}
>
{#each $channels as channel}
<ChannelItem id={channel.id} name={channel.name} />
<ChannelItem
{channel}
onUpdate={async () => {
await initChannels();
}}
/>
{/each}
</Folder>
{/if}

View File

@ -1,33 +1,55 @@
<script lang="ts">
import { toast } from 'svelte-sonner';
import { onMount, getContext, createEventDispatcher, tick, onDestroy } from 'svelte';
import { onMount, getContext, tick, onDestroy } from 'svelte';
const i18n = getContext('i18n');
const dispatch = createEventDispatcher();
import { page } from '$app/stores';
import { mobile, showSidebar, user } from '$lib/stores';
import EllipsisHorizontal from '$lib/components/icons/EllipsisHorizontal.svelte';
import { updateChannelById } from '$lib/apis/channels';
import Cog6 from '$lib/components/icons/Cog6.svelte';
import ChannelModal from './ChannelModal.svelte';
export let onUpdate: Function = () => {};
export let className = '';
export let channel;
export let id;
export let name;
let showEditChannelModal = false;
let itemElement;
</script>
<ChannelModal
bind:show={showEditChannelModal}
{channel}
edit={true}
onSubmit={async ({ name, access_control }) => {
const res = await updateChannelById(localStorage.token, channel.id, {
name,
access_control
}).catch((error) => {
toast.error(error.message);
});
if (res) {
toast.success('Channel updated successfully');
}
onUpdate();
}}
/>
<div
bind:this={itemElement}
class=" w-full {className} rounded-lg flex relative group hover:bg-gray-100 dark:hover:bg-gray-900 {$page
.url.pathname === `/channels/${id}`
.url.pathname === `/channels/${channel.id}`
? 'bg-gray-100 dark:bg-gray-900'
: ''} px-2.5 py-1"
>
<a
class=" w-full flex justify-between"
href="/channels/{id}"
href="/channels/{channel.id}"
on:click={() => {
if ($mobile) {
showSidebar.set(false);
@ -50,7 +72,7 @@
</svg>
<div class=" text-left self-center overflow-hidden w-full line-clamp-1">
{name}
{channel.name}
</div>
</div>
</a>
@ -60,10 +82,12 @@
class="absolute z-10 right-2 invisible group-hover:visible self-center flex items-center dark:text-gray-300"
on:pointerup={(e) => {
e.stopPropagation();
showEditChannelModal = true;
}}
>
<button class="p-0.5 dark:hover:bg-gray-850 rounded-lg touch-auto" on:click={(e) => {}}>
<EllipsisHorizontal className="size-4" strokeWidth="2.5" />
<Cog6 className="size-3.5" />
</button>
</button>
{/if}

View File

@ -1,15 +1,19 @@
<script lang="ts">
import { getContext, createEventDispatcher } from 'svelte';
import { getContext, createEventDispatcher, onMount } from 'svelte';
import { createNewChannel } from '$lib/apis/channels';
import Modal from '$lib/components/common/Modal.svelte';
import AccessControl from '$lib/components/workspace/common/AccessControl.svelte';
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
import { toast } from 'svelte-sonner';
const i18n = getContext('i18n');
export let show = false;
export let onChange: Function = () => {};
export let onSubmit: Function = () => {};
export let channel = null;
export let edit = false;
let name = '';
let accessControl = null;
@ -22,25 +26,41 @@
const submitHandler = async () => {
loading = true;
const res = await createNewChannel(localStorage.token, {
await onSubmit({
name: name.replace(/\s/g, '-'),
access_control: accessControl
}).catch((error) => {
toast.error(error);
return null;
});
onChange();
show = false;
loading = false;
};
const init = () => {
name = channel.name;
accessControl = channel.access_control;
};
$: if (channel) {
init();
}
let showDeleteConfirmDialog = false;
const deleteHandler = async () => {
showDeleteConfirmDialog = false;
show = false;
};
</script>
<Modal size="sm" bind:show>
<div>
<div class=" flex justify-between dark:text-gray-300 px-5 pt-4 pb-1">
<div class=" text-lg font-medium self-center">{$i18n.t('Create Channel')}</div>
<div class=" text-lg font-medium self-center">
{#if edit}
{$i18n.t('Edit Channel')}
{:else}
{$i18n.t('Create Channel')}
{/if}
</div>
<button
class="self-center"
on:click={() => {
@ -93,6 +113,18 @@
</div>
<div class="flex justify-end pt-3 text-sm font-medium gap-1.5">
{#if edit}
<button
class="px-3.5 py-1.5 text-sm font-medium dark:bg-black dark:hover:bg-black/90 dark:text-white bg-white text-black hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center"
type="button"
on:click={() => {
showDeleteConfirmDialog = true;
}}
>
{$i18n.t('Delete')}
</button>
{/if}
<button
class="px-3.5 py-1.5 text-sm font-medium bg-black hover:bg-gray-950 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full flex flex-row space-x-1 items-center {loading
? ' cursor-not-allowed'
@ -100,7 +132,11 @@
type="submit"
disabled={loading}
>
{$i18n.t('Create')}
{#if edit}
{$i18n.t('Update')}
{:else}
{$i18n.t('Create')}
{/if}
{#if loading}
<div class="ml-2 self-center">
@ -136,3 +172,12 @@
</div>
</div>
</Modal>
<DeleteConfirmDialog
bind:show={showDeleteConfirmDialog}
message={$i18n.t('Are you sure you want to delete this channel?')}
confirmLabel={$i18n.t('Delete')}
on:confirm={() => {
deleteHandler();
}}
/>