Lay groundwork for dynamic modals

This commit is contained in:
Webifi 2023-06-04 20:47:08 -05:00
parent 49c7570952
commit 39233051da
8 changed files with 141 additions and 68 deletions

10
package-lock.json generated
View File

@ -34,6 +34,7 @@
"svelte-highlight": "^7.2.1", "svelte-highlight": "^7.2.1",
"svelte-local-storage-store": "^0.4.0", "svelte-local-storage-store": "^0.4.0",
"svelte-markdown": "^0.2.3", "svelte-markdown": "^0.2.3",
"svelte-modals": "^1.2.1",
"svelte-spa-router": "^3.3.0", "svelte-spa-router": "^3.3.0",
"svelte-use-click-outside": "^1.0.0", "svelte-use-click-outside": "^1.0.0",
"tslib": "^2.5.0", "tslib": "^2.5.0",
@ -4186,6 +4187,15 @@
"integrity": "sha512-vSSbKZFbNktrQ15v7o1EaH78EbWV+sPQbPjHG+Cp8CaNcPFUEfjZ0Iml/V0bFDwsTlYe8o6XC5Hfdp91cqPV2g==", "integrity": "sha512-vSSbKZFbNktrQ15v7o1EaH78EbWV+sPQbPjHG+Cp8CaNcPFUEfjZ0Iml/V0bFDwsTlYe8o6XC5Hfdp91cqPV2g==",
"dev": true "dev": true
}, },
"node_modules/svelte-modals": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/svelte-modals/-/svelte-modals-1.2.1.tgz",
"integrity": "sha512-7MEKUx5wb5YppkXWFGeRlYM5FMGEnpix39u9Y6GtCNTMXRDZ7DB2Z50IYLMRTMW5lOsCdtJgFbB0E3iZMKmsAA==",
"dev": true,
"peerDependencies": {
"svelte": "^3.0.0"
}
},
"node_modules/svelte-preprocess": { "node_modules/svelte-preprocess": {
"version": "5.0.3", "version": "5.0.3",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.3.tgz", "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.0.3.tgz",

View File

@ -40,6 +40,7 @@
"svelte-highlight": "^7.2.1", "svelte-highlight": "^7.2.1",
"svelte-local-storage-store": "^0.4.0", "svelte-local-storage-store": "^0.4.0",
"svelte-markdown": "^0.2.3", "svelte-markdown": "^0.2.3",
"svelte-modals": "^1.2.1",
"svelte-spa-router": "^3.3.0", "svelte-spa-router": "^3.3.0",
"svelte-use-click-outside": "^1.0.0", "svelte-use-click-outside": "^1.0.0",
"tslib": "^2.5.0", "tslib": "^2.5.0",

View File

@ -8,6 +8,7 @@
import Chat from './lib/Chat.svelte' import Chat from './lib/Chat.svelte'
import NewChat from './lib/NewChat.svelte' import NewChat from './lib/NewChat.svelte'
import { chatsStorage, apiKeyStorage } from './lib/Storage.svelte' import { chatsStorage, apiKeyStorage } from './lib/Storage.svelte'
import { Modals, closeModal } from 'svelte-modals'
// The definition of the routes with some conditions // The definition of the routes with some conditions
const routes = { const routes = {
@ -31,7 +32,6 @@
} }
</script> </script>
<Navbar /> <Navbar />
<div class="side-bar-column"> <div class="side-bar-column">
<Sidebar /> <Sidebar />
@ -41,3 +41,23 @@
<Router {routes} on:conditionsFailed={() => replace('/')}/> <Router {routes} on:conditionsFailed={() => replace('/')}/>
{/key} {/key}
</div> </div>
<Modals>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
slot="backdrop"
class="backdrop"
on:click={closeModal}
/>
</Modals>
<style>
.backdrop {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
background: transparent
}
</style>

View File

@ -60,7 +60,7 @@ html {
} }
@media screen and (min-width: 1024px) { @media screen and (min-width: 1024px) {
.modal-card { .modal-card.wide {
width: 960px !important; width: 960px !important;
} }
} }

View File

@ -40,6 +40,8 @@
import { autoGrowInputOnEvent, sizeTextElements } from './Util.svelte' import { autoGrowInputOnEvent, sizeTextElements } from './Util.svelte'
import ChatSettingsModal from './ChatSettingsModal.svelte' import ChatSettingsModal from './ChatSettingsModal.svelte'
import Footer from './Footer.svelte' import Footer from './Footer.svelte'
import { openModal } from 'svelte-modals'
import PromptInput from './PromptInput.svelte'
// This makes it possible to override the OpenAI API base URL in the .env file // This makes it possible to override the OpenAI API base URL in the .env file
const apiBase = import.meta.env.VITE_API_BASE || 'https://api.openai.com' const apiBase = import.meta.env.VITE_API_BASE || 'https://api.openai.com'
@ -51,7 +53,7 @@
let updatingMessage: string = '' let updatingMessage: string = ''
let input: HTMLTextAreaElement let input: HTMLTextAreaElement
// let settings: HTMLDivElement // let settings: HTMLDivElement
let chatNameSettings: HTMLFormElement // let chatNameSettings: HTMLFormElement
let recognition: any = null let recognition: any = null
let recording = false let recording = false
@ -469,24 +471,16 @@
} }
} }
const showChatNameSettings = () => { function promptRename () {
chatNameSettings.classList.add('is-active'); openModal(PromptInput, {
(chatNameSettings.querySelector('#settings-chat-name') as HTMLInputElement).focus(); title: 'Enter Name for Chat',
(chatNameSettings.querySelector('#settings-chat-name') as HTMLInputElement).select() label: 'Name',
value: chat.name,
onSubmit: (value) => {
chat.name = (value || '').trim() || chat.name
$checkStateChange++
} }
})
const saveChatNameSettings = () => {
const newChatName = (chatNameSettings.querySelector('#settings-chat-name') as HTMLInputElement).value
// save if changed
if (newChatName && newChatName !== chat.name) {
chat.name = newChatName
chatsStorage.set($chatsStorage)
}
closeChatNameSettings()
}
const closeChatNameSettings = () => {
chatNameSettings.classList.remove('is-active')
} }
const recordToggle = () => { const recordToggle = () => {
@ -503,48 +497,13 @@
<ChatSettingsModal chatId={chatId} bind:show={showSettingsModal} /> <ChatSettingsModal chatId={chatId} bind:show={showSettingsModal} />
<!-- rename modal -->
<form class="modal" bind:this={chatNameSettings} on:submit={saveChatNameSettings}>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="modal-background" on:click={closeChatNameSettings} />
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">Enter a new name for this chat</p>
</header>
<section class="modal-card-body">
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label" for="settings-chat-name">New name:</label>
</div>
<div class="field-body">
<div class="field">
<input
class="input"
type="text"
id="settings-chat-name"
value={chat.name}
/>
</div>
</div>
</div>
</section>
<footer class="modal-card-foot">
<input type="submit" class="button is-info" value="Save" />
<button class="button" on:click={closeChatNameSettings}>Cancel</button>
</footer>
</div>
</form>
<!-- end -->
<div class="chat-content"> <div class="chat-content">
<nav class="level chat-header"> <nav class="level chat-header">
<div class="level-left"> <div class="level-left">
<div class="level-item"> <div class="level-item">
<p class="subtitle is-5"> <p class="subtitle is-5">
<span>{chat.name || `Chat ${chat.id}`}</span> <span>{chat.name || `Chat ${chat.id}`}</span>
<a href={'#'} class="greyscale ml-2 is-hidden has-text-weight-bold editbutton" title="Rename chat" on:click|preventDefault={showChatNameSettings}><Fa icon={faPenToSquare} /></a> <a href={'#'} class="greyscale ml-2 is-hidden has-text-weight-bold editbutton" title="Rename chat" on:click|preventDefault={promptRename}><Fa icon={faPenToSquare} /></a>
<a href={'#'} class="greyscale ml-2 is-hidden has-text-weight-bold editbutton" title="Suggest a chat name" on:click|preventDefault={suggestName}><Fa icon={faLightbulb} /></a> <a href={'#'} class="greyscale ml-2 is-hidden has-text-weight-bold editbutton" title="Suggest a chat name" on:click|preventDefault={suggestName}><Fa icon={faLightbulb} /></a>
<!-- <a href={'#'} class="greyscale ml-2 is-hidden has-text-weight-bold editbutton" title="Copy this chat" on:click|preventDefault={() => { copyChat(chatId) }}><Fa icon={faClone} /></a> --> <!-- <a href={'#'} class="greyscale ml-2 is-hidden has-text-weight-bold editbutton" title="Copy this chat" on:click|preventDefault={() => { copyChat(chatId) }}><Fa icon={faClone} /></a> -->
<!-- <a href={'#'} class="greyscale ml-2 is-hidden has-text-weight-bold editbutton" title="Delete this chat" on:click|preventDefault={deleteChat}><Fa icon={faTrash} /></a> --> <!-- <a href={'#'} class="greyscale ml-2 is-hidden has-text-weight-bold editbutton" title="Delete this chat" on:click|preventDefault={deleteChat}><Fa icon={faTrash} /></a> -->
@ -617,11 +576,3 @@
{/each} {/each}
</div> </div>
</Footer> </Footer>
<svelte:window
on:keydown={(event) => {
if (event.key === 'Escape') {
closeChatNameSettings()
}
}}
/>

View File

@ -255,7 +255,7 @@
<!-- svelte-ignore a11y-click-events-have-key-events --> <!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="modal" class:is-active={showSettingsModal}> <div class="modal" class:is-active={showSettingsModal}>
<div class="modal-background" on:click={closeSettings} /> <div class="modal-background" on:click={closeSettings} />
<div class="modal-card" on:click={() => { showProfileMenu = false }}> <div class="modal-card wide" on:click={() => { showProfileMenu = false }}>
<header class="modal-card-head"> <header class="modal-card-head">
<p class="modal-card-title">Chat Settings</p> <p class="modal-card-title">Chat Settings</p>
<button class="delete" aria-label="close" on:click={closeSettings}></button> <button class="delete" aria-label="close" on:click={closeSettings}></button>

View File

@ -0,0 +1,90 @@
<script lang="ts">
import Fa from 'svelte-fa/src/fa.svelte'
import { closeModal } from 'svelte-modals'
import {
faExclamation
} from '@fortawesome/free-solid-svg-icons/index'
import { onMount } from 'svelte'
import { v4 as uuidv4 } from 'uuid'
// provided by <Modals />
export let isOpen:boolean
export let title:string
export let label:string
export let value:any
export let onSubmit:(value:any)=>boolean|void
export let onClose:(()=>boolean|void) = () => {}
export let saveButton:string = 'Save'
export let closeButton:string = 'Cancel'
export let placeholder:string = ''
export let error:string = ''
export let icon:Fa|null = null
const id = uuidv4()
onMount(async () => {
const input = document.getElementById(id)
input && input.focus()
})
const doClose = () => {
if (!onClose || !onClose()) closeModal()
}
const doSubmit = (value) => {
onSubmit(value)
closeModal()
}
</script>
{#if isOpen}
<div class="modal is-active">
<form action="{'#'}" on:submit|preventDefault={() => { doSubmit(value) }}>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="modal-background" on:click={doClose} />
<div class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">{title}</p>
<button class="delete" aria-label="close" type="button" on:click={doClose}></button>
</header>
<section class="modal-card-body">
<div class="field">
<label class="label" for="text-input">{label}</label>
<div class="control" class:has-icons-left={icon} class:has-icons-right={error} >
<input id={id} name="text-input" class="input" class:is-danger={error} type="text" placeholder={placeholder} bind:value={value}>
{#if icon}
<span class="icon is-small is-left">
<Fa icon={icon}/>
</span>
{/if}
{#if error}
<span class="icon is-small is-right">
<Fa icon={faExclamation}/>
<i class="fas fa-exclamation-triangle"></i>
</span>
{/if}
</div>
{#if error}
<p class="help is-danger">{error}</p>
{/if}
</div>
</section>
<footer class="modal-card-foot">
<input type="submit" class="button is-info" value="{saveButton}" />
<button class="button" type="button" on:click={doClose} >{closeButton}</button>
</footer>
</div>
</form>
</div>
{/if}
<!--
<svelte:window
on:keydown={(event) => {
if (event.key === 'Escape') {
closeModal()
}
}}
/> -->

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { params } from 'svelte-spa-router' import { params } from 'svelte-spa-router'
import ChatMenuItem from './ChatMenuItem.svelte' import ChatMenuItem from './ChatMenuItem.svelte'
import { apiKeyStorage, chatsStorage, pinMainMenu } from './Storage.svelte' import { apiKeyStorage, chatsStorage, pinMainMenu, checkStateChange } from './Storage.svelte'
import Fa from 'svelte-fa/src/fa.svelte' import Fa from 'svelte-fa/src/fa.svelte'
import { faSquarePlus, faKey } from '@fortawesome/free-solid-svg-icons/index' import { faSquarePlus, faKey } from '@fortawesome/free-solid-svg-icons/index'
import ChatOptionMenu from './ChatOptionMenu.svelte' import ChatOptionMenu from './ChatOptionMenu.svelte'
@ -9,7 +9,6 @@
import { clickOutside } from 'svelte-use-click-outside' import { clickOutside } from 'svelte-use-click-outside'
$: sortedChats = $chatsStorage.sort((a, b) => b.id - a.id) $: sortedChats = $chatsStorage.sort((a, b) => b.id - a.id)
$: activeChatId = $params && $params.chatId ? parseInt($params.chatId) : undefined $: activeChatId = $params && $params.chatId ? parseInt($params.chatId) : undefined
</script> </script>
@ -29,9 +28,11 @@
{#if sortedChats.length === 0} {#if sortedChats.length === 0}
<li><a href={'#'} class="is-disabled">No chats yet...</a></li> <li><a href={'#'} class="is-disabled">No chats yet...</a></li>
{:else} {:else}
{#key $checkStateChange}
{#each sortedChats as chat, i} {#each sortedChats as chat, i}
<ChatMenuItem activeChatId={activeChatId} chat={chat} prevChat={sortedChats[i - 1]} nextChat={sortedChats[i + 1]} /> <ChatMenuItem activeChatId={activeChatId} chat={chat} prevChat={sortedChats[i - 1]} nextChat={sortedChats[i + 1]} />
{/each} {/each}
{/key}
{/if} {/if}
</ul> </ul>
<!-- <p class="menu-label">Actions</p> --> <!-- <p class="menu-label">Actions</p> -->