userscripts/HeadlessRecorder/controls.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>