mirror of
https://github.com/CharaChorder/DeviceManager.git
synced 2026-01-20 17:03:42 +00:00
feat: 3d click in layout
feat: action autocomplete [deploy]
This commit is contained in:
59
src/lib/action-autocomplete.ts
Normal file
59
src/lib/action-autocomplete.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import type {Action} from "svelte/action"
|
||||||
|
import Index from "flexsearch"
|
||||||
|
import {KEYMAP_CODES} from "$lib/serial/keymap-codes"
|
||||||
|
import tippy from "tippy.js"
|
||||||
|
import ActionAutocomplete from "$lib/components/ActionAutocomplete.svelte"
|
||||||
|
import {browser} from "$app/environment"
|
||||||
|
|
||||||
|
const index = browser ? new Index({tokenize: "full"}) : undefined
|
||||||
|
for (const action of Object.values(KEYMAP_CODES)) {
|
||||||
|
index?.add(
|
||||||
|
action.code,
|
||||||
|
`${action.title || ""} ${action.variant || ""} ${action.category} ${action.id || ""} ${
|
||||||
|
action.description || ""
|
||||||
|
}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const exact = Object.fromEntries(
|
||||||
|
Object.values(KEYMAP_CODES)
|
||||||
|
.filter(it => !!it.id)
|
||||||
|
.map(it => [it.id, it] as const),
|
||||||
|
)
|
||||||
|
|
||||||
|
export const actionAutocomplete: Action<HTMLInputElement> = node => {
|
||||||
|
if (!browser) return
|
||||||
|
|
||||||
|
let completionComponent: ActionAutocomplete
|
||||||
|
const completionDialog = tippy(node, {
|
||||||
|
interactive: true,
|
||||||
|
placement: "bottom-start",
|
||||||
|
hideOnClick: false,
|
||||||
|
theme: "surface-variant search-completion",
|
||||||
|
arrow: false,
|
||||||
|
trigger: "focus",
|
||||||
|
offset: [0, 0],
|
||||||
|
onCreate(instance) {
|
||||||
|
const target = instance.popper.querySelector(".tippy-content")!
|
||||||
|
completionComponent = new ActionAutocomplete({target, props: {width: node.clientWidth}})
|
||||||
|
},
|
||||||
|
onDestroy() {
|
||||||
|
completionComponent.$destroy()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
function input(event: Event) {
|
||||||
|
completionComponent.$set({
|
||||||
|
results: index!.search(node.value),
|
||||||
|
exact: exact[node.value],
|
||||||
|
code: Number(node.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
node.addEventListener("input", input)
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
node.removeEventListener("input", input)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
name: Action Codes
|
|
||||||
description: Invalid action codes
|
|
||||||
actions:
|
|
||||||
0x00:
|
|
||||||
id: "0x00"
|
|
||||||
0x01:
|
|
||||||
id: "0x01"
|
|
||||||
0x02:
|
|
||||||
id: "0x02"
|
|
||||||
0x03:
|
|
||||||
id: "0x03"
|
|
||||||
0x04:
|
|
||||||
id: "0x04"
|
|
||||||
0x05:
|
|
||||||
id: "0x05"
|
|
||||||
0x06:
|
|
||||||
id: "0x06"
|
|
||||||
0x07:
|
|
||||||
id: "0x07"
|
|
||||||
0x08:
|
|
||||||
id: "0x08"
|
|
||||||
0x09:
|
|
||||||
id: "0x09"
|
|
||||||
0x0A:
|
|
||||||
id: "0x0A"
|
|
||||||
0x0B:
|
|
||||||
id: "0x0B"
|
|
||||||
0x0C:
|
|
||||||
id: "0x0C"
|
|
||||||
0x0D:
|
|
||||||
id: "0x0D"
|
|
||||||
0x0E:
|
|
||||||
id: "0x0E"
|
|
||||||
0x0F:
|
|
||||||
id: "0x0F"
|
|
||||||
0x10:
|
|
||||||
id: "0x10"
|
|
||||||
0x11:
|
|
||||||
id: "0x11"
|
|
||||||
0x12:
|
|
||||||
id: "0x12"
|
|
||||||
0x13:
|
|
||||||
id: "0x13"
|
|
||||||
0x14:
|
|
||||||
id: "0x14"
|
|
||||||
0x15:
|
|
||||||
id: "0x15"
|
|
||||||
0x16:
|
|
||||||
id: "0x16"
|
|
||||||
0x17:
|
|
||||||
id: "0x17"
|
|
||||||
0x18:
|
|
||||||
id: "0x18"
|
|
||||||
0x19:
|
|
||||||
id: "0x19"
|
|
||||||
0x1A:
|
|
||||||
id: "0x1A"
|
|
||||||
0x1B:
|
|
||||||
id: "0x1B"
|
|
||||||
0x1C:
|
|
||||||
id: "0x1C"
|
|
||||||
0x1D:
|
|
||||||
id: "0x1D"
|
|
||||||
0x1E:
|
|
||||||
id: "0x1E"
|
|
||||||
0x1F:
|
|
||||||
id: "0x1F"
|
|
||||||
66
src/lib/components/ActionAutocomplete.svelte
Normal file
66
src/lib/components/ActionAutocomplete.svelte
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {KEYMAP_CODES} from "$lib/serial/keymap-codes"
|
||||||
|
import ActionListItem from "$lib/components/ActionListItem.svelte"
|
||||||
|
|
||||||
|
export let exact: number | undefined = undefined
|
||||||
|
export let code: number = Number.NaN
|
||||||
|
export let results: number[] = []
|
||||||
|
|
||||||
|
export let width: number
|
||||||
|
|
||||||
|
console.log(width)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="list" style="width: {width}px">
|
||||||
|
{#if exact !== undefined}
|
||||||
|
<div class="exact">
|
||||||
|
<i>Exact match</i>
|
||||||
|
<ActionListItem id={exact} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if !exact && code}
|
||||||
|
{#if code >= 2 ** 5 && code < 2 ** 13}
|
||||||
|
<button>USE CODE</button>
|
||||||
|
{:else}
|
||||||
|
<div>Action code is out of range</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
{#each results as id (id)}
|
||||||
|
<ActionListItem {id} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.list {
|
||||||
|
--scrollbar-color: var(--md-sys-color-on-surface-variant);
|
||||||
|
|
||||||
|
scrollbar-gutter: stable both-edges;
|
||||||
|
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
max-height: 500px;
|
||||||
|
padding-block: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.exact {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
border: 1px solid var(--md-sys-color-primary);
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
> i {
|
||||||
|
padding-inline: 8px;
|
||||||
|
color: var(--md-sys-color-on-primary);
|
||||||
|
background: var(--md-sys-color-primary);
|
||||||
|
border-radius: 0 0 8px 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
71
src/lib/components/ActionListItem.svelte
Normal file
71
src/lib/components/ActionListItem.svelte
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {KEYMAP_CODES} from "$lib/serial/keymap-codes"
|
||||||
|
import type {KeyInfo} from "$lib/serial/keymap-codes"
|
||||||
|
|
||||||
|
export let id: number | KeyInfo
|
||||||
|
|
||||||
|
$: key = (typeof id === "number" ? KEYMAP_CODES[id] ?? id : id) as number | KeyInfo
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button>
|
||||||
|
{#if typeof key === "object"}
|
||||||
|
<div class="title">
|
||||||
|
<b>
|
||||||
|
{key.title || ""}
|
||||||
|
{#if key.variant === "left"}
|
||||||
|
(Left)
|
||||||
|
{:else if key.variant === "right"}
|
||||||
|
(Right)
|
||||||
|
{/if}
|
||||||
|
</b>
|
||||||
|
{#if key.description}
|
||||||
|
<i>{key.description}</i>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<span class:icon={!!key.icon} class="key">{key.icon || key.id || `0x${key.code.toString(16)}`}</span>
|
||||||
|
{:else}
|
||||||
|
<span class="key">0x{key.toString(16)}</span>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
button {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
font-family: "Noto Sans Mono", monospace;
|
||||||
|
color: inherit;
|
||||||
|
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
min-width: 32px;
|
||||||
|
padding: 4px;
|
||||||
|
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
border: 1px solid currentcolor;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -3,52 +3,16 @@
|
|||||||
import {KEYMAP_CODES} from "$lib/serial/keymap-codes"
|
import {KEYMAP_CODES} from "$lib/serial/keymap-codes"
|
||||||
import ActionSelector from "$lib/components/layout/ActionSelector.svelte"
|
import ActionSelector from "$lib/components/layout/ActionSelector.svelte"
|
||||||
import {popup} from "$lib/popup"
|
import {popup} from "$lib/popup"
|
||||||
|
import ActionListItem from "$lib/components/ActionListItem.svelte"
|
||||||
|
|
||||||
export let id: number = 0
|
export let id: number = 0
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
{#each $layout as layer, i}
|
{#each $layout as layer, i}
|
||||||
{@const action = KEYMAP_CODES[layer[id]]}
|
|
||||||
<tr>
|
<tr>
|
||||||
<th class="icon">counter_{i + 1}</th>
|
<th class="icon">counter_{i + 1}</th>
|
||||||
<td
|
<ActionListItem id={layer[id]} />
|
||||||
><button use:popup={ActionSelector}
|
|
||||||
>{action?.title || action?.id} <span class="icon">edit</span></button
|
|
||||||
></td
|
|
||||||
>
|
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
span.icon {
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 250ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
gap: 4px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
font-family: "Noto Sans Mono", monospace;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--md-sys-color-on-surface);
|
|
||||||
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
|
|
||||||
transition: all 250ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover > span.icon {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
<script>
|
<script lang="ts">
|
||||||
import {serialPort} from "$lib/serial/connection"
|
import {serialPort} from "$lib/serial/connection"
|
||||||
import LayoutCC1 from "$lib/components/layout/LayoutCC1.svelte"
|
import LayoutCC1 from "$lib/components/layout/LayoutCC1.svelte"
|
||||||
|
|
||||||
$: device = $serialPort?.device ?? "ONE"
|
$: device = $serialPort?.device ?? "ONE"
|
||||||
let activeLayer = 0
|
let activeLayer = 0
|
||||||
|
|
||||||
|
const layers = [
|
||||||
|
["Numeric Layer", "123", 1],
|
||||||
|
["Primary Layer", "abc", 0],
|
||||||
|
["Function Layer", "function", 2],
|
||||||
|
] as const
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@@ -13,7 +19,7 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
{#each [["Numeric Layer", "123", 1], ["Primary Layer", "abc", 0], ["Function Layer", "function", 2]] as [title, icon, value]}
|
{#each layers as [title, icon, value]}
|
||||||
<button
|
<button
|
||||||
{title}
|
{title}
|
||||||
class="icon"
|
class="icon"
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="radial {type}">
|
<div class="radial {type}">
|
||||||
{#each [keys.n, keys.e, keys.s, keys.w] as id, quadrant}
|
{#each [keys.n, keys.e, keys.s, keys.w, keys.d] as id, quadrant}
|
||||||
{@const actions = getActions(id, $layout)}
|
{@const actions = getActions(id, $layout)}
|
||||||
<button
|
<button
|
||||||
use:editableLayout={{id, quadrant}}
|
use:editableLayout={{id, quadrant}}
|
||||||
@@ -154,6 +154,21 @@
|
|||||||
&:nth-child(4) {
|
&:nth-child(4) {
|
||||||
clip-path: polygon(50% 50%, 0 0, 0 100%);
|
clip-path: polygon(50% 50%, 0 0, 0 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
translate: -50% -50%;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
width: 25cqw;
|
||||||
|
height: 25cqh;
|
||||||
|
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
mask-image: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.secondary > button {
|
.secondary > button {
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
import type {Chord} from "$lib/serial/chord"
|
|
||||||
import type {CharaLayout} from "$lib/serialization/layout"
|
|
||||||
|
|
||||||
export interface Profile {
|
|
||||||
name: string
|
|
||||||
layout: CharaLayout
|
|
||||||
chords: Chord[]
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import type {Index} from "flexsearch"
|
|
||||||
import FlexSearch from "flexsearch"
|
|
||||||
|
|
||||||
export function createIndex() {}
|
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
$padding: 16px;
|
$padding: 16px;
|
||||||
|
|
||||||
.tippy-box[data-theme~="surface-variant"] {
|
.tippy-box[data-theme~="surface-variant"] {
|
||||||
// overflow: hidden;
|
|
||||||
|
|
||||||
color: var(--md-sys-color-on-surface-variant);
|
color: var(--md-sys-color-on-surface-variant);
|
||||||
|
|
||||||
background-color: var(--md-sys-color-surface-variant);
|
background-color: var(--md-sys-color-surface-variant);
|
||||||
filter: drop-shadow(0 0 12px #000a);
|
filter: drop-shadow(0 0 12px #000a);
|
||||||
border-radius: calc(24px + $padding);
|
border-radius: calc(24px + $padding);
|
||||||
@@ -26,3 +23,13 @@ $padding: 16px;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tippy-box[data-theme~="search-completion"] {
|
||||||
|
filter: none;
|
||||||
|
border-radius: 0 0 16px 16px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.tippy-content {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -50,7 +50,6 @@
|
|||||||
initLocalStorage()
|
initLocalStorage()
|
||||||
|
|
||||||
if (pwaInfo) {
|
if (pwaInfo) {
|
||||||
// noinspection TypeScriptCheckImport
|
|
||||||
const {registerSW} = await import("virtual:pwa-register")
|
const {registerSW} = await import("virtual:pwa-register")
|
||||||
registerSW({
|
registerSW({
|
||||||
immediate: true,
|
immediate: true,
|
||||||
|
|||||||
@@ -4,10 +4,8 @@
|
|||||||
import Index from "flexsearch"
|
import Index from "flexsearch"
|
||||||
import {tick} from "svelte"
|
import {tick} from "svelte"
|
||||||
import type {Chord} from "$lib/serial/chord"
|
import type {Chord} from "$lib/serial/chord"
|
||||||
import tippy from "tippy.js"
|
|
||||||
import {calculateChordCoverage} from "$lib/chords/coverage"
|
|
||||||
import type {MouseEventHandler} from "svelte/elements"
|
|
||||||
import LL from "../../../i18n/i18n-svelte"
|
import LL from "../../../i18n/i18n-svelte"
|
||||||
|
import {actionAutocomplete} from "$lib/action-autocomplete"
|
||||||
|
|
||||||
$: searchIndex = $chords?.length > 0 ? buildIndex($chords) : undefined
|
$: searchIndex = $chords?.length > 0 ? buildIndex($chords) : undefined
|
||||||
|
|
||||||
@@ -29,10 +27,6 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function sort(event: Event) {
|
|
||||||
tippy(event.target as HTMLInputElement, {})
|
|
||||||
}
|
|
||||||
|
|
||||||
$: items = searchFilter?.map(it => [$chords[it], it] as const) ?? $chords.map((it, i) => [it, i] as const)
|
$: items = searchFilter?.map(it => [$chords[it], it] as const) ?? $chords.map((it, i) => [it, i] as const)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -40,15 +34,22 @@
|
|||||||
<title>Chord Manager</title>
|
<title>Chord Manager</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="search"
|
||||||
|
placeholder={$LL.configure.chords.search.PLACEHOLDER($chords.length)}
|
||||||
|
use:actionAutocomplete
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--
|
||||||
{#if searchIndex}
|
{#if searchIndex}
|
||||||
<input
|
<input
|
||||||
on:input={search}
|
on:input={search}
|
||||||
type="search"
|
type="search"
|
||||||
placeholder={$LL.configure.chords.search.PLACEHOLDER($chords.length)}
|
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}-->
|
||||||
<button class="icon" on:click={sort}>sort</button>
|
|
||||||
<button class="icon">filter_list</button>
|
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<table>
|
<table>
|
||||||
@@ -72,29 +73,21 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
</table>
|
</table>
|
||||||
<div>
|
|
||||||
<p>15 Duplicate Chords</p>
|
|
||||||
<p>12 Chords use</p>
|
|
||||||
{#await calculateChordCoverage($chords) then { missing, coverage }}
|
|
||||||
<p>{(coverage * 100).toFixed(1)}% of English 200</p>
|
|
||||||
{/await}
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
input[type="search"] {
|
input[type="search"] {
|
||||||
width: 300px;
|
width: 512px;
|
||||||
|
margin-block-start: 16px;
|
||||||
padding-block: 8px;
|
padding-block: 8px;
|
||||||
padding-inline: 32px;
|
padding-inline: 16px;
|
||||||
|
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: var(--md-sys-color-on-surface-variant);
|
color: inherit;
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
background: var(--md-sys-color-surface-variant);
|
background: none;
|
||||||
clip-path: polygon(0 0, 100% 0, 90% 100%, 10% 100%);
|
border: 0 solid var(--md-sys-color-surface-variant);
|
||||||
filter: brightness(80%);
|
border-bottom-width: 1px;
|
||||||
border: none;
|
|
||||||
|
|
||||||
transition: all 250ms ease;
|
transition: all 250ms ease;
|
||||||
|
|
||||||
@@ -104,7 +97,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
filter: brightness(90%);
|
border-color: var(--md-sys-color-primary);
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,11 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
async function shareLayout(event) {
|
async function shareLayout(event: Event) {
|
||||||
const url = new URL(window.location.href)
|
const url = new URL(window.location.href)
|
||||||
url.searchParams.set("layout", await layoutAsUrlComponent($layout))
|
url.searchParams.set("layout", await layoutAsUrlComponent($layout))
|
||||||
await navigator.clipboard.writeText(url.toString())
|
await navigator.clipboard.writeText(url.toString())
|
||||||
tippy(event.target, {
|
tippy(event.target as HTMLElement, {
|
||||||
content: "Share url copied!",
|
content: "Share url copied!",
|
||||||
delay: [0, 1000000],
|
delay: [0, 1000000],
|
||||||
onHidden(instance) {
|
onHidden(instance) {
|
||||||
|
|||||||
Reference in New Issue
Block a user