Merge branch 'develop' into swn

This commit is contained in:
tamaina
2021-10-07 14:33:10 +09:00
223 changed files with 3373 additions and 2658 deletions

View File

@@ -25,7 +25,7 @@
<script lang="ts">
import { defineComponent, markRaw } from 'vue';
import XWindow from '@client/components/ui/window.vue';
import MkTextarea from '@client/components/ui/textarea.vue';
import MkTextarea from '@client/components/form/textarea.vue';
import MkButton from '@client/components/ui/button.vue';
import * as os from '@client/os';

View File

@@ -10,12 +10,12 @@
</li>
<li @click="chooseUser()" @keydown="onKeydown" tabindex="-1" class="choose">{{ $ts.selectUser }}</li>
</ol>
<ol class="hashtags" ref="suggests" v-if="hashtags.length > 0">
<ol class="hashtags" ref="suggests" v-else-if="hashtags.length > 0">
<li v-for="hashtag in hashtags" @click="complete(type, hashtag)" @keydown="onKeydown" tabindex="-1">
<span class="name">{{ hashtag }}</span>
</li>
</ol>
<ol class="emojis" ref="suggests" v-if="emojis.length > 0">
<ol class="emojis" ref="suggests" v-else-if="emojis.length > 0">
<li v-for="emoji in emojis" @click="complete(type, emoji.emoji)" @keydown="onKeydown" tabindex="-1">
<span class="emoji" v-if="emoji.isCustomEmoji"><img :src="$store.state.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url" :alt="emoji.emoji"/></span>
<span class="emoji" v-else-if="!$store.state.useOsNativeEmojis"><img :src="emoji.url" :alt="emoji.emoji"/></span>
@@ -24,6 +24,11 @@
<span class="alias" v-if="emoji.aliasOf">({{ emoji.aliasOf }})</span>
</li>
</ol>
<ol class="mfmTags" ref="suggests" v-else-if="mfmTags.length > 0">
<li v-for="tag in mfmTags" @click="complete(type, tag)" @keydown="onKeydown" tabindex="-1">
<span class="tag">{{ tag }}</span>
</li>
</ol>
</div>
</template>
@@ -106,6 +111,8 @@ emojiDefinitions.sort((a, b) => a.name.length - b.name.length);
const emojiDb = markRaw(emojiDefinitions.concat(emjdb));
//#endregion
const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'font', 'blur', 'rainbow', 'sparkle'];
export default defineComponent({
props: {
type: {
@@ -137,11 +144,6 @@ export default defineComponent({
type: Number,
required: true,
},
showing: {
type: Boolean,
required: true
},
},
emits: ['done', 'closed'],
@@ -154,18 +156,11 @@ export default defineComponent({
hashtags: [],
emojis: [],
items: [],
mfmTags: [],
select: -1,
}
},
watch: {
showing() {
if (!this.showing) {
this.$emit('closed');
}
}
},
updated() {
this.setPosition();
this.items = (this.$refs.suggests as Element | undefined)?.children || [];
@@ -236,7 +231,7 @@ export default defineComponent({
}
}
if (this.type == 'user') {
if (this.type === 'user') {
if (this.q == null) {
this.users = [];
this.fetching = false;
@@ -262,7 +257,7 @@ export default defineComponent({
sessionStorage.setItem(cacheKey, JSON.stringify(users));
});
}
} else if (this.type == 'hashtag') {
} else if (this.type === 'hashtag') {
if (this.q == null || this.q == '') {
this.hashtags = JSON.parse(localStorage.getItem('hashtags') || '[]');
this.fetching = false;
@@ -286,7 +281,7 @@ export default defineComponent({
});
}
}
} else if (this.type == 'emoji') {
} else if (this.type === 'emoji') {
if (this.q == null || this.q == '') {
// 最近使った絵文字をサジェスト
this.emojis = this.$store.state.recentlyUsedEmojis.map(emoji => emojiDb.find(e => e.emoji == emoji)).filter(x => x != null);
@@ -314,6 +309,13 @@ export default defineComponent({
}
this.emojis = matched;
} else if (this.type === 'mfmTag') {
if (this.q == null || this.q == '') {
this.mfmTags = MFM_TAGS;
return;
}
this.mfmTags = MFM_TAGS.filter(tag => tag.startsWith(this.q));
}
},
@@ -490,5 +492,11 @@ export default defineComponent({
margin: 0 0 0 8px;
}
}
> .mfmTags > li {
.name {
}
}
}
</style>

View File

@@ -39,7 +39,7 @@ export default defineComponent({
type: String,
required: true,
},
value: {
modelValue: {
type: String,
},
},
@@ -116,7 +116,7 @@ export default defineComponent({
}
},
callback(response?: string) {
this.$emit('update:value', typeof response == 'string' ? response : null);
this.$emit('update:modelValue', typeof response == 'string' ? response : null);
},
},
});

View File

@@ -91,7 +91,7 @@ export default defineComponent({
width: 31px;
}
&:focus {
&:focus-visible {
&:after {
content: "";
pointer-events: none;

View File

@@ -1,7 +1,7 @@
<template>
<button class="nrvgflfu _button" @click="toggle">
<b>{{ value ? $ts._cw.hide : $ts._cw.show }}</b>
<span v-if="!value">{{ label }}</span>
<b>{{ modelValue ? $ts._cw.hide : $ts._cw.show }}</b>
<span v-if="!modelValue">{{ label }}</span>
</button>
</template>
@@ -12,7 +12,7 @@ import { concat } from '../../prelude/array';
export default defineComponent({
props: {
value: {
modelValue: {
type: Boolean,
required: true
},
@@ -36,7 +36,7 @@ export default defineComponent({
length,
toggle() {
this.$emit('update:value', !this.value);
this.$emit('update:modelValue', !this.modelValue);
}
}
});

View File

@@ -21,39 +21,39 @@ export default defineComponent({
<style lang="scss" scoped>
.rbusrurv {
// CSS
--formXPadding: 32px;
--formYPadding: 32px;
--debobigegoXPadding: 32px;
--debobigegoYPadding: 32px;
--formContentHMargin: 16px;
--debobigegoContentHMargin: 16px;
font-size: 95%;
line-height: 1.3em;
background: var(--bg);
padding: var(--formYPadding) var(--formXPadding);
padding: var(--debobigegoYPadding) var(--debobigegoXPadding);
max-width: 750px;
margin: 0 auto;
&:not(.wide).max-width_400px {
--formXPadding: 0px;
--debobigegoXPadding: 0px;
> ::v-deep(*) {
._formPanel {
._debobigegoPanel {
border: solid 0.5px var(--divider);
border-radius: 0;
border-left: none;
border-right: none;
}
._form_group {
> *:not(._formNoConcat) {
&:not(:last-child):not(._formNoConcatPrev) {
&._formPanel, ._formPanel {
._debobigego_group {
> *:not(._debobigegoNoConcat) {
&:not(:last-child):not(._debobigegoNoConcatPrev) {
&._debobigegoPanel, ._debobigegoPanel {
border-bottom: solid 0.5px var(--divider);
}
}
&:not(:first-child):not(._formNoConcatNext) {
&._formPanel, ._formPanel {
&:not(:first-child):not(._debobigegoNoConcatNext) {
&._debobigegoPanel, ._debobigegoPanel {
border-top: none;
}
}

View File

@@ -1,7 +1,7 @@
<template>
<div class="yzpgjkxe _formItem">
<div class="_formLabel"><slot name="label"></slot></div>
<button class="main _button _formPanel _formClickable" :class="{ center, primary, danger }">
<div class="yzpgjkxe _debobigegoItem">
<div class="_debobigegoLabel"><slot name="label"></slot></div>
<button class="main _button _debobigegoPanel _debobigegoClickable" :class="{ center, primary, danger }">
<slot></slot>
<div class="suffix">
<slot name="suffix"></slot>
@@ -10,13 +10,13 @@
</div>
</div>
</button>
<div class="_formCaption"><slot name="desc"></slot></div>
<div class="_debobigegoCaption"><slot name="desc"></slot></div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import './form.scss';
import './debobigego.scss';
export default defineComponent({
props: {

View File

@@ -1,9 +1,9 @@
._formPanel {
._debobigegoPanel {
background: var(--panel);
border-radius: var(--radius);
transition: background 0.2s ease;
&._formClickable {
&._debobigegoClickable {
&:hover {
//background: var(--panelHighlight);
}
@@ -15,8 +15,8 @@
}
}
._formLabel,
._formCaption {
._debobigegoLabel,
._debobigegoCaption {
font-size: 80%;
color: var(--fgTransparentWeak);
@@ -25,28 +25,28 @@
}
}
._formLabel {
._debobigegoLabel {
position: sticky;
top: var(--stickyTop, 0px);
z-index: 2;
margin: -8px calc(var(--formXPadding) * -1) 0 calc(var(--formXPadding) * -1);
padding: 8px calc(var(--formContentHMargin) + var(--formXPadding)) 8px calc(var(--formContentHMargin) + var(--formXPadding));
margin: -8px calc(var(--debobigegoXPadding) * -1) 0 calc(var(--debobigegoXPadding) * -1);
padding: 8px calc(var(--debobigegoContentHMargin) + var(--debobigegoXPadding)) 8px calc(var(--debobigegoContentHMargin) + var(--debobigegoXPadding));
background: var(--X17);
-webkit-backdrop-filter: var(--blur, blur(10px));
backdrop-filter: var(--blur, blur(10px));
}
._themeChanging_ ._formLabel {
._themeChanging_ ._debobigegoLabel {
transition: none !important;
background: transparent;
}
._formCaption {
padding: 8px var(--formContentHMargin) 0 var(--formContentHMargin);
._debobigegoCaption {
padding: 8px var(--debobigegoContentHMargin) 0 var(--debobigegoContentHMargin);
}
._formItem {
& + ._formItem {
._debobigegoItem {
& + ._debobigegoItem {
margin-top: 24px;
}
}

View File

@@ -1,10 +1,10 @@
<template>
<div class="vrtktovg _formItem _formNoConcat" v-size="{ max: [500] }" v-sticky-container>
<div class="_formLabel"><slot name="label"></slot></div>
<div class="main _form_group" ref="child">
<div class="vrtktovg _debobigegoItem _debobigegoNoConcat" v-size="{ max: [500] }" v-sticky-container>
<div class="_debobigegoLabel"><slot name="label"></slot></div>
<div class="main _debobigego_group" ref="child">
<slot></slot>
</div>
<div class="_formCaption"><slot name="caption"></slot></div>
<div class="_debobigegoCaption"><slot name="caption"></slot></div>
</div>
</template>
@@ -20,9 +20,9 @@ export default defineComponent({
const els = Array.from(child.value.children);
for (let i = 0; i < els.length; i++) {
const el = els[i];
if (el.classList.contains('_formNoConcat')) {
if (els[i - 1]) els[i - 1].classList.add('_formNoConcatPrev');
if (els[i + 1]) els[i + 1].classList.add('_formNoConcatNext');
if (el.classList.contains('_debobigegoNoConcat')) {
if (els[i - 1]) els[i - 1].classList.add('_debobigegoNoConcatPrev');
if (els[i + 1]) els[i + 1].classList.add('_debobigegoNoConcatNext');
}
}
};
@@ -52,21 +52,21 @@ export default defineComponent({
<style lang="scss" scoped>
.vrtktovg {
> .main {
> ::v-deep(*):not(._formNoConcat) {
&:not(._formNoConcatNext) {
> ::v-deep(*):not(._debobigegoNoConcat) {
&:not(._debobigegoNoConcatNext) {
margin: 0;
}
&:not(:last-child):not(._formNoConcatPrev) {
&._formPanel, ._formPanel {
&:not(:last-child):not(._debobigegoNoConcatPrev) {
&._debobigegoPanel, ._debobigegoPanel {
border-bottom: solid 0.5px var(--divider);
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}
&:not(:first-child):not(._formNoConcatNext) {
&._formPanel, ._formPanel {
&:not(:first-child):not(._debobigegoNoConcatNext) {
&._debobigegoPanel, ._debobigegoPanel {
border-top: none;
border-top-left-radius: 0;
border-top-right-radius: 0;

View File

@@ -1,6 +1,6 @@
<template>
<div class="fzenkabp _formItem">
<div class="_formPanel" :class="{ warn }">
<div class="fzenkabp _debobigegoItem">
<div class="_debobigegoPanel" :class="{ warn }">
<i v-if="warn" class="fas fa-exclamation-triangle"></i>
<i v-else class="fas fa-info-circle"></i>
<slot></slot>

View File

@@ -1,49 +1,53 @@
<template>
<div class="matxzzsk">
<div class="label" @click="focus"><slot name="label"></slot></div>
<div class="input" :class="{ inline, disabled, focused }">
<div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div>
<input ref="inputEl"
:type="type"
v-model="v"
:disabled="disabled"
:required="required"
:readonly="readonly"
:placeholder="placeholder"
:pattern="pattern"
:autocomplete="autocomplete"
:spellcheck="spellcheck"
:step="step"
@focus="focused = true"
@blur="focused = false"
@keydown="onKeydown($event)"
@input="onInput"
:list="id"
>
<datalist :id="id" v-if="datalist">
<option v-for="data in datalist" :value="data"/>
</datalist>
<div class="suffix" ref="suffixEl"><slot name="suffix"></slot></div>
<FormGroup class="_debobigegoItem">
<template #label><slot></slot></template>
<div class="ztzhwixg _debobigegoItem" :class="{ inline, disabled }">
<div class="icon" ref="icon"><slot name="icon"></slot></div>
<div class="input _debobigegoPanel">
<div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div>
<input ref="inputEl"
:type="type"
v-model="v"
:disabled="disabled"
:required="required"
:readonly="readonly"
:placeholder="placeholder"
:pattern="pattern"
:autocomplete="autocomplete"
:spellcheck="spellcheck"
:step="step"
@focus="focused = true"
@blur="focused = false"
@keydown="onKeydown($event)"
@input="onInput"
:list="id"
>
<datalist :id="id" v-if="datalist">
<option v-for="data in datalist" :value="data"/>
</datalist>
<div class="suffix" ref="suffixEl"><slot name="suffix"></slot></div>
</div>
</div>
<div class="caption"><slot name="caption"></slot></div>
<template #caption><slot name="desc"></slot></template>
<MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
</div>
<FormButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
</FormGroup>
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue';
import MkButton from './button.vue';
import { debounce } from 'throttle-debounce';
import './debobigego.scss';
import FormButton from './button.vue';
import FormGroup from './group.vue';
export default defineComponent({
components: {
MkButton,
FormGroup,
FormButton,
},
props: {
modelValue: {
required: true
required: false
},
type: {
type: String,
@@ -92,20 +96,13 @@ export default defineComponent({
required: false,
default: false
},
debounce: {
type: Boolean,
required: false,
default: false
},
manualSave: {
type: Boolean,
required: false,
default: false
},
},
emits: ['change', 'keydown', 'enter', 'update:modelValue'],
setup(props, context) {
const { modelValue, type, autofocus } = toRefs(props);
const v = ref(modelValue.value);
@@ -140,19 +137,13 @@ export default defineComponent({
}
};
const debouncedUpdated = debounce(1000, updated);
watch(modelValue, newValue => {
watch(modelValue.value, newValue => {
v.value = newValue;
});
watch(v, newValue => {
if (!props.manualSave) {
if (props.debounce) {
debouncedUpdated();
} else {
updated();
}
updated();
}
invalid.value = inputEl.value.validity.badInput;
@@ -205,68 +196,59 @@ export default defineComponent({
</script>
<style lang="scss" scoped>
.matxzzsk {
margin: 1.5em 0;
.ztzhwixg {
position: relative;
> .label {
font-size: 0.85em;
padding: 0 0 8px 12px;
user-select: none;
> .icon {
position: absolute;
top: 0;
left: 0;
width: 24px;
text-align: center;
line-height: 32px;
&:empty {
display: none;
}
}
> .caption {
font-size: 0.8em;
padding: 8px 0 0 12px;
color: var(--fgTransparentWeak);
&:empty {
display: none;
&:not(:empty) + .input {
margin-left: 28px;
}
}
> .input {
$height: 42px;
$height: 48px;
position: relative;
> input {
appearance: none;
-webkit-appearance: none;
display: block;
height: $height;
width: 100%;
margin: 0;
padding: 0 12px;
padding: 0 16px;
font: inherit;
font-weight: normal;
font-size: 1em;
color: var(--fg);
background: var(--panel);
border: solid 0.5px var(--inputBorder);
border-radius: 6px;
line-height: $height;
color: var(--inputText);
background: transparent;
border: none;
border-radius: 0;
outline: none;
box-shadow: none;
box-sizing: border-box;
transition: border-color 0.1s ease-out;
&:hover {
border-color: var(--inputBorderHover);
&[type='file'] {
display: none;
}
}
> .prefix,
> .suffix {
display: flex;
align-items: center;
display: block;
position: absolute;
z-index: 1;
top: 0;
padding: 0 12px;
padding: 0 16px;
font-size: 1em;
height: $height;
line-height: $height;
color: var(--inputLabel);
pointer-events: none;
&:empty {
@@ -285,32 +267,25 @@ export default defineComponent({
> .prefix {
left: 0;
padding-right: 6px;
padding-right: 8px;
}
> .suffix {
right: 0;
padding-left: 6px;
padding-left: 8px;
}
}
&.inline {
display: inline-block;
margin: 0;
}
&.inline {
display: inline-block;
margin: 0;
}
&.focused {
> input {
border-color: var(--accent);
//box-shadow: 0 0 0 4px var(--focus);
}
}
&.disabled {
opacity: 0.7;
&.disabled {
opacity: 0.7;
&, * {
cursor: not-allowed !important;
}
&, * {
cursor: not-allowed !important;
}
}
}

View File

@@ -1,6 +1,6 @@
<template>
<div class="_formItem">
<div class="_formPanel anocepby">
<div class="_debobigegoItem">
<div class="_debobigegoPanel anocepby">
<span class="key"><slot name="key"></slot></span>
<span class="value"><slot name="value"></slot></span>
</div>
@@ -9,7 +9,7 @@
<script lang="ts">
import { defineComponent } from 'vue';
import './form.scss';
import './debobigego.scss';
export default defineComponent({
@@ -20,7 +20,7 @@ export default defineComponent({
.anocepby {
display: flex;
align-items: center;
padding: 14px var(--formContentHMargin);
padding: 14px var(--debobigegoContentHMargin);
> .key {
margin-right: 12px;

View File

@@ -1,6 +1,6 @@
<template>
<div class="qmfkfnzi _formItem">
<a class="main _button _formPanel _formClickable" :href="to" target="_blank" v-if="external">
<div class="qmfkfnzi _debobigegoItem">
<a class="main _button _debobigegoPanel _debobigegoClickable" :href="to" target="_blank" v-if="external">
<span class="icon"><slot name="icon"></slot></span>
<span class="text"><slot></slot></span>
<span class="right">
@@ -8,7 +8,7 @@
<i class="fas fa-external-link-alt icon"></i>
</span>
</a>
<MkA class="main _button _formPanel _formClickable" :class="{ active }" :to="to" :behavior="behavior" v-else>
<MkA class="main _button _debobigegoPanel _debobigegoClickable" :class="{ active }" :to="to" :behavior="behavior" v-else>
<span class="icon"><slot name="icon"></slot></span>
<span class="text"><slot></slot></span>
<span class="right">
@@ -21,7 +21,7 @@
<script lang="ts">
import { defineComponent } from 'vue';
import './form.scss';
import './debobigego.scss';
export default defineComponent({
props: {

View File

@@ -1,8 +1,8 @@
<template>
<FormGroup class="_formItem">
<FormGroup class="_debobigegoItem">
<template #label><slot></slot></template>
<div class="drooglns _formItem" :class="{ tall }">
<div class="input _formPanel">
<div class="drooglns _debobigegoItem" :class="{ tall }">
<div class="input _debobigegoPanel">
<textarea class="_monospace"
v-model="v"
readonly
@@ -17,7 +17,7 @@
<script lang="ts">
import { defineComponent, ref, toRefs, watch } from 'vue';
import * as JSON5 from 'json5';
import './form.scss';
import './debobigego.scss';
import FormGroup from './group.vue';
export default defineComponent({
@@ -75,7 +75,7 @@ export default defineComponent({
max-width: 100%;
min-height: 130px;
margin: 0;
padding: 16px var(--formContentHMargin);
padding: 16px var(--debobigegoContentHMargin);
box-sizing: border-box;
font: inherit;
font-weight: normal;

View File

@@ -1,5 +1,5 @@
<template>
<FormGroup class="uljviswt _formItem">
<FormGroup class="uljviswt _debobigegoItem">
<template #label><slot name="label"></slot></template>
<slot :items="items"></slot>
<div class="empty" v-if="empty" key="_empty_">

View File

@@ -0,0 +1,112 @@
<script lang="ts">
import { defineComponent, h } from 'vue';
import MkRadio from '@client/components/form/radio.vue';
import './debobigego.scss';
export default defineComponent({
components: {
MkRadio
},
props: {
modelValue: {
required: false
},
},
data() {
return {
value: this.modelValue,
}
},
watch: {
modelValue() {
this.value = this.modelValue;
},
value() {
this.$emit('update:modelValue', this.value);
}
},
render() {
const label = this.$slots.desc();
let options = this.$slots.default();
// なぜかFragmentになることがあるため
if (options.length === 1 && options[0].props == null) options = options[0].children;
return h('div', {
class: 'cnklmpwm _debobigegoItem'
}, [
h('div', {
class: '_debobigegoLabel',
}, label),
...options.map(option => h('button', {
class: '_button _debobigegoPanel _debobigegoClickable',
key: option.key,
onClick: () => this.value = option.props.value,
}, [h('span', {
class: ['check', { checked: this.value === option.props.value }],
}), option.children]))
]);
}
});
</script>
<style lang="scss">
.cnklmpwm {
> button {
display: block;
width: 100%;
box-sizing: border-box;
padding: 14px 18px;
text-align: left;
&:not(:first-of-type) {
border-top: none !important;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
&:not(:last-of-type) {
border-bottom: solid 0.5px var(--divider);
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
> .check {
display: inline-block;
vertical-align: bottom;
position: relative;
width: 16px;
height: 16px;
margin-right: 8px;
background: none;
border: 2px solid var(--inputBorder);
border-radius: 100%;
transition: inherit;
&:after {
content: "";
display: block;
position: absolute;
top: 3px;
right: 3px;
bottom: 3px;
left: 3px;
border-radius: 100%;
opacity: 0;
transform: scale(0);
transition: .4s cubic-bezier(.25,.8,.25,1);
}
&.checked {
border-color: var(--accent);
&:after {
background-color: var(--accent);
transform: scale(1);
opacity: 1;
}
}
}
}
}
</style>

View File

@@ -0,0 +1,122 @@
<template>
<div class="ifitouly _debobigegoItem" :class="{ focused, disabled }">
<div class="_debobigegoLabel"><slot name="label"></slot></div>
<div class="_debobigegoPanel main">
<input
type="range"
ref="input"
v-model="v"
:disabled="disabled"
:min="min"
:max="max"
:step="step"
@focus="focused = true"
@blur="focused = false"
@input="$emit('update:value', $event.target.value)"
/>
</div>
<div class="_debobigegoCaption"><slot name="caption"></slot></div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
props: {
value: {
type: Number,
required: false,
default: 0
},
disabled: {
type: Boolean,
required: false,
default: false
},
min: {
type: Number,
required: false,
default: 0
},
max: {
type: Number,
required: false,
default: 100
},
step: {
type: Number,
required: false,
default: 1
},
},
data() {
return {
v: this.value,
focused: false
};
},
watch: {
value(v) {
this.v = parseFloat(v);
}
},
});
</script>
<style lang="scss" scoped>
.ifitouly {
position: relative;
> .main {
padding: 22px 16px;
> input {
display: block;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background: var(--X10);
height: 4px;
width: 100%;
box-sizing: border-box;
margin: 0;
outline: 0;
border: 0;
border-radius: 7px;
&.disabled {
opacity: 0.6;
cursor: not-allowed;
}
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
cursor: pointer;
width: 20px;
height: 20px;
display: block;
border-radius: 50%;
border: none;
background: var(--accent);
box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
box-sizing: content-box;
}
&::-moz-range-thumb {
-moz-appearance: none;
appearance: none;
cursor: pointer;
width: 20px;
height: 20px;
display: block;
border-radius: 50%;
border: none;
background: var(--accent);
box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
}
}
}
}
</style>

View File

@@ -0,0 +1,145 @@
<template>
<div class="yrtfrpux _debobigegoItem" :class="{ disabled, inline }">
<div class="_debobigegoLabel"><slot name="label"></slot></div>
<div class="icon" ref="icon"><slot name="icon"></slot></div>
<div class="input _debobigegoPanel _debobigegoClickable" @click="focus">
<div class="prefix" ref="prefix"><slot name="prefix"></slot></div>
<select ref="input"
v-model="v"
:required="required"
:disabled="disabled"
@focus="focused = true"
@blur="focused = false"
>
<slot></slot>
</select>
<div class="suffix">
<i class="fas fa-chevron-down"></i>
</div>
</div>
<div class="_debobigegoCaption"><slot name="caption"></slot></div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import './debobigego.scss';
export default defineComponent({
props: {
modelValue: {
required: false
},
required: {
type: Boolean,
required: false
},
disabled: {
type: Boolean,
required: false
},
inline: {
type: Boolean,
required: false,
default: false
},
},
data() {
return {
};
},
computed: {
v: {
get() {
return this.modelValue;
},
set(v) {
this.$emit('update:modelValue', v);
}
},
},
methods: {
focus() {
this.$refs.input.focus();
}
}
});
</script>
<style lang="scss" scoped>
.yrtfrpux {
position: relative;
> .icon {
position: absolute;
top: 0;
left: 0;
width: 24px;
text-align: center;
line-height: 32px;
&:not(:empty) + .input {
margin-left: 28px;
}
}
> .input {
display: flex;
position: relative;
> select {
display: block;
flex: 1;
width: 100%;
padding: 0 16px;
font: inherit;
font-weight: normal;
font-size: 1em;
height: 48px;
background: none;
border: none;
border-radius: 0;
outline: none;
box-shadow: none;
appearance: none;
-webkit-appearance: none;
color: var(--fg);
option,
optgroup {
color: var(--fg);
background: var(--bg);
}
}
> .prefix,
> .suffix {
display: block;
align-self: center;
justify-self: center;
font-size: 1em;
line-height: 32px;
color: var(--inputLabel);
pointer-events: none;
&:empty {
display: none;
}
> * {
display: block;
min-width: 16px;
}
}
> .prefix {
padding-right: 4px;
}
> .suffix {
padding: 0 16px 0 0;
opacity: 0.7;
}
}
}
</style>

View File

@@ -1,15 +1,15 @@
<template>
<transition name="fade" mode="out-in">
<div class="_formItem" v-if="pending">
<div class="_formPanel">
<div class="_debobigegoItem" v-if="pending">
<div class="_debobigegoPanel">
<MkLoading/>
</div>
</div>
<div v-else-if="resolved" class="_formItem">
<div v-else-if="resolved" class="_debobigegoItem">
<slot :result="result"></slot>
</div>
<div class="_formItem" v-else>
<div class="_formPanel eiurkvay">
<div class="_debobigegoItem" v-else>
<div class="_debobigegoPanel eiurkvay">
<div><i class="fas fa-exclamation-triangle"></i> {{ $ts.somethingHappened }}</div>
<MkButton inline @click="retry" class="retry"><i class="fas fa-redo-alt"></i> {{ $ts.retry }}</MkButton>
</div>
@@ -19,7 +19,7 @@
<script lang="ts">
import { defineComponent, PropType, ref, watch } from 'vue';
import './form.scss';
import './debobigego.scss';
import MkButton from '@client/components/ui/button.vue';
export default defineComponent({

View File

@@ -0,0 +1,132 @@
<template>
<div class="ijnpvmgr _debobigegoItem">
<div class="main _debobigegoPanel _debobigegoClickable"
:class="{ disabled, checked }"
:aria-checked="checked"
:aria-disabled="disabled"
@click.prevent="toggle"
>
<input
type="checkbox"
ref="input"
:disabled="disabled"
@keydown.enter="toggle"
>
<span class="button" v-tooltip="checked ? $ts.itsOn : $ts.itsOff">
<span class="handle"></span>
</span>
<span class="label">
<span><slot></slot></span>
</span>
</div>
<div class="_debobigegoCaption"><slot name="desc"></slot></div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import './debobigego.scss';
export default defineComponent({
props: {
modelValue: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
}
},
computed: {
checked(): boolean {
return this.modelValue;
}
},
methods: {
toggle() {
if (this.disabled) return;
this.$emit('update:modelValue', !this.checked);
}
}
});
</script>
<style lang="scss" scoped>
.ijnpvmgr {
> .main {
position: relative;
display: flex;
padding: 14px 16px;
cursor: pointer;
> * {
user-select: none;
}
> input {
position: absolute;
width: 0;
height: 0;
opacity: 0;
margin: 0;
}
> .button {
position: relative;
display: inline-block;
flex-shrink: 0;
margin: 0;
width: 34px;
height: 22px;
background: var(--switchBg);
outline: none;
border-radius: 999px;
transition: all 0.3s;
cursor: pointer;
> .handle {
position: absolute;
top: 0;
left: 3px;
bottom: 0;
margin: auto 0;
border-radius: 100%;
transition: background-color 0.3s, transform 0.3s;
width: 16px;
height: 16px;
background-color: #fff;
pointer-events: none;
}
}
> .label {
margin-left: 12px;
display: block;
transition: inherit;
color: var(--fg);
> span {
display: block;
line-height: 20px;
transition: inherit;
}
}
&.disabled {
opacity: 0.6;
cursor: not-allowed;
}
&.checked {
> .button {
background-color: var(--accent);
> .handle {
transform: translateX(12px);
}
}
}
}
}
</style>

View File

@@ -0,0 +1,161 @@
<template>
<FormGroup class="_debobigegoItem">
<template #label><slot></slot></template>
<div class="rivhosbp _debobigegoItem" :class="{ tall, pre }">
<div class="input _debobigegoPanel">
<textarea ref="input" :class="{ code, _monospace: code }"
v-model="v"
:required="required"
:readonly="readonly"
:pattern="pattern"
:autocomplete="autocomplete"
:spellcheck="!code"
@input="onInput"
@focus="focused = true"
@blur="focused = false"
></textarea>
</div>
</div>
<template #caption><slot name="desc"></slot></template>
<FormButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
</FormGroup>
</template>
<script lang="ts">
import { defineComponent, ref, toRefs, watch } from 'vue';
import './debobigego.scss';
import FormButton from './button.vue';
import FormGroup from './group.vue';
export default defineComponent({
components: {
FormGroup,
FormButton,
},
props: {
modelValue: {
required: false
},
required: {
type: Boolean,
required: false
},
readonly: {
type: Boolean,
required: false
},
pattern: {
type: String,
required: false
},
autocomplete: {
type: String,
required: false
},
code: {
type: Boolean,
required: false
},
tall: {
type: Boolean,
required: false,
default: false
},
pre: {
type: Boolean,
required: false,
default: false
},
manualSave: {
type: Boolean,
required: false,
default: false
},
},
setup(props, context) {
const { modelValue } = toRefs(props);
const v = ref(modelValue.value);
const changed = ref(false);
const inputEl = ref(null);
const focus = () => inputEl.value.focus();
const onInput = (ev) => {
changed.value = true;
context.emit('change', ev);
};
const updated = () => {
changed.value = false;
context.emit('update:modelValue', v.value);
};
watch(modelValue.value, newValue => {
v.value = newValue;
});
watch(v, newValue => {
if (!props.manualSave) {
updated();
}
});
return {
v,
updated,
changed,
focus,
onInput,
};
}
});
</script>
<style lang="scss" scoped>
.rivhosbp {
position: relative;
> .input {
position: relative;
> textarea {
display: block;
width: 100%;
min-width: 100%;
max-width: 100%;
min-height: 130px;
margin: 0;
padding: 16px;
box-sizing: border-box;
font: inherit;
font-weight: normal;
font-size: 1em;
background: transparent;
border: none;
border-radius: 0;
outline: none;
box-shadow: none;
color: var(--fg);
&.code {
tab-size: 2;
}
}
}
&.tall {
> .input {
> textarea {
min-height: 200px;
}
}
}
&.pre {
> .input {
> textarea {
white-space: pre;
}
}
}
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<div class="wthhikgt _formItem" v-size="{ max: [500] }">
<div class="wthhikgt _debobigegoItem" v-size="{ max: [500] }">
<slot></slot>
</div>
</template>

View File

@@ -40,8 +40,8 @@
import { defineComponent } from 'vue';
import MkModal from '@client/components/ui/modal.vue';
import MkButton from '@client/components/ui/button.vue';
import MkInput from '@client/components/ui/input.vue';
import MkSelect from '@client/components/ui/select.vue';
import MkInput from '@client/components/form/input.vue';
import MkSelect from '@client/components/form/select.vue';
export default defineComponent({
components: {

View File

@@ -153,7 +153,7 @@ export default defineComponent({
height: var(--eachSize);
border-radius: 4px;
&:focus {
&:focus-visible {
outline: solid 2px var(--focus);
z-index: 1;
}

View File

@@ -465,7 +465,7 @@ export default defineComponent({
height: var(--eachSize);
border-radius: 4px;
&:focus {
&:focus-visible {
outline: solid 2px var(--focus);
z-index: 1;
}

View File

@@ -161,7 +161,7 @@ export default defineComponent({
width: 31px;
}
&:focus {
&:focus-visible {
&:after {
content: "";
pointer-events: none;

View File

@@ -35,7 +35,7 @@
import { defineComponent } from 'vue';
import XModalWindow from '@client/components/ui/modal-window.vue';
import MkButton from '@client/components/ui/button.vue';
import MkInput from '@client/components/ui/input.vue';
import MkInput from '@client/components/form/input.vue';
import * as os from '@client/os';
export default defineComponent({

View File

@@ -14,23 +14,23 @@
</template>
<FormBase class="xkpnjxcv">
<template v-for="item in Object.keys(form).filter(item => !form[item].hidden)">
<FormInput v-if="form[item].type === 'number'" v-model:value="values[item]" type="number" :step="form[item].step || 1">
<FormInput v-if="form[item].type === 'number'" v-model="values[item]" type="number" :step="form[item].step || 1">
<span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span>
<template v-if="form[item].description" #desc>{{ form[item].description }}</template>
</FormInput>
<FormInput v-else-if="form[item].type === 'string' && !form[item].multiline" v-model:value="values[item]" type="text">
<FormInput v-else-if="form[item].type === 'string' && !form[item].multiline" v-model="values[item]" type="text">
<span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span>
<template v-if="form[item].description" #desc>{{ form[item].description }}</template>
</FormInput>
<FormTextarea v-else-if="form[item].type === 'string' && form[item].multiline" v-model:value="values[item]">
<FormTextarea v-else-if="form[item].type === 'string' && form[item].multiline" v-model="values[item]">
<span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span>
<template v-if="form[item].description" #desc>{{ form[item].description }}</template>
</FormTextarea>
<FormSwitch v-else-if="form[item].type === 'boolean'" v-model:value="values[item]">
<FormSwitch v-else-if="form[item].type === 'boolean'" v-model="values[item]">
<span v-text="form[item].label || item"></span>
<template v-if="form[item].description" #desc>{{ form[item].description }}</template>
</FormSwitch>
<FormSelect v-else-if="form[item].type === 'enum'" v-model:value="values[item]">
<FormSelect v-else-if="form[item].type === 'enum'" v-model="values[item]">
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
<option v-for="item in form[item].enum" :value="item.value" :key="item.value">{{ item.label }}</option>
</FormSelect>
@@ -38,7 +38,7 @@
<template #desc><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
<option v-for="item in form[item].options" :value="item.value" :key="item.value">{{ item.label }}</option>
</FormRadios>
<FormRange v-else-if="form[item].type === 'range'" v-model:value="values[item]" :min="form[item].mim" :max="form[item].max" :step="form[item].step">
<FormRange v-else-if="form[item].type === 'range'" v-model="values[item]" :min="form[item].mim" :max="form[item].max" :step="form[item].step">
<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ $ts.optional }})</span></template>
<template v-if="form[item].description" #desc>{{ form[item].description }}</template>
</FormRange>
@@ -53,14 +53,14 @@
<script lang="ts">
import { defineComponent } from 'vue';
import XModalWindow from '@client/components/ui/modal-window.vue';
import FormBase from './form/base.vue';
import FormInput from './form/input.vue';
import FormTextarea from './form/textarea.vue';
import FormSwitch from './form/switch.vue';
import FormSelect from './form/select.vue';
import FormRange from './form/range.vue';
import FormButton from './form/button.vue';
import FormRadios from './form/radios.vue';
import FormBase from './debobigego/base.vue';
import FormInput from './debobigego/input.vue';
import FormTextarea from './debobigego/textarea.vue';
import FormSwitch from './debobigego/switch.vue';
import FormSelect from './debobigego/select.vue';
import FormRange from './debobigego/range.vue';
import FormButton from './debobigego/button.vue';
import FormRadios from './debobigego/radios.vue';
export default defineComponent({
components: {

View File

@@ -1,53 +1,49 @@
<template>
<FormGroup class="_formItem">
<template #label><slot></slot></template>
<div class="ztzhwixg _formItem" :class="{ inline, disabled }">
<div class="icon" ref="icon"><slot name="icon"></slot></div>
<div class="input _formPanel">
<div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div>
<input ref="inputEl"
:type="type"
v-model="v"
:disabled="disabled"
:required="required"
:readonly="readonly"
:placeholder="placeholder"
:pattern="pattern"
:autocomplete="autocomplete"
:spellcheck="spellcheck"
:step="step"
@focus="focused = true"
@blur="focused = false"
@keydown="onKeydown($event)"
@input="onInput"
:list="id"
>
<datalist :id="id" v-if="datalist">
<option v-for="data in datalist" :value="data"/>
</datalist>
<div class="suffix" ref="suffixEl"><slot name="suffix"></slot></div>
</div>
<div class="matxzzsk">
<div class="label" @click="focus"><slot name="label"></slot></div>
<div class="input" :class="{ inline, disabled, focused }">
<div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div>
<input ref="inputEl"
:type="type"
v-model="v"
:disabled="disabled"
:required="required"
:readonly="readonly"
:placeholder="placeholder"
:pattern="pattern"
:autocomplete="autocomplete"
:spellcheck="spellcheck"
:step="step"
@focus="focused = true"
@blur="focused = false"
@keydown="onKeydown($event)"
@input="onInput"
:list="id"
>
<datalist :id="id" v-if="datalist">
<option v-for="data in datalist" :value="data"/>
</datalist>
<div class="suffix" ref="suffixEl"><slot name="suffix"></slot></div>
</div>
<template #caption><slot name="desc"></slot></template>
<div class="caption"><slot name="caption"></slot></div>
<FormButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
</FormGroup>
<MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue';
import './form.scss';
import FormButton from './button.vue';
import FormGroup from './group.vue';
import MkButton from '../ui/button.vue';
import { debounce } from 'throttle-debounce';
export default defineComponent({
components: {
FormGroup,
FormButton,
MkButton,
},
props: {
value: {
required: false
modelValue: {
required: true
},
type: {
type: String,
@@ -96,16 +92,23 @@ export default defineComponent({
required: false,
default: false
},
debounce: {
type: Boolean,
required: false,
default: false
},
manualSave: {
type: Boolean,
required: false,
default: false
},
},
emits: ['change', 'keydown', 'enter'],
emits: ['change', 'keydown', 'enter', 'update:modelValue'],
setup(props, context) {
const { value, type, autofocus } = toRefs(props);
const v = ref(value.value);
const { modelValue, type, autofocus } = toRefs(props);
const v = ref(modelValue.value);
const id = Math.random().toString(); // TODO: uuid?
const focused = ref(false);
const changed = ref(false);
@@ -131,19 +134,25 @@ export default defineComponent({
const updated = () => {
changed.value = false;
if (type?.value === 'number') {
context.emit('update:value', parseFloat(v.value));
context.emit('update:modelValue', parseFloat(v.value));
} else {
context.emit('update:value', v.value);
context.emit('update:modelValue', v.value);
}
};
watch(value, newValue => {
const debouncedUpdated = debounce(1000, updated);
watch(modelValue, newValue => {
v.value = newValue;
});
watch(v, newValue => {
if (!props.manualSave) {
updated();
if (props.debounce) {
debouncedUpdated();
} else {
updated();
}
}
invalid.value = inputEl.value.validity.badInput;
@@ -196,59 +205,66 @@ export default defineComponent({
</script>
<style lang="scss" scoped>
.ztzhwixg {
position: relative;
.matxzzsk {
> .label {
font-size: 0.85em;
padding: 0 0 8px 12px;
user-select: none;
> .icon {
position: absolute;
top: 0;
left: 0;
width: 24px;
text-align: center;
line-height: 32px;
&:empty {
display: none;
}
}
&:not(:empty) + .input {
margin-left: 28px;
> .caption {
font-size: 0.8em;
padding: 8px 0 0 12px;
color: var(--fgTransparentWeak);
&:empty {
display: none;
}
}
> .input {
$height: 48px;
$height: 42px;
position: relative;
> input {
appearance: none;
-webkit-appearance: none;
display: block;
height: $height;
width: 100%;
margin: 0;
padding: 0 16px;
padding: 0 12px;
font: inherit;
font-weight: normal;
font-size: 1em;
line-height: $height;
color: var(--inputText);
background: transparent;
border: none;
border-radius: 0;
color: var(--fg);
background: var(--panel);
border: solid 0.5px var(--inputBorder);
border-radius: 6px;
outline: none;
box-shadow: none;
box-sizing: border-box;
transition: border-color 0.1s ease-out;
&[type='file'] {
display: none;
&:hover {
border-color: var(--inputBorderHover);
}
}
> .prefix,
> .suffix {
display: block;
display: flex;
align-items: center;
position: absolute;
z-index: 1;
top: 0;
padding: 0 16px;
padding: 0 12px;
font-size: 1em;
line-height: $height;
color: var(--inputLabel);
height: $height;
pointer-events: none;
&:empty {
@@ -267,25 +283,32 @@ export default defineComponent({
> .prefix {
left: 0;
padding-right: 8px;
padding-right: 6px;
}
> .suffix {
right: 0;
padding-left: 8px;
padding-left: 6px;
}
}
&.inline {
display: inline-block;
margin: 0;
}
&.inline {
display: inline-block;
margin: 0;
}
&.disabled {
opacity: 0.7;
&.focused {
> input {
border-color: var(--accent);
//box-shadow: 0 0 0 4px var(--focus);
}
}
&, * {
cursor: not-allowed !important;
&.disabled {
opacity: 0.7;
&, * {
cursor: not-allowed !important;
}
}
}
}

View File

@@ -1,7 +1,6 @@
<script lang="ts">
import { defineComponent, h } from 'vue';
import MkRadio from '@client/components/ui/radio.vue';
import './form.scss';
import MkRadio from './radio.vue';
export default defineComponent({
components: {
@@ -18,9 +17,6 @@ export default defineComponent({
}
},
watch: {
modelValue() {
this.value = this.modelValue;
},
value() {
this.$emit('update:modelValue', this.value);
}
@@ -33,80 +29,38 @@ export default defineComponent({
if (options.length === 1 && options[0].props == null) options = options[0].children;
return h('div', {
class: 'cnklmpwm _formItem'
class: 'novjtcto'
}, [
h('div', {
class: '_formLabel',
}, label),
...options.map(option => h('button', {
class: '_button _formPanel _formClickable',
h('div', { class: 'label' }, label),
...options.map(option => h(MkRadio, {
key: option.key,
onClick: () => this.value = option.props.value,
}, [h('span', {
class: ['check', { checked: this.value === option.props.value }],
}), option.children]))
value: option.props.value,
modelValue: this.value,
'onUpdate:modelValue': value => this.value = value,
}, option.children))
]);
}
});
</script>
<style lang="scss">
.cnklmpwm {
> button {
display: block;
width: 100%;
box-sizing: border-box;
padding: 14px 18px;
text-align: left;
.novjtcto {
> .label {
font-size: 0.85em;
padding: 0 0 8px 12px;
user-select: none;
&:not(:first-of-type) {
border-top: none !important;
border-top-left-radius: 0;
border-top-right-radius: 0;
&:empty {
display: none;
}
}
&:not(:last-of-type) {
border-bottom: solid 0.5px var(--divider);
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
&:first-child {
margin-top: 0;
}
> .check {
display: inline-block;
vertical-align: bottom;
position: relative;
width: 16px;
height: 16px;
margin-right: 8px;
background: none;
border: 2px solid var(--inputBorder);
border-radius: 100%;
transition: inherit;
&:after {
content: "";
display: block;
position: absolute;
top: 3px;
right: 3px;
bottom: 3px;
left: 3px;
border-radius: 100%;
opacity: 0;
transform: scale(0);
transition: .4s cubic-bezier(.25,.8,.25,1);
}
&.checked {
border-color: var(--accent);
&:after {
background-color: var(--accent);
transform: scale(1);
opacity: 1;
}
}
}
&:last-child {
margin-bottom: 0;
}
}
</style>

View File

@@ -1,21 +1,20 @@
<template>
<div class="ifitouly _formItem" :class="{ focused, disabled }">
<div class="_formLabel"><slot name="label"></slot></div>
<div class="_formPanel main">
<input
type="range"
ref="input"
v-model="v"
:disabled="disabled"
:min="min"
:max="max"
:step="step"
@focus="focused = true"
@blur="focused = false"
@input="$emit('update:value', $event.target.value)"
/>
</div>
<div class="_formCaption"><slot name="caption"></slot></div>
<div class="timctyfi" :class="{ focused, disabled }">
<div class="icon"><slot name="icon"></slot></div>
<span class="label"><slot name="label"></slot></span>
<input
type="range"
ref="input"
v-model="v"
:disabled="disabled"
:min="min"
:max="max"
:step="step"
:autofocus="autofocus"
@focus="focused = true"
@blur="focused = false"
@input="$emit('update:value', $event.target.value)"
/>
</div>
</template>
@@ -49,6 +48,10 @@ export default defineComponent({
required: false,
default: 1
},
autofocus: {
type: Boolean,
required: false
}
},
data() {
return {
@@ -61,61 +64,75 @@ export default defineComponent({
this.v = parseFloat(v);
}
},
mounted() {
if (this.autofocus) {
this.$nextTick(() => {
this.$refs.input.focus();
});
}
}
});
</script>
<style lang="scss" scoped>
.ifitouly {
.timctyfi {
position: relative;
margin: 8px;
> .main {
padding: 22px 16px;
> .icon {
display: inline-block;
width: 24px;
text-align: center;
}
> input {
display: block;
> .title {
pointer-events: none;
font-size: 16px;
color: var(--inputLabel);
overflow: hidden;
}
> input {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background: var(--X10);
height: 7px;
margin: 0 8px;
outline: 0;
border: 0;
border-radius: 7px;
&.disabled {
opacity: 0.6;
cursor: not-allowed;
}
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
cursor: pointer;
width: 20px;
height: 20px;
display: block;
border-radius: 50%;
border: none;
background: var(--accent);
box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
box-sizing: content-box;
}
&::-moz-range-thumb {
-moz-appearance: none;
appearance: none;
background: var(--X10);
height: 4px;
width: 100%;
box-sizing: border-box;
margin: 0;
outline: 0;
border: 0;
border-radius: 7px;
&.disabled {
opacity: 0.6;
cursor: not-allowed;
}
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
cursor: pointer;
width: 20px;
height: 20px;
display: block;
border-radius: 50%;
border: none;
background: var(--accent);
box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
box-sizing: content-box;
}
&::-moz-range-thumb {
-moz-appearance: none;
appearance: none;
cursor: pointer;
width: 20px;
height: 20px;
display: block;
border-radius: 50%;
border: none;
background: var(--accent);
box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
}
cursor: pointer;
width: 20px;
height: 20px;
display: block;
border-radius: 50%;
border: none;
background: var(--accent);
box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
}
}
}

View File

@@ -0,0 +1,31 @@
<template>
<div class="vrtktovh" v-size="{ max: [500] }" v-sticky-container>
<div class="label"><slot name="label"></slot></div>
<div class="main">
<slot></slot>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
});
</script>
<style lang="scss" scoped>
.vrtktovh {
border-top: solid 0.5px var(--divider);
> .label {
font-weight: bold;
padding: 24px 0 16px 0;
}
> .main {
margin-bottom: 32px;
}
}
</style>

View File

@@ -1,125 +1,216 @@
<template>
<div class="yrtfrpux _formItem" :class="{ disabled, inline }">
<div class="_formLabel"><slot name="label"></slot></div>
<div class="icon" ref="icon"><slot name="icon"></slot></div>
<div class="input _formPanel _formClickable" @click="focus">
<div class="prefix" ref="prefix"><slot name="prefix"></slot></div>
<select ref="input"
<div class="vblkjoeq">
<div class="label" @click="focus"><slot name="label"></slot></div>
<div class="input" :class="{ inline, disabled, focused }">
<div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div>
<select ref="inputEl"
v-model="v"
:required="required"
:disabled="disabled"
:required="required"
:readonly="readonly"
:placeholder="placeholder"
@focus="focused = true"
@blur="focused = false"
@input="onInput"
>
<slot></slot>
</select>
<div class="suffix">
<i class="fas fa-chevron-down"></i>
</div>
<div class="suffix" ref="suffixEl"><i class="fas fa-chevron-down"></i></div>
</div>
<div class="_formCaption"><slot name="caption"></slot></div>
<div class="caption"><slot name="caption"></slot></div>
<MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import './form.scss';
import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue';
import MkButton from '../ui/button.vue';
export default defineComponent({
components: {
MkButton,
},
props: {
value: {
required: false
modelValue: {
required: true
},
required: {
type: Boolean,
required: false
},
readonly: {
type: Boolean,
required: false
},
disabled: {
type: Boolean,
required: false
},
placeholder: {
type: String,
required: false
},
autofocus: {
type: Boolean,
required: false,
default: false
},
inline: {
type: Boolean,
required: false,
default: false
},
},
data() {
return {
};
},
computed: {
v: {
get() {
return this.value;
},
set(v) {
this.$emit('update:value', v);
}
manualSave: {
type: Boolean,
required: false,
default: false
},
},
methods: {
focus() {
this.$refs.input.focus();
}
}
emits: ['change', 'update:modelValue'],
setup(props, context) {
const { modelValue, autofocus } = toRefs(props);
const v = ref(modelValue.value);
const focused = ref(false);
const changed = ref(false);
const invalid = ref(false);
const filled = computed(() => v.value !== '' && v.value != null);
const inputEl = ref(null);
const prefixEl = ref(null);
const suffixEl = ref(null);
const focus = () => inputEl.value.focus();
const onInput = (ev) => {
changed.value = true;
context.emit('change', ev);
};
const updated = () => {
changed.value = false;
context.emit('update:modelValue', v.value);
};
watch(modelValue, newValue => {
v.value = newValue;
});
watch(v, newValue => {
if (!props.manualSave) {
updated();
}
invalid.value = inputEl.value.validity.badInput;
});
onMounted(() => {
nextTick(() => {
if (autofocus.value) {
focus();
}
// このコンポーネントが作成された時、非表示状態である場合がある
// 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する
const clock = setInterval(() => {
if (prefixEl.value) {
if (prefixEl.value.offsetWidth) {
inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px';
}
}
if (suffixEl.value) {
if (suffixEl.value.offsetWidth) {
inputEl.value.style.paddingRight = suffixEl.value.offsetWidth + 'px';
}
}
}, 100);
onUnmounted(() => {
clearInterval(clock);
});
});
});
return {
v,
focused,
invalid,
changed,
filled,
inputEl,
prefixEl,
suffixEl,
focus,
onInput,
updated,
};
},
});
</script>
<style lang="scss" scoped>
.yrtfrpux {
position: relative;
.vblkjoeq {
> .label {
font-size: 0.85em;
padding: 0 0 8px 12px;
user-select: none;
> .icon {
position: absolute;
top: 0;
left: 0;
width: 24px;
text-align: center;
line-height: 32px;
&:empty {
display: none;
}
}
&:not(:empty) + .input {
margin-left: 28px;
> .caption {
font-size: 0.8em;
padding: 8px 0 0 12px;
color: var(--fgTransparentWeak);
&:empty {
display: none;
}
}
> .input {
display: flex;
$height: 42px;
position: relative;
> select {
appearance: none;
-webkit-appearance: none;
display: block;
flex: 1;
height: $height;
width: 100%;
padding: 0 16px;
margin: 0;
padding: 0 12px;
font: inherit;
font-weight: normal;
font-size: 1em;
height: 48px;
background: none;
border: none;
border-radius: 0;
color: var(--fg);
background: var(--panel);
border: solid 1px var(--inputBorder);
border-radius: 6px;
outline: none;
box-shadow: none;
appearance: none;
-webkit-appearance: none;
color: var(--fg);
box-sizing: border-box;
cursor: pointer;
transition: border-color 0.1s ease-out;
option,
optgroup {
color: var(--fg);
background: var(--bg);
&:hover {
border-color: var(--inputBorderHover);
}
}
> .prefix,
> .suffix {
display: block;
align-self: center;
justify-self: center;
display: flex;
align-items: center;
position: absolute;
z-index: 1;
top: 0;
padding: 0 12px;
font-size: 1em;
line-height: 32px;
color: var(--inputLabel);
height: $height;
pointer-events: none;
&:empty {
@@ -127,18 +218,42 @@ export default defineComponent({
}
> * {
display: block;
display: inline-block;
min-width: 16px;
max-width: 150px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
> .prefix {
padding-right: 4px;
left: 0;
padding-right: 6px;
}
> .suffix {
padding: 0 16px 0 0;
right: 0;
padding-left: 6px;
}
&.inline {
display: inline-block;
margin: 0;
}
&.focused {
> select {
border-color: var(--accent);
}
}
&.disabled {
opacity: 0.7;
&, * {
cursor: not-allowed !important;
}
}
}
}

View File

@@ -0,0 +1,50 @@
<template>
<div class="adhpbeou">
<div class="label" @click="focus"><slot name="label"></slot></div>
<div class="content">
<slot></slot>
</div>
<div class="caption"><slot name="caption"></slot></div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
});
</script>
<style lang="scss" scoped>
.adhpbeou {
margin: 1.5em 0;
> .label {
font-size: 0.85em;
padding: 0 0 8px 12px;
user-select: none;
&:empty {
display: none;
}
}
> .caption {
font-size: 0.8em;
padding: 8px 0 0 12px;
color: var(--fgTransparentWeak);
&:empty {
display: none;
}
}
> .content {
position: relative;
background: var(--panel);
border: solid 0.5px var(--inputBorder);
border-radius: 6px;
}
}
</style>

View File

@@ -1,35 +1,34 @@
<template>
<div class="ijnpvmgr _formItem">
<div class="main _formPanel _formClickable"
:class="{ disabled, checked }"
:aria-checked="checked"
:aria-disabled="disabled"
@click.prevent="toggle"
<div
class="ziffeoms"
:class="{ disabled, checked }"
role="switch"
:aria-checked="checked"
:aria-disabled="disabled"
@click.prevent="toggle"
>
<input
type="checkbox"
ref="input"
:disabled="disabled"
@keydown.enter="toggle"
>
<input
type="checkbox"
ref="input"
:disabled="disabled"
@keydown.enter="toggle"
>
<span class="button">
<span></span>
</span>
<span class="label">
<span><slot></slot></span>
</span>
</div>
<div class="_formCaption"><slot name="desc"></slot></div>
<span class="button" v-tooltip="checked ? $ts.itsOn : $ts.itsOff">
<span class="handle"></span>
</span>
<span class="label">
<span><slot></slot></span>
<p><slot name="caption"></slot></p>
</span>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import './form.scss';
export default defineComponent({
props: {
value: {
modelValue: {
type: Boolean,
default: false
},
@@ -40,91 +39,110 @@ export default defineComponent({
},
computed: {
checked(): boolean {
return this.value;
return this.modelValue;
}
},
methods: {
toggle() {
if (this.disabled) return;
this.$emit('update:value', !this.checked);
this.$emit('update:modelValue', !this.checked);
}
}
});
</script>
<style lang="scss" scoped>
.ijnpvmgr {
> .main {
.ziffeoms {
position: relative;
display: flex;
cursor: pointer;
transition: all 0.3s;
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
> * {
user-select: none;
}
> input {
position: absolute;
width: 0;
height: 0;
opacity: 0;
margin: 0;
}
> .button {
position: relative;
display: flex;
padding: 14px 16px;
cursor: pointer;
display: inline-block;
flex-shrink: 0;
margin: 0;
width: 36px;
height: 26px;
background: var(--switchBg);
outline: none;
border-radius: 999px;
transition: inherit;
> * {
user-select: none;
}
&.disabled {
opacity: 0.6;
cursor: not-allowed;
}
&.checked {
> .button {
background-color: var(--X10);
border-color: var(--X10);
> * {
background-color: var(--accent);
transform: translateX(14px);
}
}
}
> input {
> .handle {
position: absolute;
width: 0;
height: 0;
opacity: 0;
margin: 0;
top: 0;
bottom: 0;
left: 5px;
margin: auto 0;
border-radius: 100%;
transition: background-color 0.3s, transform 0.3s;
width: 16px;
height: 16px;
background-color: #fff;
}
}
> .button {
position: relative;
display: inline-block;
flex-shrink: 0;
margin: 3px 0 0 0;
width: 34px;
height: 14px;
background: var(--X6);
outline: none;
border-radius: 14px;
transition: all 0.3s;
cursor: pointer;
> .label {
margin-left: 16px;
margin-top: 2px;
display: block;
cursor: pointer;
transition: inherit;
color: var(--fg);
> * {
position: absolute;
top: -3px;
left: 0;
border-radius: 100%;
transition: background-color 0.3s, transform 0.3s;
width: 20px;
height: 20px;
background-color: #fff;
box-shadow: 0 2px 1px -1px rgba(#000, 0.2), 0 1px 1px 0 rgba(#000, 0.14), 0 1px 3px 0 rgba(#000, 0.12);
}
}
> .label {
margin-left: 12px;
> span {
display: block;
line-height: 20px;
transition: inherit;
color: var(--fg);
}
> span {
display: block;
line-height: 20px;
transition: inherit;
> p {
margin: 0;
color: var(--fgTransparentWeak);
font-size: 90%;
}
}
&:hover {
> .button {
background-color: var(--accentedBg);
}
}
&.disabled {
opacity: 0.6;
cursor: not-allowed;
}
&.checked {
> .button {
background-color: var(--accent);
border-color: var(--accent);
> .handle {
transform: translateX(10px);
}
}
}

View File

@@ -1,40 +1,45 @@
<template>
<FormGroup class="_formItem">
<template #label><slot></slot></template>
<div class="rivhosbp _formItem" :class="{ tall, pre }">
<div class="input _formPanel">
<textarea ref="input" :class="{ code, _monospace: code }"
v-model="v"
:required="required"
:readonly="readonly"
:pattern="pattern"
:autocomplete="autocomplete"
:spellcheck="!code"
@input="onInput"
@focus="focused = true"
@blur="focused = false"
></textarea>
</div>
<div class="adhpbeos">
<div class="label" @click="focus"><slot name="label"></slot></div>
<div class="input" :class="{ disabled, focused, tall, pre }">
<textarea ref="inputEl"
:class="{ code, _monospace: code }"
v-model="v"
:disabled="disabled"
:required="required"
:readonly="readonly"
:placeholder="placeholder"
:pattern="pattern"
:autocomplete="autocomplete"
:spellcheck="spellcheck"
@focus="focused = true"
@blur="focused = false"
@keydown="onKeydown($event)"
@input="onInput"
></textarea>
</div>
<template #caption><slot name="desc"></slot></template>
<div class="caption"><slot name="caption"></slot></div>
<FormButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
</FormGroup>
<MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, toRefs, watch } from 'vue';
import './form.scss';
import FormButton from './button.vue';
import FormGroup from './group.vue';
import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue';
import MkButton from '../ui/button.vue';
import { debounce } from 'throttle-debounce';
export default defineComponent({
components: {
FormGroup,
FormButton,
MkButton,
},
props: {
value: {
modelValue: {
required: true
},
type: {
type: String,
required: false
},
required: {
@@ -45,14 +50,29 @@ export default defineComponent({
type: Boolean,
required: false
},
disabled: {
type: Boolean,
required: false
},
pattern: {
type: String,
required: false
},
autocomplete: {
placeholder: {
type: String,
required: false
},
autofocus: {
type: Boolean,
required: false,
default: false
},
autocomplete: {
required: false
},
spellcheck: {
required: false
},
code: {
type: Boolean,
required: false
@@ -67,91 +87,162 @@ export default defineComponent({
required: false,
default: false
},
debounce: {
type: Boolean,
required: false,
default: false
},
manualSave: {
type: Boolean,
required: false,
default: false
},
},
emits: ['change', 'keydown', 'enter', 'update:modelValue'],
setup(props, context) {
const { value } = toRefs(props);
const v = ref(value.value);
const { modelValue, autofocus } = toRefs(props);
const v = ref(modelValue.value);
const focused = ref(false);
const changed = ref(false);
const invalid = ref(false);
const filled = computed(() => v.value !== '' && v.value != null);
const inputEl = ref(null);
const focus = () => inputEl.value.focus();
const onInput = (ev) => {
changed.value = true;
context.emit('change', ev);
};
const onKeydown = (ev: KeyboardEvent) => {
context.emit('keydown', ev);
if (ev.code === 'Enter') {
context.emit('enter');
}
};
const updated = () => {
changed.value = false;
context.emit('update:value', v.value);
context.emit('update:modelValue', v.value);
};
watch(value, newValue => {
const debouncedUpdated = debounce(1000, updated);
watch(modelValue, newValue => {
v.value = newValue;
});
watch(v, newValue => {
if (!props.manualSave) {
updated();
if (props.debounce) {
debouncedUpdated();
} else {
updated();
}
}
invalid.value = inputEl.value.validity.badInput;
});
onMounted(() => {
nextTick(() => {
if (autofocus.value) {
focus();
}
});
});
return {
v,
updated,
focused,
invalid,
changed,
filled,
inputEl,
focus,
onInput,
onKeydown,
updated,
};
}
},
});
</script>
<style lang="scss" scoped>
.rivhosbp {
position: relative;
.adhpbeos {
> .label {
font-size: 0.85em;
padding: 0 0 8px 12px;
user-select: none;
&:empty {
display: none;
}
}
> .caption {
font-size: 0.8em;
padding: 8px 0 0 12px;
color: var(--fgTransparentWeak);
&:empty {
display: none;
}
}
> .input {
position: relative;
> textarea {
appearance: none;
-webkit-appearance: none;
display: block;
width: 100%;
min-width: 100%;
max-width: 100%;
min-height: 130px;
margin: 0;
padding: 16px;
box-sizing: border-box;
padding: 12px;
font: inherit;
font-weight: normal;
font-size: 1em;
background: transparent;
border: none;
border-radius: 0;
color: var(--fg);
background: var(--panel);
border: solid 0.5px var(--inputBorder);
border-radius: 6px;
outline: none;
box-shadow: none;
color: var(--fg);
box-sizing: border-box;
transition: border-color 0.1s ease-out;
&.code {
tab-size: 2;
&:hover {
border-color: var(--inputBorderHover);
}
}
}
&.tall {
> .input {
&.focused {
> textarea {
border-color: var(--accent);
}
}
&.disabled {
opacity: 0.7;
&, * {
cursor: not-allowed !important;
}
}
&.tall {
> textarea {
min-height: 200px;
}
}
}
&.pre {
> .input {
&.pre {
> textarea {
white-space: pre;
}

View File

@@ -36,7 +36,7 @@
<script lang="ts">
import { defineComponent, markRaw } from 'vue';
import Chart from 'chart.js';
import MkSelect from './ui/select.vue';
import MkSelect from './form/select.vue';
import number from '@client/filters/number';
const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));

View File

@@ -3,10 +3,13 @@
<div class="container">
<div class="fullwidth top-caption">
<div class="mk-dialog">
<header v-if="title"><Mfm :text="title"/></header>
<header>
<Mfm v-if="title" class="title" :text="title"/>
<span class="text-count" :class="{ over: remainingLength < 0 }">{{ remainingLength }}</span>
</header>
<textarea autofocus v-model="inputValue" :placeholder="input.placeholder" @keydown="onInputKeydown"></textarea>
<div class="buttons" v-if="(showOkButton || showCancelButton)">
<MkButton inline @click="ok" primary>{{ $ts.ok }}</MkButton>
<MkButton inline @click="ok" primary :disabled="remainingLength < 0">{{ $ts.ok }}</MkButton>
<MkButton inline @click="cancel" >{{ $ts.cancel }}</MkButton>
</div>
</div>
@@ -26,10 +29,12 @@
<script lang="ts">
import { defineComponent } from 'vue';
import { length } from 'stringz';
import MkModal from '@client/components/ui/modal.vue';
import MkButton from '@client/components/ui/button.vue';
import bytes from '@client/filters/bytes';
import number from '@client/filters/number';
import { DB_MAX_IMAGE_COMMENT_LENGTH } from '@/misc/hard-limits';
export default defineComponent({
components: {
@@ -79,6 +84,13 @@ export default defineComponent({
document.removeEventListener('keydown', this.onKeydown);
},
computed: {
remainingLength(): number {
if (typeof this.inputValue != "string") return DB_MAX_IMAGE_COMMENT_LENGTH;
return DB_MAX_IMAGE_COMMENT_LENGTH - length(this.inputValue);
}
},
methods: {
bytes,
number,
@@ -156,8 +168,18 @@ export default defineComponent({
> header {
margin: 0 0 8px 0;
font-weight: bold;
font-size: 20px;
position: relative;
> .title {
font-weight: bold;
font-size: 20px;
}
> .text-count {
opacity: 0.7;
position: absolute;
right: 0;
}
}
> .buttons {
@@ -184,7 +206,7 @@ export default defineComponent({
min-width: 100%;
min-height: 90px;
&:focus {
&:focus-visible {
outline: none;
}

View File

@@ -185,7 +185,7 @@ export default defineComponent({
}
}
if (style == null) {
return h('span', {}, ['[', token.props.name, ...genEl(token.children), ']']);
return h('span', {}, ['[', token.props.name, ' ', ...genEl(token.children), ']']);
} else {
return h('span', {
style: 'display: inline-block;' + style,

View File

@@ -59,7 +59,7 @@
<div class="body">
<p v-if="appearNote.cw != null" class="cw">
<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
<XCwButton v-model:value="showContent" :note="appearNote"/>
<XCwButton v-model="showContent" :note="appearNote"/>
</p>
<div class="content" v-show="appearNote.cw == null || showContent">
<div class="text">

View File

@@ -6,7 +6,7 @@
<div class="body">
<p v-if="note.cw != null" class="cw">
<span class="text" v-if="note.cw != ''">{{ note.cw }}</span>
<XCwButton v-model:value="showContent" :note="note"/>
<XCwButton v-model="showContent" :note="note"/>
</p>
<div class="content" v-show="note.cw == null || showContent">
<XSubNote-content class="text" :note="note"/>

View File

@@ -7,7 +7,7 @@
<div class="body">
<p v-if="note.cw != null" class="cw">
<Mfm v-if="note.cw != ''" class="text" :text="note.cw" :author="note.user" :i="$i" :custom-emojis="note.emojis" />
<XCwButton v-model:value="showContent" :note="note"/>
<XCwButton v-model="showContent" :note="note"/>
</p>
<div class="content" v-show="note.cw == null || showContent">
<XSubNote-content class="text" :note="note"/>

View File

@@ -43,7 +43,7 @@
<div class="body">
<p v-if="appearNote.cw != null" class="cw">
<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i" :custom-emojis="appearNote.emojis"/>
<XCwButton v-model:value="showContent" :note="appearNote"/>
<XCwButton v-model="showContent" :note="appearNote"/>
</p>
<div class="content" :class="{ collapsed }" v-show="appearNote.cw == null || showContent">
<div class="text">
@@ -888,7 +888,7 @@ export default defineComponent({
//content-visibility: auto;
//contain-intrinsic-size: 0 128px;
&:focus {
&:focus-visible {
outline: none;
&:after {

View File

@@ -29,7 +29,7 @@
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import XModalWindow from '@client/components/ui/modal-window.vue';
import MkSwitch from './ui/switch.vue';
import MkSwitch from './form/switch.vue';
import MkInfo from './ui/info.vue';
import MkButton from './ui/button.vue';
import { notificationTypes } from '../../types';

View File

@@ -8,7 +8,7 @@
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import MkInput from '../ui/input.vue';
import MkInput from '../form/input.vue';
import * as os from '@client/os';
import { Hpml } from '@client/scripts/hpml/evaluator';
import { NumberInputVarBlock } from '@client/scripts/hpml/block';

View File

@@ -10,7 +10,7 @@
<script lang="ts">
import { defineComponent, PropType } from 'vue';
import MkTextarea from '../ui/textarea.vue';
import MkTextarea from '../form/textarea.vue';
import MkButton from '../ui/button.vue';
import { apiUrl } from '@client/config';
import * as os from '@client/os';

View File

@@ -7,7 +7,7 @@
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import MkRadio from '../ui/radio.vue';
import MkRadio from '../form/radio.vue';
import * as os from '@client/os';
import { Hpml } from '@client/scripts/hpml/evaluator';
import { RadioButtonVarBlock } from '@client/scripts/hpml/block';

View File

@@ -6,7 +6,7 @@
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import MkSwitch from '../ui/switch.vue';
import MkSwitch from '../form/switch.vue';
import * as os from '@client/os';
import { Hpml } from '@client/scripts/hpml/evaluator';
import { SwitchVarBlock } from '@client/scripts/hpml/block';

View File

@@ -8,7 +8,7 @@
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import MkInput from '../ui/input.vue';
import MkInput from '../form/input.vue';
import * as os from '@client/os';
import { Hpml } from '@client/scripts/hpml/evaluator';
import { TextInputVarBlock } from '@client/scripts/hpml/block';

View File

@@ -8,7 +8,7 @@
<script lang="ts">
import { computed, defineComponent, PropType } from 'vue';
import MkTextarea from '../ui/textarea.vue';
import MkTextarea from '../form/textarea.vue';
import * as os from '@client/os';
import { Hpml } from '@client/scripts/hpml/evaluator';
import { HpmlTextInput } from '@client/scripts/hpml';

View File

@@ -6,7 +6,7 @@
import { TextBlock } from '@client/scripts/hpml/block';
import { Hpml } from '@client/scripts/hpml/evaluator';
import { defineComponent, PropType } from 'vue';
import MkTextarea from '../ui/textarea.vue';
import MkTextarea from '../form/textarea.vue';
export default defineComponent({
components: {

View File

@@ -51,9 +51,9 @@
import { defineComponent } from 'vue';
import { addTime } from '../../prelude/time';
import { formatDateTimeString } from '@/misc/format-time-string';
import MkInput from './ui/input.vue';
import MkSelect from './ui/select.vue';
import MkSwitch from './ui/switch.vue';
import MkInput from './form/input.vue';
import MkSelect from './form/select.vue';
import MkSwitch from './form/switch.vue';
import MkButton from './ui/button.vue';
export default defineComponent({

View File

@@ -845,7 +845,7 @@ export default defineComponent({
color: var(--fg);
font-family: inherit;
&:focus {
&:focus-visible {
outline: none;
}

View File

@@ -30,10 +30,10 @@
<script lang="ts">
import { defineComponent } from 'vue';
import MkButton from '@client/components/ui/button.vue';
import MkInput from '@client/components/ui/input.vue';
import MkSwitch from '@client/components/ui/switch.vue';
import MkTextarea from '@client/components/ui/textarea.vue';
import MkRadio from '@client/components/ui/radio.vue';
import MkInput from '@client/components/form/input.vue';
import MkSwitch from '@client/components/form/switch.vue';
import MkTextarea from '@client/components/form/textarea.vue';
import MkRadio from '@client/components/form/radio.vue';
import * as os from '@client/os';
import * as config from '@client/config';

View File

@@ -1,17 +1,17 @@
<template>
<form class="eppvobhk _monolithic_" :class="{ signing, totpLogin }" @submit.prevent="onSubmit">
<div class="auth _section">
<div class="auth _section _formRoot">
<div class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }" v-show="withAvatar"></div>
<div class="normal-signin" v-if="!totpLogin">
<MkInput v-model="username" :placeholder="$ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @update:modelValue="onUsernameChange" data-cy-signin-username>
<MkInput class="_formBlock" v-model="username" :placeholder="$ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @update:modelValue="onUsernameChange" data-cy-signin-username>
<template #prefix>@</template>
<template #suffix>@{{ host }}</template>
</MkInput>
<MkInput v-model="password" :placeholder="$ts.password" type="password" :with-password-toggle="true" v-if="!user || user && !user.usePasswordLessLogin" required data-cy-signin-password>
<MkInput class="_formBlock" v-model="password" :placeholder="$ts.password" type="password" :with-password-toggle="true" v-if="!user || user && !user.usePasswordLessLogin" required data-cy-signin-password>
<template #prefix><i class="fas fa-lock"></i></template>
<template #caption><button class="_textButton" @click="resetPassword" type="button">{{ $ts.forgotPassword }}</button></template>
</MkInput>
<MkButton type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton>
<MkButton class="_formBlock" type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton>
</div>
<div class="2fa-signin" v-if="totpLogin" :class="{ securityKeys: user && user.securityKeys }">
<div v-if="user && user.securityKeys" class="twofa-group tap-group">
@@ -49,7 +49,7 @@
import { defineComponent } from 'vue';
import { toUnicode } from 'punycode/';
import MkButton from '@client/components/ui/button.vue';
import MkInput from '@client/components/ui/input.vue';
import MkInput from '@client/components/form/input.vue';
import { apiUrl, host } from '@client/config';
import { byteify, hexify } from '@client/scripts/2fa';
import * as os from '@client/os';

View File

@@ -1,11 +1,11 @@
<template>
<form class="qlvuhzng" @submit.prevent="onSubmit" :autocomplete="Math.random()">
<form class="qlvuhzng _formRoot" @submit.prevent="onSubmit" :autocomplete="Math.random()">
<template v-if="meta">
<MkInput class="_inputNoTopMargin" v-if="meta.disableRegistration" v-model="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required>
<MkInput class="_formBlock" v-if="meta.disableRegistration" v-model="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required>
<template #label>{{ $ts.invitationCode }}</template>
<template #prefix><i class="fas fa-key"></i></template>
</MkInput>
<MkInput class="_inputNoTopMargin" v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @update:modelValue="onChangeUsername" data-cy-signup-username>
<MkInput class="_formBlock" v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @update:modelValue="onChangeUsername" data-cy-signup-username>
<template #label>{{ $ts.username }} <div class="_button _help" v-tooltip:dialog="$ts.usernameInfo"><i class="far fa-question-circle"></i></div></template>
<template #prefix>@</template>
<template #suffix>@{{ host }}</template>
@@ -19,7 +19,7 @@
<span v-if="usernameState == 'max-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooLong }}</span>
</template>
</MkInput>
<MkInput v-model="password" type="password" :autocomplete="Math.random()" required @update:modelValue="onChangePassword" data-cy-signup-password>
<MkInput class="_formBlock" v-model="password" type="password" :autocomplete="Math.random()" required @update:modelValue="onChangePassword" data-cy-signup-password>
<template #label>{{ $ts.password }}</template>
<template #prefix><i class="fas fa-lock"></i></template>
<template #caption>
@@ -28,7 +28,7 @@
<span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.strongPassword }}</span>
</template>
</MkInput>
<MkInput v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @update:modelValue="onChangePasswordRetype" data-cy-signup-password-retype>
<MkInput class="_formBlock" v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @update:modelValue="onChangePasswordRetype" data-cy-signup-password-retype>
<template #label>{{ $ts.password }} ({{ $ts.retype }})</template>
<template #prefix><i class="fas fa-lock"></i></template>
<template #caption>
@@ -36,7 +36,7 @@
<span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.passwordNotMatched }}</span>
</template>
</MkInput>
<label v-if="meta.tosUrl" class="tou">
<label v-if="meta.tosUrl" class="_formBlock tou">
<input type="checkbox" v-model="ToSAgreement">
<I18n :src="$ts.agreeTo">
<template #0>
@@ -44,9 +44,9 @@
</template>
</I18n>
</label>
<captcha v-if="meta.enableHcaptcha" class="captcha" provider="hcaptcha" ref="hcaptcha" v-model:value="hCaptchaResponse" :sitekey="meta.hcaptchaSiteKey"/>
<captcha v-if="meta.enableRecaptcha" class="captcha" provider="recaptcha" ref="recaptcha" v-model:value="reCaptchaResponse" :sitekey="meta.recaptchaSiteKey"/>
<MkButton type="submit" :disabled="shouldDisableSubmitting" primary data-cy-signup-submit>{{ $ts.start }}</MkButton>
<captcha v-if="meta.enableHcaptcha" class="_formBlock captcha" provider="hcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" :sitekey="meta.hcaptchaSiteKey"/>
<captcha v-if="meta.enableRecaptcha" class="_formBlock captcha" provider="recaptcha" ref="recaptcha" v-model="reCaptchaResponse" :sitekey="meta.recaptchaSiteKey"/>
<MkButton class="_formBlock" type="submit" :disabled="shouldDisableSubmitting" primary data-cy-signup-submit>{{ $ts.start }}</MkButton>
</template>
</form>
</template>
@@ -57,8 +57,8 @@ const getPasswordStrength = require('syuilo-password-strength');
import { toUnicode } from 'punycode/';
import { host, url } from '@client/config';
import MkButton from './ui/button.vue';
import MkInput from './ui/input.vue';
import MkSwitch from './ui/switch.vue';
import MkInput from './form/input.vue';
import MkSwitch from './form/switch.vue';
import * as os from '@client/os';
import { login } from '@client/account';

View File

@@ -3,7 +3,7 @@ import { defineComponent, h, resolveDirective, withDirectives } from 'vue';
export default defineComponent({
props: {
value: {
modelValue: {
required: true,
},
},
@@ -13,11 +13,11 @@ export default defineComponent({
return withDirectives(h('div', {
class: 'pxhvhrfw',
}, options.map(option => withDirectives(h('button', {
class: ['_button', { active: this.value === option.props.value }],
class: ['_button', { active: this.modelValue === option.props.value }],
key: option.key,
disabled: this.value === option.props.value,
disabled: this.modelValue === option.props.value,
onClick: () => {
this.$emit('update:value', option.props.value);
this.$emit('update:modelValue', option.props.value);
}
}, option.children), [
[resolveDirective('click-anime')]

View File

@@ -9,7 +9,7 @@
<template #header>Req Viewer</template>
<div class="rlkneywz">
<MkTab v-model:value="tab" style="border-bottom: solid 0.5px var(--divider);">
<MkTab v-model="tab" style="border-bottom: solid 0.5px var(--divider);">
<option value="req">Request</option>
<option value="res">Response</option>
</MkTab>

View File

@@ -4,7 +4,7 @@
<i class="fas fa-terminal" style="margin-right: 0.5em;"></i>Task Manager
</template>
<div class="qljqmnzj _monospace">
<MkTab v-model:value="tab" style="border-bottom: solid 0.5px var(--divider);">
<MkTab v-model="tab" style="border-bottom: solid 0.5px var(--divider);">
<option value="windows">Windows</option>
<option value="stream">Stream</option>
<option value="streamPool">Stream (Pool)</option>

View File

@@ -31,9 +31,9 @@
import { defineComponent } from 'vue';
import { kinds } from '@/misc/api-permissions';
import XModalWindow from '@client/components/ui/modal-window.vue';
import MkInput from './ui/input.vue';
import MkTextarea from './ui/textarea.vue';
import MkSwitch from './ui/switch.vue';
import MkInput from './form/input.vue';
import MkTextarea from './form/textarea.vue';
import MkSwitch from './form/switch.vue';
import MkButton from './ui/button.vue';
import MkInfo from './ui/info.vue';

View File

@@ -1,7 +1,7 @@
<template>
<component class="bghgjjyj _button"
:is="link ? 'MkA' : 'button'"
:class="{ inline, primary, danger, full }"
:class="{ inline, primary, danger, rounded, full }"
:type="type"
@click="$emit('click', $event)"
@mousedown="onMousedown"
@@ -27,6 +27,11 @@ export default defineComponent({
required: false,
default: false
},
rounded: {
type: Boolean,
required: false,
default: false
},
inline: {
type: Boolean,
required: false,
@@ -124,8 +129,8 @@ export default defineComponent({
box-shadow: none;
text-decoration: none;
background: var(--buttonBg);
border-radius: 999px;
overflow: hidden;
border-radius: 4px;
overflow: clip;
box-sizing: border-box;
transition: background 0.1s ease;
@@ -141,6 +146,10 @@ export default defineComponent({
width: 100%;
}
&.rounded {
border-radius: 999px;
}
&.primary {
font-weight: bold;
color: var(--fgOnAccent) !important;
@@ -176,19 +185,11 @@ export default defineComponent({
opacity: 0.7;
}
&:focus {
&:focus-visible {
outline: solid 2px var(--focus);
outline-offset: 2px;
}
&.inline + .bghgjjyj {
margin-left: 12px;
}
&:not(.inline) + .bghgjjyj {
margin-top: 16px;
}
&.inline {
display: inline-block;
width: auto;

View File

@@ -27,7 +27,6 @@ export default defineComponent({
<style lang="scss" scoped>
.fpezltsf {
margin: 16px 0;
padding: 16px;
font-size: 90%;
background: var(--infoBg);
@@ -39,14 +38,6 @@ export default defineComponent({
color: var(--infoWarnFg);
}
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
> i {
margin-right: 4px;
}

View File

@@ -1,5 +1,5 @@
<template>
<div class="rrevdjwt" :class="{ left: align === 'left', pointer: point === 'top' }"
<div class="rrevdjwt" :class="{ center: align === 'center' }"
ref="items"
@contextmenu.self="e => e.preventDefault()"
v-hotkey="keymap"
@@ -59,10 +59,6 @@ export default defineComponent({
type: String,
requried: false
},
point: {
type: String,
requried: false
},
},
emits: ['close'],
data() {
@@ -145,26 +141,11 @@ export default defineComponent({
<style lang="scss" scoped>
.rrevdjwt {
padding: 8px 0;
min-width: 200px;
&.pointer {
&:before {
--size: 8px;
content: '';
display: block;
position: absolute;
top: calc(0px - (var(--size) * 2));
left: 0;
right: 0;
width: 0;
margin: auto;
border: solid var(--size) transparent;
border-bottom-color: var(--popup);
}
}
&.left {
&.center {
> .item {
text-align: left;
text-align: center;
}
}
@@ -177,36 +158,58 @@ export default defineComponent({
white-space: nowrap;
font-size: 0.9em;
line-height: 20px;
text-align: center;
text-align: left;
overflow: hidden;
text-overflow: ellipsis;
&:before {
content: "";
display: block;
position: absolute;
top: 0;
left: 0;
right: 0;
margin: auto;
width: calc(100% - 16px);
height: 100%;
border-radius: 6px;
}
&.danger {
color: #ff2a2a;
&:hover {
color: #fff;
background: #ff4242;
&:before {
background: #ff4242;
}
}
&:active {
color: #fff;
background: #d42e2e;
&:before {
background: #d42e2e;
}
}
}
&:hover {
color: var(--fgOnAccent);
background: var(--accent);
color: var(--accent);
text-decoration: none;
&:before {
background: var(--accentedBg);
}
}
&:active {
color: var(--fgOnAccent);
background: var(--accentDarken);
//color: var(--fgOnAccent);
//background: var(--accentDarken);
}
&:not(:active):focus {
&:not(:active):focus-visible {
box-shadow: 0 0 0 2px var(--focus) inset;
}

View File

@@ -1,6 +1,6 @@
<template>
<MkPopup ref="popup" :src="src" @closed="$emit('closed')" #default="{point}">
<MkMenu :items="items" :align="align" :point="point" @close="$refs.popup.close()" class="_popup _shadow"/>
<MkPopup ref="popup" :src="src" @closed="$emit('closed')">
<MkMenu :items="items" :align="align" @close="$refs.popup.close()" class="_popup _shadow"/>
</MkPopup>
</template>

View File

@@ -1,7 +1,7 @@
<template>
<transition :name="$store.state.animation ? 'popup-menu' : ''" appear @after-leave="onClosed" @enter="$emit('opening')" @after-enter="childRendered">
<div v-show="manualShowing != null ? manualShowing : showing" class="ccczpooj" :class="{ front, fixed, top: position === 'top' }" ref="content" :style="{ pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
<slot :point="point"></slot>
<slot></slot>
</div>
</transition>
</template>
@@ -52,7 +52,6 @@ export default defineComponent({
fixed: false,
transformOrigin: 'center',
contentClicking: false,
point: null,
};
},
@@ -136,10 +135,8 @@ export default defineComponent({
}
if (top > rect.top + (this.fixed ? 0 : window.pageYOffset)) {
this.point = 'top';
this.transformOrigin = 'center top';
} else {
this.point = null;
this.transformOrigin = 'center';
}

View File

@@ -1,58 +0,0 @@
<script lang="ts">
import { defineComponent, h } from 'vue';
import MkRadio from '@client/components/ui/radio.vue';
export default defineComponent({
components: {
MkRadio
},
props: {
modelValue: {
required: false
},
},
data() {
return {
value: this.modelValue,
}
},
watch: {
value() {
this.$emit('update:modelValue', this.value);
}
},
render() {
const label = this.$slots.desc();
let options = this.$slots.default();
// なぜかFragmentになることがあるため
if (options.length === 1 && options[0].props == null) options = options[0].children;
return h('div', {
class: 'novjtcto'
}, [
h('div', label),
...options.map(option => h(MkRadio, {
key: option.key,
value: option.props.value,
modelValue: this.value,
'onUpdate:modelValue': value => this.value = value,
}, option.children))
]);
}
});
</script>
<style lang="scss">
.novjtcto {
margin: 32px 0;
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
}
</style>

View File

@@ -1,139 +0,0 @@
<template>
<div class="timctyfi" :class="{ focused, disabled }">
<div class="icon"><slot name="icon"></slot></div>
<span class="label"><slot name="label"></slot></span>
<input
type="range"
ref="input"
v-model="v"
:disabled="disabled"
:min="min"
:max="max"
:step="step"
:autofocus="autofocus"
@focus="focused = true"
@blur="focused = false"
@input="$emit('update:value', $event.target.value)"
/>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
props: {
value: {
type: Number,
required: false,
default: 0
},
disabled: {
type: Boolean,
required: false,
default: false
},
min: {
type: Number,
required: false,
default: 0
},
max: {
type: Number,
required: false,
default: 100
},
step: {
type: Number,
required: false,
default: 1
},
autofocus: {
type: Boolean,
required: false
}
},
data() {
return {
v: this.value,
focused: false
};
},
watch: {
value(v) {
this.v = parseFloat(v);
}
},
mounted() {
if (this.autofocus) {
this.$nextTick(() => {
this.$refs.input.focus();
});
}
}
});
</script>
<style lang="scss" scoped>
.timctyfi {
position: relative;
margin: 8px;
> .icon {
display: inline-block;
width: 24px;
text-align: center;
}
> .title {
pointer-events: none;
font-size: 16px;
color: var(--inputLabel);
overflow: hidden;
}
> input {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background: var(--X10);
height: 7px;
margin: 0 8px;
outline: 0;
border: 0;
border-radius: 7px;
&.disabled {
opacity: 0.6;
cursor: not-allowed;
}
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
cursor: pointer;
width: 20px;
height: 20px;
display: block;
border-radius: 50%;
border: none;
background: var(--accent);
box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
box-sizing: content-box;
}
&::-moz-range-thumb {
-moz-appearance: none;
appearance: none;
cursor: pointer;
width: 20px;
height: 20px;
display: block;
border-radius: 50%;
border: none;
background: var(--accent);
box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
}
}
}
</style>

View File

@@ -1,262 +0,0 @@
<template>
<div class="vblkjoeq">
<div class="label" @click="focus"><slot name="label"></slot></div>
<div class="input" :class="{ inline, disabled, focused }">
<div class="prefix" ref="prefixEl"><slot name="prefix"></slot></div>
<select ref="inputEl"
v-model="v"
:disabled="disabled"
:required="required"
:readonly="readonly"
:placeholder="placeholder"
@focus="focused = true"
@blur="focused = false"
@input="onInput"
>
<slot></slot>
</select>
<div class="suffix" ref="suffixEl"><i class="fas fa-chevron-down"></i></div>
</div>
<div class="caption"><slot name="caption"></slot></div>
<MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue';
import MkButton from './button.vue';
export default defineComponent({
components: {
MkButton,
},
props: {
modelValue: {
required: true
},
required: {
type: Boolean,
required: false
},
readonly: {
type: Boolean,
required: false
},
disabled: {
type: Boolean,
required: false
},
placeholder: {
type: String,
required: false
},
autofocus: {
type: Boolean,
required: false,
default: false
},
inline: {
type: Boolean,
required: false,
default: false
},
manualSave: {
type: Boolean,
required: false,
default: false
},
},
emits: ['change', 'update:modelValue'],
setup(props, context) {
const { modelValue, autofocus } = toRefs(props);
const v = ref(modelValue.value);
const focused = ref(false);
const changed = ref(false);
const invalid = ref(false);
const filled = computed(() => v.value !== '' && v.value != null);
const inputEl = ref(null);
const prefixEl = ref(null);
const suffixEl = ref(null);
const focus = () => inputEl.value.focus();
const onInput = (ev) => {
changed.value = true;
context.emit('change', ev);
};
const updated = () => {
changed.value = false;
context.emit('update:modelValue', v.value);
};
watch(modelValue, newValue => {
v.value = newValue;
});
watch(v, newValue => {
if (!props.manualSave) {
updated();
}
invalid.value = inputEl.value.validity.badInput;
});
onMounted(() => {
nextTick(() => {
if (autofocus.value) {
focus();
}
// このコンポーネントが作成された時、非表示状態である場合がある
// 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する
const clock = setInterval(() => {
if (prefixEl.value) {
if (prefixEl.value.offsetWidth) {
inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px';
}
}
if (suffixEl.value) {
if (suffixEl.value.offsetWidth) {
inputEl.value.style.paddingRight = suffixEl.value.offsetWidth + 'px';
}
}
}, 100);
onUnmounted(() => {
clearInterval(clock);
});
});
});
return {
v,
focused,
invalid,
changed,
filled,
inputEl,
prefixEl,
suffixEl,
focus,
onInput,
updated,
};
},
});
</script>
<style lang="scss" scoped>
.vblkjoeq {
margin: 1.5em 0;
> .label {
font-size: 0.85em;
padding: 0 0 8px 12px;
user-select: none;
&:empty {
display: none;
}
}
> .caption {
font-size: 0.8em;
padding: 8px 0 0 12px;
color: var(--fgTransparentWeak);
&:empty {
display: none;
}
}
> .input {
$height: 42px;
position: relative;
> select {
appearance: none;
-webkit-appearance: none;
display: block;
height: $height;
width: 100%;
margin: 0;
padding: 0 12px;
font: inherit;
font-weight: normal;
font-size: 1em;
color: var(--fg);
background: var(--panel);
border: solid 1px var(--inputBorder);
border-radius: 6px;
outline: none;
box-shadow: none;
box-sizing: border-box;
cursor: pointer;
transition: border-color 0.1s ease-out;
&:hover {
border-color: var(--inputBorderHover);
}
}
> .prefix,
> .suffix {
display: flex;
align-items: center;
position: absolute;
z-index: 1;
top: 0;
padding: 0 12px;
font-size: 1em;
height: $height;
pointer-events: none;
&:empty {
display: none;
}
> * {
display: inline-block;
min-width: 16px;
max-width: 150px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
> .prefix {
left: 0;
padding-right: 6px;
}
> .suffix {
right: 0;
padding-left: 6px;
}
&.inline {
display: inline-block;
margin: 0;
}
&.focused {
> select {
border-color: var(--accent);
}
}
&.disabled {
opacity: 0.7;
&, * {
cursor: not-allowed !important;
}
}
}
}
</style>

View File

@@ -1,144 +0,0 @@
<template>
<div
class="ziffeoms"
:class="{ disabled, checked }"
role="switch"
:aria-checked="checked"
:aria-disabled="disabled"
@click.prevent="toggle"
>
<input
type="checkbox"
ref="input"
:disabled="disabled"
@keydown.enter="toggle"
>
<span class="button">
<span></span>
</span>
<span class="label">
<span><slot></slot></span>
<p><slot name="caption"></slot></p>
</span>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
props: {
modelValue: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
}
},
computed: {
checked(): boolean {
return this.modelValue;
}
},
methods: {
toggle() {
if (this.disabled) return;
this.$emit('update:modelValue', !this.checked);
}
}
});
</script>
<style lang="scss" scoped>
.ziffeoms {
position: relative;
display: flex;
margin: 32px 0;
cursor: pointer;
transition: all 0.3s;
&:first-child {
margin-top: 0;
}
&:last-child {
margin-bottom: 0;
}
> * {
user-select: none;
}
&.disabled {
opacity: 0.6;
cursor: not-allowed;
}
&.checked {
> .button {
background-color: var(--X10);
border-color: var(--X10);
> * {
background-color: var(--accent);
transform: translateX(14px);
}
}
}
> input {
position: absolute;
width: 0;
height: 0;
opacity: 0;
margin: 0;
}
> .button {
position: relative;
display: inline-block;
flex-shrink: 0;
margin: 3px 0 0 0;
width: 34px;
height: 14px;
background: var(--X6);
outline: none;
border-radius: 14px;
transition: inherit;
> * {
position: absolute;
top: -3px;
left: 0;
border-radius: 100%;
transition: background-color 0.3s, transform 0.3s;
width: 20px;
height: 20px;
background-color: #fff;
box-shadow: 0 2px 1px -1px rgba(#000, 0.2), 0 1px 1px 0 rgba(#000, 0.14), 0 1px 3px 0 rgba(#000, 0.12);
}
}
> .label {
margin-left: 8px;
display: block;
cursor: pointer;
transition: inherit;
color: var(--fg);
> span {
display: block;
line-height: 20px;
transition: inherit;
}
> p {
margin: 0;
color: var(--fgTransparentWeak);
font-size: 90%;
}
}
}
</style>

View File

@@ -1,254 +0,0 @@
<template>
<div class="adhpbeos">
<div class="label" @click="focus"><slot name="label"></slot></div>
<div class="input" :class="{ disabled, focused, tall, pre }">
<textarea ref="inputEl"
:class="{ code, _monospace: code }"
v-model="v"
:disabled="disabled"
:required="required"
:readonly="readonly"
:placeholder="placeholder"
:pattern="pattern"
:autocomplete="autocomplete"
:spellcheck="spellcheck"
@focus="focused = true"
@blur="focused = false"
@keydown="onKeydown($event)"
@input="onInput"
></textarea>
</div>
<div class="caption"><slot name="caption"></slot></div>
<MkButton v-if="manualSave && changed" @click="updated" primary><i class="fas fa-save"></i> {{ $ts.save }}</MkButton>
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue';
import MkButton from './button.vue';
import { debounce } from 'throttle-debounce';
export default defineComponent({
components: {
MkButton,
},
props: {
modelValue: {
required: true
},
type: {
type: String,
required: false
},
required: {
type: Boolean,
required: false
},
readonly: {
type: Boolean,
required: false
},
disabled: {
type: Boolean,
required: false
},
pattern: {
type: String,
required: false
},
placeholder: {
type: String,
required: false
},
autofocus: {
type: Boolean,
required: false,
default: false
},
autocomplete: {
required: false
},
spellcheck: {
required: false
},
code: {
type: Boolean,
required: false
},
tall: {
type: Boolean,
required: false,
default: false
},
pre: {
type: Boolean,
required: false,
default: false
},
debounce: {
type: Boolean,
required: false,
default: false
},
manualSave: {
type: Boolean,
required: false,
default: false
},
},
emits: ['change', 'keydown', 'enter', 'update:modelValue'],
setup(props, context) {
const { modelValue, autofocus } = toRefs(props);
const v = ref(modelValue.value);
const focused = ref(false);
const changed = ref(false);
const invalid = ref(false);
const filled = computed(() => v.value !== '' && v.value != null);
const inputEl = ref(null);
const focus = () => inputEl.value.focus();
const onInput = (ev) => {
changed.value = true;
context.emit('change', ev);
};
const onKeydown = (ev: KeyboardEvent) => {
context.emit('keydown', ev);
if (ev.code === 'Enter') {
context.emit('enter');
}
};
const updated = () => {
changed.value = false;
context.emit('update:modelValue', v.value);
};
const debouncedUpdated = debounce(1000, updated);
watch(modelValue, newValue => {
v.value = newValue;
});
watch(v, newValue => {
if (!props.manualSave) {
if (props.debounce) {
debouncedUpdated();
} else {
updated();
}
}
invalid.value = inputEl.value.validity.badInput;
});
onMounted(() => {
nextTick(() => {
if (autofocus.value) {
focus();
}
});
});
return {
v,
focused,
invalid,
changed,
filled,
inputEl,
focus,
onInput,
onKeydown,
updated,
};
},
});
</script>
<style lang="scss" scoped>
.adhpbeos {
margin: 1.5em 0;
> .label {
font-size: 0.85em;
padding: 0 0 8px 12px;
user-select: none;
&:empty {
display: none;
}
}
> .caption {
font-size: 0.8em;
padding: 8px 0 0 12px;
color: var(--fgTransparentWeak);
&:empty {
display: none;
}
}
> .input {
position: relative;
> textarea {
appearance: none;
-webkit-appearance: none;
display: block;
width: 100%;
min-width: 100%;
max-width: 100%;
min-height: 130px;
margin: 0;
padding: 12px;
font: inherit;
font-weight: normal;
font-size: 1em;
color: var(--fg);
background: var(--panel);
border: solid 0.5px var(--inputBorder);
border-radius: 6px;
outline: none;
box-shadow: none;
box-sizing: border-box;
transition: border-color 0.1s ease-out;
&:hover {
border-color: var(--inputBorderHover);
}
}
&.focused {
> textarea {
border-color: var(--accent);
}
}
&.disabled {
opacity: 0.7;
&, * {
cursor: not-allowed !important;
}
}
&.tall {
> textarea {
min-height: 200px;
}
}
&.pre {
> textarea {
white-space: pre;
}
}
}
}
</style>

View File

@@ -10,7 +10,7 @@
<template #header>{{ $ts.selectUser }}</template>
<div class="tbhwbxda _monolithic_">
<div class="_section">
<div class="_inputSplit _inputNoTopMargin _inputNoBottomMargin">
<div class="_inputSplit">
<MkInput v-model="username" class="input" @update:modelValue="search" ref="username">
<template #label>{{ $ts.username }}</template>
<template #prefix>@</template>
@@ -52,7 +52,7 @@
<script lang="ts">
import { defineComponent } from 'vue';
import MkInput from './ui/input.vue';
import MkInput from './form/input.vue';
import XModalWindow from '@client/components/ui/modal-window.vue';
import * as os from '@client/os';

View File

@@ -30,7 +30,7 @@
<script lang="ts">
import { defineComponent, defineAsyncComponent } from 'vue';
import { v4 as uuid } from 'uuid';
import MkSelect from '@client/components/ui/select.vue';
import MkSelect from '@client/components/form/select.vue';
import MkButton from '@client/components/ui/button.vue';
import { widgets as widgetDefs } from '@client/widgets';