Skip to content

Commit

Permalink
Merge pull request stitionai#157 from AndrewBastin/refactor/ui
Browse files Browse the repository at this point in the history
refactor: move dropdown logic into component
  • Loading branch information
mufeedvh authored Mar 29, 2024
2 parents 2aaac6a + 7a12efc commit b72bd7f
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 124 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
Expand Down
148 changes: 26 additions & 122 deletions ui/src/lib/components/ControlPanel.svelte
Original file line number Diff line number Diff line change
@@ -1,159 +1,63 @@
<script>
import { onMount } from 'svelte';
import { selectedProject, selectedModel, projectList, modelList, internet } from '$lib/store';
import { createProject, fetchProjectList, getTokenUsage } from '$lib/api';
import { createProject, fetchProjectList, getTokenUsage } from "$lib/api";
import Dropdown from "./ui/Dropdown.svelte";
let tokenUsage = 0;
async function updateTokenUsage() {
tokenUsage = await getTokenUsage();
}
function selectProject(project) {
$selectedProject = project;
}
function selectModel(model) {
$selectedModel = `${model[1]}`;
}
async function createNewProject() {
const projectName = prompt('Enter the project name:');
if (projectName) {
await createProject(projectName);
await fetchProjectList();
selectProject(projectName);
}
}
function closeDropdowns(event) {
const projectDropdown = document.getElementById('project-dropdown');
const modelDropdown = document.getElementById('model-dropdown');
const projectButton = document.getElementById('project-button');
const modelButton = document.getElementById('model-button');
if (!projectDropdown.contains(event.target) && !projectButton.contains(event.target)) {
projectDropdown.classList.add('hidden');
}
if (!modelDropdown.contains(event.target) && !modelButton.contains(event.target)) {
modelDropdown.classList.add('hidden');
selectedProject.set(projectName);
}
}
onMount(() => {
setInterval(updateTokenUsage, 1000);
document.getElementById('project-button').addEventListener('click', function () {
const dropdown = document.getElementById('project-dropdown');
dropdown.classList.toggle('hidden');
});
document.getElementById('model-button').addEventListener('click', function () {
const dropdown = document.getElementById('model-dropdown');
dropdown.classList.toggle('hidden');
});
document.addEventListener('click', closeDropdowns);
return () => {
document.removeEventListener('click', closeDropdowns);
};
});
</script>

<div class="control-panel bg-slate-900 border border-indigo-700 rounded">
<div class="dropdown-menu relative inline-block">
<button
type="button"
class="inline-flex justify-center w-full gap-x-1.5 rounded-md bg-slate-900 px-3 py-2 text-sm font-semibold text-white shadow-sm ring-1 ring-inset ring-indigo-700 hover:bg-slate-800"
id="project-button"
aria-expanded="true"
aria-haspopup="true"
>
<span id="selected-project">{$selectedProject}</span>
<svg class="-mr-1 h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path
fill-rule="evenodd"
d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
clip-rule="evenodd"
/>
</svg>
</button>
<div
id="project-dropdown"
class="absolute left-0 z-10 mt-2 w-full origin-top-left rounded-md bg-slate-800 shadow-lg ring-1 ring-indigo-700 ring-opacity-5 focus:outline-none hidden"
role="menu"
aria-orientation="vertical"
aria-labelledby="project-button"
tabindex="-1"
>
<div class="py-1" role="none">
<button class="text-white block px-4 py-2 text-sm hover:bg-slate-700" on:click={createNewProject}>
+ Create new project
</button>
{#if $projectList.length > 0}
{#each $projectList as project}
<button class="text-white block px-4 py-2 text-sm hover:bg-slate-700" on:click={() => selectProject(project)}>
{project}
</button>
{/each}
{/if}
</div>
<Dropdown options={Object.fromEntries($projectList.map((x) => [x, x]))} label="Select Project" bind:selection={$selectedProject}>
<div slot="prefix-entries" let:closeDropdown={close}>
<button
class="text-white block px-4 py-2 text-sm hover:bg-slate-700 w-full text-left overflow-clip"
on:click|preventDefault={() => {
createNewProject();
close();
}}
>
+ Create New Project
</button>
</div>
</div>

<div class="right-controls" style="display: flex; align-items: center; gap: 20px">
</Dropdown>
<div
class="right-controls"
style="display: flex; align-items: center; gap: 20px"
>
<div class="flex items-center space-x-2">
<span>Internet:</span>
<div id="internet-status" class="internet-status" class:online={$internet} class:offline={!$internet} />
<span id="internet-status-text" />
</div>
<div class="flex items-center space-x-2">
<span>Token Usage:</span>
<span id="token-count" class="token-count-animation">{tokenUsage}</span>
<span class="token-count-animation">{tokenUsage}</span>
</div>
<div class="relative inline-block text-left">
<div>
<button
type="button"
class="inline-flex w-full justify-center gap-x-1.5 rounded-md bg-slate-900 px-3 py-2 text-sm font-semibold text-white shadow-sm ring-1 ring-inset ring-indigo-700 hover:bg-slate-800"
id="model-button"
aria-expanded="true"
aria-haspopup="true"
>
<span id="selected-model">{$selectedModel}</span>
<svg class="-mr-1 h-5 w-5 text-gray-400" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path
fill-rule="evenodd"
d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
clip-rule="evenodd"
/>
</svg>
</button>
</div>

<div
id="model-dropdown"
class="absolute right-0 z-10 mt-2 w-full origin-top-right rounded-md bg-slate-800 shadow-lg ring-1 ring-indigo-700 ring-opacity-5 focus:outline-none hidden"
role="menu"
aria-orientation="vertical"
aria-labelledby="model-button"
tabindex="-1"
>
<div class="py-1" role="none">
{#if $modelList.length > 0}
{#each $modelList as model}
<button
class="text-white block px-4 py-2 text-sm hover:bg-slate-700"
on:click={() => selectModel(model)}
>
{model[0]} ({model[1]})
</button>
{/each}
{/if}
</div>
</div>
<Dropdown
options={Object.fromEntries($modelList.map(([name, id]) => [id, `${name} (${id})`]))}
label="Select Model"
bind:selection={$selectedModel}
/>
</div>
</div>
</div>
Expand Down Expand Up @@ -203,4 +107,4 @@
border-right: 1px solid #4b5563;
padding-right: 20px;
}
</style>
</style>
115 changes: 115 additions & 0 deletions ui/src/lib/components/ui/Dropdown.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<script>
/**
* @type {string} The text shown when no option is selected
*/
export let label;
/**
* @type {string | null | undefined} The ID of the selected entry or null
*/
export let selection = undefined;
/**
* @type {Record<string, string>} An object of options with the key as the ID and the value as the label
*/
export let options;
/** @type {HTMLElement | null} **/
let dropdown = null;
let open = false;
/**
* @param {string} id ID of the selected element
*/
function selectElement(id) {
selection = id;
open = false;
}
/**
* Svelte Action function to handle clicks outside of the given element
* @param {HTMLElement} element The dropdown element
* @param {() => void} callbackFunction The function to call when a click outside is detected
*/
function onClickOutside(element, callbackFunction) {
function onClick(event) {
if (!element.contains(event.target)) {
callbackFunction();
}
}
let registered = false;
setTimeout(() => {
document.body.addEventListener('click', onClick);
registered = true;
}, 100)
return {
update(newCallbackFunction) {
callbackFunction = newCallbackFunction;
},
destroy() {
if (registered) {
document.body.removeEventListener('click', onClick);
registered = false;
}
}
}
}
// This needs to be a function so it can passed as slot prop
function closeDropdown() {
open = false;
}
</script>

<div class="dropdown-menu relative inline-block">
<button
type="button"
class="inline-flex justify-center w-full gap-x-1.5 rounded-md bg-slate-900 px-3 py-2 text-sm font-semibold text-white shadow-sm ring-1 ring-inset ring-indigo-700 hover:bg-slate-800"
aria-expanded="true"
aria-haspopup="true"
on:click={() => open = !open}
>
<span>
{selection ? options[selection] : label}
</span>

<svg
class="-mr-1 h-5 w-5 text-gray-400"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
clip-rule="evenodd"
/>
</svg>
</button>
{#if open}
<div
bind:this={dropdown}
class="absolute left-0 z-10 w-fit origin-top-left rounded-md bg-slate-800 shadow-lg ring-1 ring-indigo-700 ring-opacity-5 focus:outline-none"
role="menu"
tabindex="-1"
use:onClickOutside={() => { open = false; }}
>
<div role="none">
<slot name="prefix-entries" {closeDropdown} />

{#each Object.entries(options) as [id, label]}
<button
class="text-white block px-4 py-2 text-sm hover:bg-slate-700 w-full text-left overflow-clip"
on:click|preventDefault={() => selectElement(id)}
>
{label}
</button>
{/each}
</div>
</div>
{/if}
</div>
2 changes: 1 addition & 1 deletion ui/src/lib/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ selectedModel.subscribe((value) => {
if (typeof window !== 'undefined' && window.localStorage) {
localStorage.setItem('selectedModel', value);
}
});
});

0 comments on commit b72bd7f

Please sign in to comment.