feat: wordlists user interface
This commit is contained in:
12
src/Settings.tsx
Normal file
12
src/Settings.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import Details from './components/ui/Details'
|
||||
import WordLists from './components/WordLists'
|
||||
|
||||
const Settings = () => {
|
||||
return (
|
||||
<Details title="Randomness Helpers">
|
||||
<WordLists />
|
||||
</Details>
|
||||
)
|
||||
}
|
||||
|
||||
export default Settings
|
||||
131
src/components/WordLists.tsx
Normal file
131
src/components/WordLists.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import Button from '@/components/ui/Button'
|
||||
import UploadButton from '@/components/ui/UploadButton'
|
||||
import '@/components/wordLists.css'
|
||||
import {
|
||||
type ChangeEventHandler,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState
|
||||
} from 'react'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import Select from '@/components/ui/Select'
|
||||
import EditWordList from '@/components/wordList/EditWordList'
|
||||
import { useStore } from '@/store'
|
||||
|
||||
const WordLists = () => {
|
||||
const wordLists = useStore(
|
||||
useShallow((state) => Object.keys(state.wordLists))
|
||||
)
|
||||
const addWordList = useStore((state) => state.addWordList)
|
||||
const removeWordList = useStore((state) => state.removeWordList)
|
||||
const [selectedWordList, setSelectedWordList] = useState('')
|
||||
|
||||
const handleUpload: ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||
(event) => {
|
||||
const file = event.target.files?.[0]
|
||||
if (file !== undefined && file.type === 'text/plain') {
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
const fileName = file.name.replace('.txt', '')
|
||||
if (wordLists.includes(fileName)) {
|
||||
alert(`A word list with the name ${fileName} already exists`)
|
||||
return
|
||||
}
|
||||
const text = e.target?.result
|
||||
if (typeof text === 'string') {
|
||||
const words = text.split('\n')
|
||||
addWordList(fileName, words)
|
||||
}
|
||||
}
|
||||
reader.readAsText(file)
|
||||
}
|
||||
},
|
||||
[addWordList, wordLists]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const selectedExist = wordLists.includes(selectedWordList)
|
||||
if (!selectedExist) {
|
||||
setSelectedWordList('')
|
||||
}
|
||||
}, [wordLists, selectedWordList])
|
||||
|
||||
return (
|
||||
<>
|
||||
<strong>Word Lists</strong>
|
||||
<div id="st-rnd-wordlist-controls">
|
||||
<Select
|
||||
id="st-rnd-wordlist-select"
|
||||
disabled={wordLists.length === 0}
|
||||
value={selectedWordList}
|
||||
onChange={(event) => {
|
||||
const value = event.target.value
|
||||
if (value !== '') {
|
||||
setSelectedWordList(value)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="" disabled>
|
||||
Select a word list
|
||||
</option>
|
||||
{wordLists.map((name) => (
|
||||
<option key={name}>{name}</option>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
<EditWordList currentWordList={selectedWordList} />
|
||||
<Button
|
||||
type="button"
|
||||
icon
|
||||
disabled={selectedWordList === ''}
|
||||
onClick={() => {
|
||||
if (selectedWordList !== '') {
|
||||
const wordList = useStore.getState().wordLists[selectedWordList]
|
||||
if (wordList === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const blob = new Blob([wordList.join('\n')], {
|
||||
type: 'text/plain'
|
||||
})
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = `${selectedWordList}.txt`
|
||||
link.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<i className="fa fa-fw fa-download"></i>
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
icon
|
||||
disabled={selectedWordList === ''}
|
||||
onClick={() => {
|
||||
if (selectedWordList !== '') {
|
||||
removeWordList(selectedWordList)
|
||||
setSelectedWordList('')
|
||||
}
|
||||
}}
|
||||
>
|
||||
<i className="fa fa-fw fa-trash"></i>
|
||||
</Button>
|
||||
</div>
|
||||
<p className="st-rnd-small-text">
|
||||
Wordlists are txt files with one (or more) word per line, are used in
|
||||
placeholders and macros.
|
||||
</p>
|
||||
<div id="st-rnd-upload-wordlist-container">
|
||||
<UploadButton
|
||||
id="upload-wordlist"
|
||||
onChange={handleUpload}
|
||||
accept=".txt"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default WordLists
|
||||
32
src/components/ui/Button.tsx
Normal file
32
src/components/ui/Button.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { ButtonHTMLAttributes, DetailedHTMLProps, FC } from 'react'
|
||||
import type { MergeOmitting } from '@/types/helpers'
|
||||
import '@/components/ui/button.css'
|
||||
|
||||
export type ButtonProps = MergeOmitting<
|
||||
DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>,
|
||||
{
|
||||
neutral?: boolean
|
||||
icon?: boolean
|
||||
}
|
||||
>
|
||||
|
||||
const Button: FC<ButtonProps> = ({
|
||||
children,
|
||||
className,
|
||||
neutral,
|
||||
icon,
|
||||
...buttonProps
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
className={`st-rnd-button${neutral === true ? ' button-neutral' : ''}${icon === true ? ' button-icon' : ''}${
|
||||
className ? ` ${className}` : ''
|
||||
}`}
|
||||
{...buttonProps}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
export default Button
|
||||
24
src/components/ui/Details.tsx
Normal file
24
src/components/ui/Details.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import '@/components/ui/details.css'
|
||||
import type { FC, ReactNode } from 'react'
|
||||
import type { MergeOmitting } from '@/types/helpers'
|
||||
|
||||
export type DetailsProps = MergeOmitting<
|
||||
React.DetailedHTMLProps<
|
||||
React.HTMLAttributes<HTMLDetailsElement>,
|
||||
HTMLDetailsElement
|
||||
>,
|
||||
{
|
||||
title: string
|
||||
children: ReactNode
|
||||
}
|
||||
>
|
||||
const Details: FC<DetailsProps> = ({ title, className, children }) => {
|
||||
return (
|
||||
<details className={`st-rnd-details${className ? ` ${className}` : ''}`}>
|
||||
<summary>{title}</summary>
|
||||
<div>{children}</div>
|
||||
</details>
|
||||
)
|
||||
}
|
||||
|
||||
export default Details
|
||||
28
src/components/ui/Dialog.tsx
Normal file
28
src/components/ui/Dialog.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import '@/components/ui/dialog.css'
|
||||
import type {
|
||||
DetailedHTMLProps,
|
||||
DialogHTMLAttributes,
|
||||
FC,
|
||||
ReactNode
|
||||
} from 'react'
|
||||
import type { MergeOmitting } from '@/types/helpers'
|
||||
|
||||
export type DialogProps = MergeOmitting<
|
||||
DetailedHTMLProps<DialogHTMLAttributes<HTMLDialogElement>, HTMLDialogElement>,
|
||||
{
|
||||
children: ReactNode
|
||||
}
|
||||
>
|
||||
|
||||
const Dialog: FC<DialogProps> = ({ children, className, ...dialogProps }) => {
|
||||
return (
|
||||
<dialog
|
||||
className={`st-rnd-dialog${className ? ` ${className}` : ''}`}
|
||||
{...dialogProps}
|
||||
>
|
||||
{children}
|
||||
</dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default Dialog
|
||||
19
src/components/ui/Input.tsx
Normal file
19
src/components/ui/Input.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { DetailedHTMLProps, FC, InputHTMLAttributes } from 'react'
|
||||
import type { MergeOmitting } from '@/types/helpers'
|
||||
import '@/components/ui/input.css'
|
||||
|
||||
export type InputProps = MergeOmitting<
|
||||
DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>,
|
||||
{ fullWidth?: boolean }
|
||||
>
|
||||
|
||||
const Input: FC<InputProps> = ({ className, ...inputProps }) => {
|
||||
return (
|
||||
<input
|
||||
className={`st-rnd-input${className ? ` ${className}` : ''}`}
|
||||
{...inputProps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Input
|
||||
28
src/components/ui/Select.tsx
Normal file
28
src/components/ui/Select.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import type {
|
||||
DetailedHTMLProps,
|
||||
FC,
|
||||
ReactNode,
|
||||
SelectHTMLAttributes
|
||||
} from 'react'
|
||||
import type { MergeOmitting } from '@/types/helpers'
|
||||
import '@/components/ui/select.css'
|
||||
|
||||
type SelectProps = MergeOmitting<
|
||||
DetailedHTMLProps<SelectHTMLAttributes<HTMLSelectElement>, HTMLSelectElement>,
|
||||
{
|
||||
children: ReactNode
|
||||
}
|
||||
>
|
||||
|
||||
const Select: FC<SelectProps> = ({ children, className, ...selectProps }) => {
|
||||
return (
|
||||
<select
|
||||
className={`st-rnd-select${className ? ` ${className}` : ''}`}
|
||||
{...selectProps}
|
||||
>
|
||||
{children}
|
||||
</select>
|
||||
)
|
||||
}
|
||||
|
||||
export default Select
|
||||
24
src/components/ui/Textarea.tsx
Normal file
24
src/components/ui/Textarea.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { DetailedHTMLProps, FC, TextareaHTMLAttributes } from 'react'
|
||||
import type { MergeOmitting } from '@/types/helpers'
|
||||
import '@/components/ui/textarea.css'
|
||||
|
||||
export type TextareaProps = MergeOmitting<
|
||||
DetailedHTMLProps<
|
||||
TextareaHTMLAttributes<HTMLTextAreaElement>,
|
||||
HTMLTextAreaElement
|
||||
>,
|
||||
{
|
||||
className?: string
|
||||
}
|
||||
>
|
||||
|
||||
const Textarea: FC<TextareaProps> = ({ className, ...textareaProps }) => {
|
||||
return (
|
||||
<textarea
|
||||
className={`st-rnd-textarea${className ? ` ${className}` : ''}`}
|
||||
{...textareaProps}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Textarea
|
||||
23
src/components/ui/UploadButton.tsx
Normal file
23
src/components/ui/UploadButton.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import '@/components/ui/button.css'
|
||||
import type { DetailedHTMLProps, FC, InputHTMLAttributes } from 'react'
|
||||
import type { MergeOmitting } from '@/types/helpers'
|
||||
|
||||
export type UploadButtonProps = MergeOmitting<
|
||||
DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>,
|
||||
{
|
||||
id: string
|
||||
label?: string
|
||||
}
|
||||
>
|
||||
const UploadButton: FC<UploadButtonProps> = ({ id, label, ...inputProps }) => {
|
||||
return (
|
||||
<>
|
||||
<input type="file" id={id} {...inputProps} />
|
||||
<label htmlFor={id} className={`st-rnd-button button-neutral`}>
|
||||
{label || 'Upload'}
|
||||
</label>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default UploadButton
|
||||
81
src/components/ui/button.css
Normal file
81
src/components/ui/button.css
Normal file
@@ -0,0 +1,81 @@
|
||||
.st-rnd-button {
|
||||
--btn-background-color: var(--color-primary-3);
|
||||
--btn-background-color-hover: var(--color-primary-4);
|
||||
--btn-background-color-active: var(--color-primary-5);
|
||||
--btn-border-color: var(--color-primary-6);
|
||||
--btn-color: var(--color-primary-12);
|
||||
|
||||
&.button-neutral {
|
||||
--btn-background-color: var(--color-neutral-3);
|
||||
--btn-background-color-hover: var(--color-neutral-4);
|
||||
--btn-background-color-active: var(--color-neutral-5);
|
||||
--btn-border-color: var(--color-neutral-6);
|
||||
--btn-color: var(--color-neutral-12);
|
||||
}
|
||||
}
|
||||
|
||||
.st-rnd-button {
|
||||
display: inline-block;
|
||||
|
||||
padding: 4px 8px;
|
||||
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--btn-border-color);
|
||||
background-color: var(--btn-background-color);
|
||||
color: var(--btn-color);
|
||||
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--btn-background-color-hover);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--btn-background-color-active);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--color-neutral-1) 60%,
|
||||
var(--btn-background-color) 40%
|
||||
);
|
||||
border-color: color-mix(
|
||||
in srgb,
|
||||
var(--color-neutral-1) 60%,
|
||||
var(--btn-border-color) 40%
|
||||
);
|
||||
color: color-mix(in srgb, var(--color-neutral-1) 60%, var(--btn-color) 40%);
|
||||
|
||||
cursor: not-allowed;
|
||||
&:hover {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--color-neutral-1) 60%,
|
||||
var(--btn-background-color) 40%
|
||||
);
|
||||
border-color: color-mix(
|
||||
in srgb,
|
||||
var(--color-neutral-1) 60%,
|
||||
var(--btn-border-color) 40%
|
||||
);
|
||||
color: color-mix(
|
||||
in srgb,
|
||||
var(--color-neutral-1) 60%,
|
||||
var(--btn-color) 40%
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
&.button-icon {
|
||||
padding: 4px;
|
||||
height: auto;
|
||||
width: auto ;
|
||||
aspect-ratio: 1;
|
||||
|
||||
font-size: 0.8em;
|
||||
}
|
||||
}
|
||||
29
src/components/ui/details.css
Normal file
29
src/components/ui/details.css
Normal file
@@ -0,0 +1,29 @@
|
||||
.st-rnd-details {
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-neutral-6);
|
||||
background-color: var(--color-neutral-3);
|
||||
color: var(--color-neutral-12);
|
||||
|
||||
&[open] {
|
||||
summary {
|
||||
background-color: var(--color-neutral-5);
|
||||
}
|
||||
}
|
||||
|
||||
> summary {
|
||||
padding: 4px 8px;
|
||||
cursor: pointer;
|
||||
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-neutral-4);
|
||||
}
|
||||
}
|
||||
|
||||
> div {
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
15
src/components/ui/dialog.css
Normal file
15
src/components/ui/dialog.css
Normal file
@@ -0,0 +1,15 @@
|
||||
.st-rnd-dialog {
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-neutral-6);
|
||||
padding: 8px;
|
||||
background-color: var(--color-neutral-1);
|
||||
color: var(--color-neutral-12);
|
||||
|
||||
&::backdrop {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--color-neutral-2) 80%,
|
||||
transparent 20%
|
||||
);
|
||||
}
|
||||
}
|
||||
10
src/components/ui/input.css
Normal file
10
src/components/ui/input.css
Normal file
@@ -0,0 +1,10 @@
|
||||
.st-rnd-input {
|
||||
display: block;
|
||||
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-neutral-6);
|
||||
padding: 4px 8px;
|
||||
background-color: var(--color-neutral-3);
|
||||
color: var(--color-neutral-12);
|
||||
}
|
||||
38
src/components/ui/select.css
Normal file
38
src/components/ui/select.css
Normal file
@@ -0,0 +1,38 @@
|
||||
.st-rnd-select {
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-neutral-6);
|
||||
padding: 4px 8px;
|
||||
background-color: var(--color-neutral-3);
|
||||
color: var(--color-neutral-12);
|
||||
|
||||
&:active {
|
||||
background-color: var(--color-neutral-12);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--color-neutral-1) 60%,
|
||||
var(--color-neutral-3) 40%
|
||||
);
|
||||
color: color-mix(
|
||||
in srgb,
|
||||
var(--color-neutral-1) 60%,
|
||||
var(--color-neutral-12) 40%
|
||||
);
|
||||
cursor: not-allowed;
|
||||
|
||||
&:active {
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--color-neutral-1) 60%,
|
||||
var(--color-neutral-3) 40%
|
||||
);
|
||||
color: color-mix(
|
||||
in srgb,
|
||||
var(--color-neutral-1) 60%,
|
||||
var(--color-neutral-12) 40%
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
src/components/ui/textarea.css
Normal file
9
src/components/ui/textarea.css
Normal file
@@ -0,0 +1,9 @@
|
||||
.st-rnd-textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-neutral-6);
|
||||
background-color: var(--color-neutral-3);
|
||||
color: var(--color-neutral-12);
|
||||
}
|
||||
114
src/components/wordList/EditWordList.tsx
Normal file
114
src/components/wordList/EditWordList.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
import { type FC, useRef, useState } from 'react'
|
||||
import Button from '@/components/ui/Button'
|
||||
import Dialog from '@/components/ui/Dialog'
|
||||
import { useStore } from '@/store'
|
||||
import '@/components/wordList/editWordlList.css'
|
||||
import Input from '../ui/Input'
|
||||
import Textarea from '../ui/Textarea'
|
||||
|
||||
export interface RenameWordListProps {
|
||||
currentWordList: string
|
||||
}
|
||||
|
||||
const EditWordList: FC<RenameWordListProps> = ({ currentWordList }) => {
|
||||
const [internalStatus, setInternalStatus] = useState<string | undefined>()
|
||||
const [newName, setNewName] = useState<string | undefined>(undefined)
|
||||
const dialogRef = useRef<HTMLDialogElement>(null)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
type="button"
|
||||
icon
|
||||
disabled={currentWordList === ''}
|
||||
onClick={() => {
|
||||
if (dialogRef.current === null || currentWordList === '') {
|
||||
return
|
||||
}
|
||||
const wordList = useStore.getState().wordLists[currentWordList]
|
||||
if (wordList === undefined) {
|
||||
return
|
||||
}
|
||||
setNewName(currentWordList)
|
||||
setInternalStatus(wordList.join('\n'))
|
||||
dialogRef.current.showModal()
|
||||
}}
|
||||
>
|
||||
<i className="fa fa-fw fa-pencil"></i>
|
||||
</Button>
|
||||
<Dialog id="st-rnd-wordlist-edit-dialog" ref={dialogRef}>
|
||||
<div>
|
||||
<h2 id="st-rnd-wordlist-edit-dialog-title">
|
||||
Edit Word List: <span>{currentWordList}</span>
|
||||
</h2>
|
||||
<label htmlFor="st-rnd-wordlist-name">Name</label>
|
||||
<p className="st-rnd-small-text">
|
||||
Name of the word list, used in placeholder and macros.
|
||||
</p>
|
||||
<Input
|
||||
id="st-rnd-wordlist-name"
|
||||
type="text"
|
||||
value={newName}
|
||||
onChange={(e) => setNewName(e.target.value)}
|
||||
/>
|
||||
<label htmlFor="st-rnd-wordlist-words">Words</label>
|
||||
<p className="st-rnd-small-text">
|
||||
Words separated by newlines, words can be more than one word!
|
||||
</p>
|
||||
<Textarea
|
||||
id="st-rnd-wordlist-words"
|
||||
value={internalStatus}
|
||||
onChange={(e) => setInternalStatus(e.target.value)}
|
||||
rows={10}
|
||||
/>
|
||||
<div id="st-rnd-wordlist-actions">
|
||||
<Button
|
||||
type="button"
|
||||
neutral
|
||||
onClick={() => {
|
||||
if (dialogRef.current === null) {
|
||||
return
|
||||
}
|
||||
dialogRef.current.close()
|
||||
setInternalStatus(undefined)
|
||||
setNewName(undefined)
|
||||
}}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (
|
||||
currentWordList === '' ||
|
||||
newName === undefined ||
|
||||
internalStatus === undefined ||
|
||||
dialogRef.current === null
|
||||
) {
|
||||
return
|
||||
}
|
||||
if (newName === '') {
|
||||
alert('Name cannot be empty')
|
||||
return
|
||||
}
|
||||
const addWordList = useStore.getState().addWordList
|
||||
if (currentWordList !== newName) {
|
||||
const removeWordList = useStore.getState().removeWordList
|
||||
removeWordList(currentWordList)
|
||||
}
|
||||
addWordList(newName, internalStatus.split('\n'))
|
||||
dialogRef.current.close()
|
||||
setInternalStatus(undefined)
|
||||
setNewName(undefined)
|
||||
}}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditWordList
|
||||
24
src/components/wordList/editWordlList.css
Normal file
24
src/components/wordList/editWordlList.css
Normal file
@@ -0,0 +1,24 @@
|
||||
#st-rnd-wordlist-edit-dialog {
|
||||
position: relative;
|
||||
|
||||
width: clamp(400px, 100%, 1200px);
|
||||
& > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
#st-rnd-wordlist-edit-dialog-title {
|
||||
width: 100%;
|
||||
|
||||
text-align: center;
|
||||
|
||||
& > span {
|
||||
color: var(--color-primary-9);
|
||||
}
|
||||
}
|
||||
#st-rnd-wordlist-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
21
src/components/wordLists.css
Normal file
21
src/components/wordLists.css
Normal file
@@ -0,0 +1,21 @@
|
||||
#st-rnd-upload-wordlist-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#st-rnd-wordlist-controls {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
& > select {
|
||||
flex-grow: 1;
|
||||
}
|
||||
& > button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.st-rnd-small-text {
|
||||
font-size: 0.8em;
|
||||
color: var(--color-neutral-11);
|
||||
}
|
||||
27
src/gui.css
Normal file
27
src/gui.css
Normal file
@@ -0,0 +1,27 @@
|
||||
:root {
|
||||
--color-neutral-1: #111113;
|
||||
--color-neutral-2: #18191b;
|
||||
--color-neutral-3: #212225;
|
||||
--color-neutral-4: #272a2d;
|
||||
--color-neutral-5: #2e3135;
|
||||
--color-neutral-6: #363a3f;
|
||||
--color-neutral-7: #43484e;
|
||||
--color-neutral-8: #5a6169;
|
||||
--color-neutral-9: #696e77;
|
||||
--color-neutral-10: #777b84;
|
||||
--color-neutral-11: #b0b4ba;
|
||||
--color-neutral-12: #edeef0;
|
||||
|
||||
--color-primary-1: #13131e;
|
||||
--color-primary-2: #171625;
|
||||
--color-primary-3: #202248;
|
||||
--color-primary-4: #262a65;
|
||||
--color-primary-5: #303374;
|
||||
--color-primary-6: #3d3e82;
|
||||
--color-primary-7: #4a4a95;
|
||||
--color-primary-8: #5958b1;
|
||||
--color-primary-9: #5b5bd6;
|
||||
--color-primary-10: #6e6ade;
|
||||
--color-primary-11: #b1a9ff;
|
||||
--color-primary-12: #e0dffe;
|
||||
}
|
||||
24
src/gui.tsx
Normal file
24
src/gui.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { StrictMode } from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import Settings from '@/Settings'
|
||||
import '@/gui.css'
|
||||
|
||||
const renderSettingsGui = () => {
|
||||
const rootContainer = document.getElementById('extensions_settings')
|
||||
|
||||
if (rootContainer === null) {
|
||||
throw new Error('[st-randomness-helpers] root container not found')
|
||||
}
|
||||
|
||||
const rootElement = document.createElement('div')
|
||||
|
||||
rootContainer.appendChild(rootElement)
|
||||
|
||||
ReactDOM.createRoot(rootElement).render(
|
||||
<StrictMode>
|
||||
<Settings />
|
||||
</StrictMode>
|
||||
)
|
||||
}
|
||||
|
||||
export default renderSettingsGui
|
||||
@@ -1,4 +1,3 @@
|
||||
console.log('Hello via Bun!')
|
||||
import renderSettingsGui from '@/gui'
|
||||
|
||||
const context = SillyTavern.getContext()
|
||||
console.log(context)
|
||||
renderSettingsGui()
|
||||
|
||||
5
src/types/helpers.ts
Normal file
5
src/types/helpers.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export type MergeOmitting<ReplaceableType, ReplacerType> = Omit<
|
||||
ReplaceableType,
|
||||
keyof ReplacerType
|
||||
> &
|
||||
ReplacerType
|
||||
Reference in New Issue
Block a user