diff --git a/index.html b/index.html index 61d4201..6baa8c6 100644 --- a/index.html +++ b/index.html @@ -20,9 +20,56 @@
+ Controls
+ diff --git a/src/fsm.js b/src/fsm.js index bf17b7d..568b712 100644 --- a/src/fsm.js +++ b/src/fsm.js @@ -6,6 +6,7 @@ const IS_INVALID = 'is-invalid'; const wordInput = /** @type {HTMLInputElement} */ (document.getElementById('word-input')); const buttons = /** @type {HTMLDivElement} */ (document.getElementById('input-buttons')); +const controlsButton = /** @type {HTMLButtonElement} */ (document.getElementById('controls-button')); const clearButton = /** @type {HTMLButtonElement} */ (document.getElementById('clear-button')); const light = /** @type {HTMLDivElement} */ (document.getElementById('light')); @@ -19,6 +20,11 @@ export function openAutomaton(states, editable = false) { const viewer = new GraphEditor(); viewer.readonly = !editable; + if (editable) { + controlsButton.removeAttribute('hidden'); + } else { + controlsButton.setAttribute('hidden', 'hidden'); + } const graph = createGraph(states); viewer.addEventListener('change', () => { try { diff --git a/src/main.js b/src/main.js index 0d05b03..e5ae8bd 100644 --- a/src/main.js +++ b/src/main.js @@ -1,5 +1,6 @@ import { AUTOMATONS } from './examples.js'; import { openAutomaton } from './fsm.js'; +import './modal.ts'; const automatonSelector = /** @type {HTMLDivElement} */ (document.getElementById('automaton-selector')); const automatonCollection = /** @type {HTMLDivElement} */ (document.getElementById('automaton-collection')); diff --git a/src/modal.ts b/src/modal.ts new file mode 100644 index 0000000..4411041 --- /dev/null +++ b/src/modal.ts @@ -0,0 +1,34 @@ +let modal: HTMLElement | null = null; + +function openModal(event: Event) { + event.preventDefault(); + const el = document.querySelector((event.target as HTMLElement).getAttribute('href')!)! as HTMLElement; + // @ts-expect-error reset + el.style.display = null; + el.removeAttribute('hidden'); + el.setAttribute('aria-modal', 'true'); + modal = el; + modal.addEventListener('click', closeModal); +} + +function closeModal(event: Event) { + event.preventDefault(); + if (modal === null) { + return; + } + modal.style.display = 'none'; + modal.setAttribute('hidden', 'hidden'); + modal.removeAttribute('aria-modal'); + modal.removeEventListener('click', closeModal); + modal = null; +} + +document.querySelectorAll('.open-modal').forEach((el) => { + el.addEventListener('click', openModal); +}); + +window.addEventListener('keydown', (event) => { + if (event.key === 'Escape' && modal !== null) { + closeModal(event); + } +}); diff --git a/src/style.css b/src/style.css index 2993b82..aec9a20 100644 --- a/src/style.css +++ b/src/style.css @@ -43,7 +43,7 @@ input { background-color: transparent; transition: border-color 0.25s; } -button { +button, .button { border-radius: 8px; border: 1px solid transparent; padding: 0.6em 1.2em; @@ -53,8 +53,10 @@ button { background-color: #1a1a1a; cursor: pointer; transition: border-color 0.25s; + text-decoration: none; + color: inherit; } -button:hover { +button:hover, .button:hover { border-color: #646cff; } button:focus, @@ -184,12 +186,47 @@ graph-editor { float: right; } +table { + width: 100%; +} +th, td { + border-bottom: thin solid hsla(0, 0%, 100%, .12); + height: 48px; + padding: 0 16px; +} + +.modal[hidden] { + display: none; +} + +.modal { + position: fixed; + display: flex; + align-items: center; + justify-content: center; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, .8); + animation: fadeIn .3s both +} + +.modal-content { + overflow: auto; + width: 600px; + max-width: calc(100vw - 20px); + max-height: calc(100vh - 20px); + padding: 20px; + background-color: #1a1a1a; +} + @media (prefers-color-scheme: light) { :root { color: #213547; background-color: #ffffff; } - button { + button, .btn { background-color: #f9f9f9; } .context-menu {