281 lines
No EOL
11 KiB
HTML
281 lines
No EOL
11 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Headless recorder : controls</title>
|
|
<style>
|
|
:root {
|
|
--color-dark: #282C34;
|
|
--color-light: #ABB2BF;
|
|
--color-red: #E06C75;
|
|
}
|
|
|
|
@font-face {
|
|
font-family: 'Montserrat';
|
|
src: url('https://cdn.jsdelivr.net/npm/@fontsource/montserrat/files/montserrat-latin-ext-400-normal.woff2') format('woff2');
|
|
}
|
|
|
|
@font-face {
|
|
font-family: 'Inconsolata';
|
|
src: url('https://cdn.jsdelivr.net/npm/@fontsource/inconsolata/files/inconsolata-latin-400-normal.woff2') format('woff2');
|
|
}
|
|
|
|
* {
|
|
all: unset;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
head {
|
|
display: none;
|
|
}
|
|
|
|
.app {
|
|
width: 100vw;
|
|
height: 100vh;
|
|
color: var(--color-light);
|
|
background-color: var(--color-dark);
|
|
font-family: 'Montserrat', sans-serif;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.app__header,
|
|
.app__main__history {
|
|
margin: 1rem;
|
|
}
|
|
|
|
.app__header,
|
|
.app__header__options,
|
|
.app__header__options__option {
|
|
display: flex;
|
|
column-gap: 1rem;
|
|
}
|
|
|
|
.app__header__options__option {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.app__header__options__option__list,
|
|
.app__header__options__option__button {
|
|
border: 2px solid rgba(0, 0, 0, 0.25);
|
|
border-radius: 1rem;
|
|
}
|
|
|
|
.app__header__options__option__list {
|
|
display: flex;
|
|
}
|
|
|
|
.app__header__options__option__list__item,
|
|
.app__header__options__option__button {
|
|
padding: 0.5rem 1rem;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.app__header__options__option__list__item {
|
|
border-radius: calc(1rem - 2px);
|
|
}
|
|
|
|
/*noinspection CssUnusedSymbol*/
|
|
.app__header__options__option__list__item--active,
|
|
.app__header__options__option__button--active {
|
|
background-color: rgba(0, 0, 0, 0.25);
|
|
}
|
|
|
|
/*noinspection CssUnusedSymbol*/
|
|
.app__header__options__option__button--active {
|
|
border-color: transparent;
|
|
}
|
|
|
|
.app__header__clear {
|
|
margin-left: auto;
|
|
cursor: pointer;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.app__header__clear:hover {
|
|
color: var(--color-red);
|
|
}
|
|
|
|
.app__main {
|
|
height: 0;
|
|
flex: 1 1 auto;
|
|
display: flex;
|
|
}
|
|
|
|
.app__main__formats {
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding-left: 0.5rem;
|
|
}
|
|
|
|
.app__main__formats__item {
|
|
padding: 0.5rem 1rem;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.app__main__formats__item--active {
|
|
background-color: rgba(0, 0, 0, 0.25);
|
|
}
|
|
|
|
.app__main__history {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: auto;
|
|
}
|
|
|
|
.app__main__history__item {
|
|
font-family: 'Inconsolata', monospace;
|
|
}
|
|
</style>
|
|
<script src="https://cdn.jsdelivr.net/npm/vue@3.1.1"></script>
|
|
<!--suppress JSUnresolvedVariable,JSUnresolvedFunction-->
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const
|
|
vue = Vue.createApp({
|
|
template: `
|
|
<Header />
|
|
<Main />
|
|
`
|
|
});
|
|
vue.component('Header', {
|
|
data: () => ({
|
|
options: [
|
|
{
|
|
label: 'Modes',
|
|
slug: 'mode',
|
|
items: [
|
|
{ slug: 'disabled', label: 'Disabled' },
|
|
{ slug: 'default', label: 'Click/Type' },
|
|
{ slug: 'expect', label: 'Expect' }
|
|
],
|
|
value: undefined
|
|
},
|
|
{
|
|
slug: 'navigate',
|
|
items: [{ label: 'Navigate' }],
|
|
value: undefined
|
|
},
|
|
{
|
|
slug: 'wait',
|
|
items: [{ label: 'Wait' }],
|
|
value: undefined
|
|
}
|
|
]
|
|
}),
|
|
methods: {
|
|
getValue,
|
|
setValue,
|
|
getStatus: async function(){
|
|
return await this.getValue('status');
|
|
},
|
|
setStatus: async function(status){
|
|
await this.setValue('status', status);
|
|
},
|
|
setOption: async function(option, value){
|
|
const
|
|
{ slug } = option,
|
|
status = await this.getStatus();
|
|
status[slug] = value;
|
|
await this.setStatus(status);
|
|
option.value = value;
|
|
},
|
|
clearHistory: async function(){
|
|
await this.setValue('history', { items: [] });
|
|
}
|
|
},
|
|
mounted: async function(){
|
|
const status = await this.getStatus();
|
|
if(!status.mode) status.mode = 'disabled';
|
|
for(const option of this.options)
|
|
option.value = status[option.slug];
|
|
},
|
|
template: `
|
|
<header class="app__header">
|
|
<ul class="app__header__options">
|
|
<li
|
|
v-for="option in options"
|
|
class="app__header__options__option"
|
|
>
|
|
<h3
|
|
v-if="option.label"
|
|
class="app__header__options__option__title"
|
|
>
|
|
{{ option.label }}
|
|
</h3>
|
|
<ul
|
|
v-if="option.items.length > 1"
|
|
class="app__header__options__option__list"
|
|
>
|
|
<li
|
|
v-for="{ slug, label } in option.items"
|
|
class="app__header__options__option__list__item"
|
|
:class="{ 'app__header__options__option__list__item--active': option.value === slug }"
|
|
@click="setOption(option, slug)"
|
|
>{{ label }}</li>
|
|
</ul>
|
|
<button
|
|
v-if="option.items.length === 1"
|
|
class="app__header__options__option__button"
|
|
:class="{ 'app__header__options__option__button--active': option.value }"
|
|
@click="setOption(option, typeof option.value === 'boolean' ? !option.value : true)"
|
|
>{{ option.items[0].label }}</button>
|
|
</li>
|
|
</ul>
|
|
<button
|
|
class="app__header__clear"
|
|
@click="clearHistory"
|
|
>Clear</button>
|
|
</header>
|
|
`
|
|
});
|
|
vue.component('Main', {
|
|
data: () => ({
|
|
formats,
|
|
format: 'raw',
|
|
historyItems: []
|
|
}),
|
|
methods: {
|
|
getValue,
|
|
json2code
|
|
},
|
|
mounted: async function(){
|
|
const refresh = async () => {
|
|
const
|
|
{ items = [] } = await this.getValue('history'),
|
|
isUpdated = JSON.stringify(this.historyItems) !== JSON.stringify(items);
|
|
this.historyItems.splice(0, this.historyItems.length, ...items);
|
|
await this.$nextTick();
|
|
if(isUpdated)
|
|
this.$refs.history.scrollTop = this.$refs.history.scrollHeight;
|
|
requestAnimationFrame(refresh);
|
|
};
|
|
await refresh();
|
|
},
|
|
template: `
|
|
<main class="app__main">
|
|
<ul class="app__main__formats">
|
|
<li
|
|
v-for="item in ['raw', ...formats]"
|
|
class="app__main__formats__item"
|
|
:class="{ 'app__main__formats__item--active': format === item }"
|
|
@click="format = item"
|
|
>{{ item }}</li>
|
|
</ul>
|
|
<ul class="app__main__history" ref="history">
|
|
<li
|
|
v-for="item in historyItems"
|
|
class="app__main__history__item"
|
|
>{{ format === 'raw' ? item : json2code(item, format) }}</li>
|
|
</ul>
|
|
</main>
|
|
`
|
|
});
|
|
vue.mount('.app');
|
|
});
|
|
</script>
|
|
</head>
|
|
<body class="app"></body>
|
|
</html> |