Collapsable+resizable sidebar. Standard view header component.
This commit is contained in:
1 parent
69e8614a98
commit
9b30473c40
9 files changed
+202
-41
No files matched your search
@@ -1,5 +1,5 @@
|
||||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
:root {
|
||||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
--vt-c-white: #ffffff;
|
||||
--vt-c-white-soft: #f8f8f8;
|
||||
--vt-c-white-mute: #f2f2f2;
|
||||
@@ -19,33 +19,46 @@
|
||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||
--vt-c-text-dark-1: var(--vt-c-white);
|
||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||
--vt-c-text-dark-3: rgba(180, 180, 180, 0.64);
|
||||
|
||||
/* Horde colors */
|
||||
--color-green: #00bd7e;
|
||||
--color-green-dark: #003825;
|
||||
--color-blue: #3d8de1;
|
||||
}
|
||||
|
||||
/* semantic color variables for this project */
|
||||
/* semantic color and size variables for this project */
|
||||
:root {
|
||||
--color-background: var(--vt-c-black);
|
||||
--color-background-soft: var(--vt-c-black-soft);
|
||||
--color-background-mute: var(--vt-c-black-mute);
|
||||
|
||||
--color-background-hover: var(--vt-c-black-mute);
|
||||
--color-background-active: var(--vt-c-black-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-dark-2);
|
||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-dark-1);
|
||||
--color-text: var(--vt-c-text-dark-2);
|
||||
|
||||
--color-text: var(--vt-c-text-dark-2);
|
||||
--color-text-mute: var(--vt-c-text-dark-3);
|
||||
|
||||
--color-active: var(--color-heading);
|
||||
|
||||
--border-radius: 6px;
|
||||
--section-gap: 160px;
|
||||
}
|
||||
|
||||
/* Horde Colors */
|
||||
/* Z-Index hierarchy */
|
||||
:root {
|
||||
--color-green: #00bd7e;
|
||||
--color-green-dark: #003825;
|
||||
--color-blue: #3d8de1;
|
||||
|
||||
--border-radius: 6px;
|
||||
--z-index-sidebar: 100;
|
||||
--z-index-alert: 150;
|
||||
--z-index-context-menu: 160;
|
||||
}
|
||||
|
||||
|
||||
/* Dark theme by default, Light theme via browser settings */
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--color-background: var(--vt-c-white);
|
||||
|
||||
@@ -29,4 +29,8 @@ main {
|
||||
height: 100vh;
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.with-sidebar-expanded .when-sidebar-collapsed {
|
||||
display: none;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<script setup>
|
||||
import * as ui from '../ui';
|
||||
|
||||
function sidebarExpand () {
|
||||
ui.emit('sidebar-expand');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header>
|
||||
<button class="when-sidebar-collapsed" @click="sidebarExpand()">
|
||||
>>
|
||||
</button>
|
||||
|
||||
<slot>
|
||||
<h1>Title Bar</h1>
|
||||
</slot>
|
||||
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
header {
|
||||
height: 3em;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:slotted(h1) {
|
||||
font-size: 160%;
|
||||
font-weight: bold;
|
||||
padding: 0 0.4em;
|
||||
}
|
||||
</style>
|
||||
@@ -1,19 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterLink } from 'vue-router'
|
||||
import {
|
||||
ref, computed, reactive,
|
||||
ref, computed, reactive, watch,
|
||||
onMounted, onUnmounted,
|
||||
} from "vue";
|
||||
|
||||
import * as backend from '../../backend';
|
||||
import * as ui from '../../ui';
|
||||
|
||||
const collapsed = ref(false);
|
||||
const mode = ref('chats');
|
||||
const chats = ref([]);
|
||||
const agents = ref([]);
|
||||
|
||||
const mode = ref('chats');
|
||||
const sidebarExpanded = ref(true);
|
||||
|
||||
const MIN_WIDTH = 150;
|
||||
const MAX_WIDTH = 400;
|
||||
const sidebarWidth = ref(200);
|
||||
let dragState = null;
|
||||
|
||||
watch(sidebarExpanded, newValue => {
|
||||
if (newValue) {
|
||||
document.body.classList.add('with-sidebar-expanded');
|
||||
} else {
|
||||
document.body.classList.remove('with-sidebar-expanded');
|
||||
}
|
||||
}, {immediate: true});
|
||||
|
||||
ui.listen('sidebar-expand', ev => {
|
||||
sidebarExpanded.value = true;
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
document.body.addEventListener('mousemove', documentMouseMove);
|
||||
document.body.addEventListener('mouseup', documentMouseUp);
|
||||
|
||||
chats.value = (await backend.getChats()).map(it => {
|
||||
return {id: it.id, name: it.name};
|
||||
});
|
||||
@@ -21,23 +42,61 @@
|
||||
return {id: it.id, name: it.name};
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.body.removeEventListener('mousemove', documentMouseMove);
|
||||
document.body.removeEventListener('mouseup', documentMouseUp);
|
||||
});
|
||||
|
||||
function dragBarMouseDown (event) {
|
||||
dragState = {x: event.clientX, y: event.clientY, width: sidebarWidth.value};
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
console.log("Set Drag State", dragState);
|
||||
}
|
||||
|
||||
function documentMouseMove (event) {
|
||||
if (dragState) {
|
||||
let widthDiff = event.clientX - dragState.x;
|
||||
let newWidth = dragState.width + widthDiff;
|
||||
newWidth = Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, newWidth));
|
||||
sidebarWidth.value = newWidth;
|
||||
}
|
||||
}
|
||||
|
||||
function documentMouseUp (event) {
|
||||
if (dragState) {
|
||||
dragState = null;
|
||||
}
|
||||
}
|
||||
|
||||
function sidebarStyle () {
|
||||
return {
|
||||
flex: `0 0 ${sidebarWidth.value}px`
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button class="nav-button expand-navbar" v-if="collapsed" @click="collapsed=false;">
|
||||
</button>
|
||||
|
||||
<aside class="sidebar" style="min-width:8em" v-else>
|
||||
<div class="drag-bar"></div>
|
||||
<ul class="tab-bar">
|
||||
<li @click="mode='chats'" :class="{active: mode=='chats'}">
|
||||
<i class="fa-solid fa-message"></i>
|
||||
</li>
|
||||
<li @click="mode='agents'" :class="{active: mode=='agents'}">
|
||||
<i class="fa-solid fa-user-group"></i>
|
||||
</li>
|
||||
<li><i class="fa-solid fa-angles-left"></i></li>
|
||||
</ul>
|
||||
<aside class="sidebar" :style="sidebarStyle()" v-if="sidebarExpanded">
|
||||
<div class="drag-bar" @mousedown="dragBarMouseDown($event)"></div>
|
||||
<div class="tab-bar">
|
||||
<div class="tab-group">
|
||||
<div @click="mode='chats'" class="tab" :class="{active: mode=='chats'}">
|
||||
<i class="fa-solid fa-message"></i>
|
||||
</div>
|
||||
<div @click="mode='agents'" class="tab" :class="{active: mode=='agents'}">
|
||||
<i class="fa-solid fa-user-group"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-group">
|
||||
<button class="header-button">
|
||||
<i class="fa-solid fa-angles-left" @click="sidebarExpanded=false;"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<nav v-if="mode=='chats'">
|
||||
<RouterLink v-for="it in chats" :to="'/chat/' + it.id" class="nav-item">{{it.name}}</RouterLink>
|
||||
<RouterLink to="/chat/create" class="nav-item">Create Chat</RouterLink>
|
||||
@@ -54,33 +113,43 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
z-index: var(--z-index-sidebar);
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
|
||||
.tab-bar {
|
||||
.tab-bar, .tab-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.tab-bar {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.tab-bar li {
|
||||
.tab {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
list-style-type: none;
|
||||
border-radius: var(--border-radius);
|
||||
width: 3em;
|
||||
height: 3em;
|
||||
transition: background-color 0.5s;
|
||||
width: 3em;
|
||||
|
||||
padding-top: 3px;
|
||||
color: var(--color-text-mute);
|
||||
/*flex-grow: 1;*/
|
||||
}
|
||||
|
||||
.tab-bar li:hover {
|
||||
.tab:hover {
|
||||
background-color: var(--color-background-mute);
|
||||
}
|
||||
|
||||
.tab-bar li.active {
|
||||
background-color: var(--color-background-soft);
|
||||
.tab.active {
|
||||
color: var(--color-text-heading);
|
||||
background-color: var(--color-background);
|
||||
|
||||
padding-top: 0;
|
||||
border-top: 3px solid var(--color-active);
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
@@ -118,4 +187,27 @@
|
||||
right: 0;
|
||||
cursor: ew-resize;
|
||||
}
|
||||
|
||||
/* Adapt sidebar to small screens */
|
||||
@media (max-width: 480px) {
|
||||
.sidebar {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.drag-bar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.header-button {
|
||||
height: 3em;
|
||||
width: 3em;
|
||||
border-radius: 1.5em;
|
||||
background-color: none;
|
||||
border: none;
|
||||
}
|
||||
.header-button:hover {
|
||||
background-color: var(--color-background-hover);
|
||||
}
|
||||
</style>
|
||||
@@ -4,6 +4,8 @@
|
||||
onMounted, onUnmounted,
|
||||
} from "vue";
|
||||
|
||||
import TitleBar from '../components/TitleBar.vue';
|
||||
|
||||
import * as backend from '../backend';
|
||||
import * as ui from '../ui';
|
||||
|
||||
@@ -188,7 +190,10 @@
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<h1>{{ agent.name || `Agent-${id}` }}</h1>
|
||||
<TitleBar>
|
||||
<h1>{{ agent.name || `Agent-${id}` }}</h1>
|
||||
</TitleBar>
|
||||
|
||||
<section class="top-section">
|
||||
<p style="white-space: pre-wrap; padding-bottom: 1em; border-bottom: 1px solid var(--color-border)">{{ kindInfo.description }}</p>
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
onMounted, onUnmounted,
|
||||
} from "vue";
|
||||
|
||||
import TitleBar from '../components/TitleBar.vue';
|
||||
|
||||
import * as backend from '../backend';
|
||||
import * as ui from '../ui';
|
||||
|
||||
@@ -48,7 +50,9 @@
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<h1>Create New Chat</h1>
|
||||
<TitleBar>
|
||||
<h1>Create Agent</h1>
|
||||
</TitleBar>
|
||||
|
||||
<div class="main-container">
|
||||
<input v-model="agent.name" type="text" placeholder="name">
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
onMounted, onUnmounted,
|
||||
} from "vue";
|
||||
|
||||
import TitleBar from '../components/TitleBar.vue';
|
||||
|
||||
import * as backend from '../backend';
|
||||
import * as ui from '../ui';
|
||||
|
||||
@@ -390,7 +392,9 @@
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<h1>{{ chat.name || 'Chat #' + chat.id }}</h1>
|
||||
<TitleBar>
|
||||
<h1>{{ chat.name || 'Chat #' + chat.id }}</h1>
|
||||
</TitleBar>
|
||||
|
||||
<div class="messages-container" ref="chatContainerElem">
|
||||
<div v-for="msg in chatEntries" :class="chatEntryClass(msg)" @contextmenu.prevent="chatEntryContextMenu(msg)">
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
onMounted, onUnmounted,
|
||||
} from "vue";
|
||||
|
||||
import TitleBar from '../components/TitleBar.vue';
|
||||
|
||||
import * as backend from '../backend';
|
||||
import * as ui from '../ui';
|
||||
|
||||
@@ -39,7 +41,9 @@
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<h1>Create New Chat</h1>
|
||||
<TitleBar>
|
||||
<h1>Create Chat</h1>
|
||||
</TitleBar>
|
||||
|
||||
<div class="main-container">
|
||||
<input v-model="title" type="text" placeholder="title">
|
||||
|
||||
Reference in new issue
Block a user