enhance(frontend/2fa): 二要素認証のバックアップコードを保存するように促す (MisskeyIO#611)
This commit is contained in:
parent
7da775df10
commit
1f38f58117
8 changed files with 100 additions and 33 deletions
|
@ -48,6 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<option v-for="item in select.items" :value="item.value">{{ item.text }}</option>
|
||||
</template>
|
||||
</MkSelect>
|
||||
<MkSwitch v-if="switchLabel" v-model="switchValue" style="display: flex; margin: 1em 0; justify-content: center;">{{ switchLabel }}</MkSwitch>
|
||||
<details v-if="details" class="_acrylic" style="margin: 1em 0;">
|
||||
<summary>{{ i18n.ts.details }}</summary>
|
||||
<div class="_gaps_s" style="text-align: initial;">
|
||||
|
@ -58,7 +59,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</details>
|
||||
<div v-if="(showOkButton || showCancelButton) && !actions" :class="$style.buttons">
|
||||
<MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabledReason" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton>
|
||||
<MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okDisabled || okButtonDisabledReason" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}<span v-if="okDisabled && okWaitInitiated"> ({{ sec }})</span></MkButton>
|
||||
<MkButton v-if="showCancelButton || input || select" data-cy-modal-dialog-cancel inline rounded @click="cancel">{{ cancelText ?? i18n.ts.cancel }}</MkButton>
|
||||
</div>
|
||||
<div v-if="actions" :class="$style.buttons">
|
||||
|
@ -69,11 +70,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onBeforeUnmount, onMounted, ref, shallowRef, computed } from 'vue';
|
||||
import { onBeforeUnmount, onMounted, ref, shallowRef, computed, watch } from 'vue';
|
||||
import MkModal from '@/components/MkModal.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
@ -104,6 +106,7 @@ const props = withDefaults(defineProps<{
|
|||
text?: string | null;
|
||||
input?: Input;
|
||||
select?: Select;
|
||||
switchLabel?: string | null;
|
||||
details?: Record<string, string>;
|
||||
actions?: {
|
||||
text: string;
|
||||
|
@ -115,6 +118,8 @@ const props = withDefaults(defineProps<{
|
|||
showCancelButton?: boolean;
|
||||
cancelableByBgClick?: boolean;
|
||||
okText?: string;
|
||||
okWaitInitiate?: 'dialog' | 'input' | 'switch';
|
||||
okWaitDuration?: number;
|
||||
cancelText?: string;
|
||||
}>(), {
|
||||
type: 'info',
|
||||
|
@ -123,17 +128,20 @@ const props = withDefaults(defineProps<{
|
|||
text: undefined,
|
||||
input: undefined,
|
||||
select: undefined,
|
||||
switchLabel: undefined,
|
||||
details: undefined,
|
||||
actions: undefined,
|
||||
showOkButton: true,
|
||||
showCancelButton: false,
|
||||
cancelableByBgClick: true,
|
||||
okText: undefined,
|
||||
okWaitInitiate: undefined,
|
||||
okWaitDuration: 0,
|
||||
cancelText: undefined,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'done', v: { canceled: true } | { canceled: false, result: Result }): void;
|
||||
(ev: 'done', v: { canceled: true } | { canceled: false, result: Result, toggle: boolean }): void;
|
||||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
|
@ -141,6 +149,16 @@ const modal = shallowRef<InstanceType<typeof MkModal>>();
|
|||
|
||||
const inputValue = ref<string | number | null>(props.input?.default ?? null);
|
||||
const selectedValue = ref(props.select?.default ?? null);
|
||||
const switchValue = ref<boolean>(false);
|
||||
|
||||
const sec = ref(props.okWaitDuration);
|
||||
const okWaitInitiated = computed(() => {
|
||||
if (props.okWaitInitiate === 'dialog') return true;
|
||||
if (props.okWaitInitiate === 'input') return inputValue.value !== null;
|
||||
if (props.okWaitInitiate === 'switch') return switchValue.value;
|
||||
return false;
|
||||
});
|
||||
const okDisabled = computed(() => sec.value > 0);
|
||||
|
||||
const okButtonDisabledReason = computed<null | 'charactersExceeded' | 'charactersBelow'>(() => {
|
||||
if (props.input) {
|
||||
|
@ -161,9 +179,9 @@ const okButtonDisabledReason = computed<null | 'charactersExceeded' | 'character
|
|||
|
||||
// overload function を使いたいので lint エラーを無視する
|
||||
function done(canceled: true): void;
|
||||
function done(canceled: false, result: Result): void; // eslint-disable-line no-redeclare
|
||||
function done(canceled: boolean, result?: Result): void { // eslint-disable-line no-redeclare
|
||||
emit('done', { canceled, result } as { canceled: true } | { canceled: false, result: Result });
|
||||
function done(canceled: false, result: Result, toggle: boolean): void; // eslint-disable-line no-redeclare
|
||||
function done(canceled: boolean, result?: Result, toggle?: boolean ): void { // eslint-disable-line no-redeclare
|
||||
emit('done', { canceled, result, toggle } as { canceled: true } | { canceled: false, result: Result, toggle: boolean });
|
||||
modal.value?.close();
|
||||
}
|
||||
|
||||
|
@ -174,18 +192,13 @@ async function ok() {
|
|||
props.input ? inputValue.value :
|
||||
props.select ? selectedValue.value :
|
||||
true;
|
||||
done(false, result);
|
||||
done(false, result, switchValue.value);
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
done(true);
|
||||
}
|
||||
|
||||
/*
|
||||
function onBgClick() {
|
||||
if (props.cancelableByBgClick) cancel();
|
||||
}
|
||||
*/
|
||||
function onKeydown(evt: KeyboardEvent) {
|
||||
if (evt.key === 'Escape') cancel();
|
||||
}
|
||||
|
@ -198,8 +211,24 @@ function onInputKeydown(evt: KeyboardEvent) {
|
|||
}
|
||||
}
|
||||
|
||||
watch(okWaitInitiated, () => {
|
||||
sec.value = props.okWaitDuration;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('keydown', onKeydown);
|
||||
|
||||
sec.value = props.okWaitDuration;
|
||||
if (sec.value > 0) {
|
||||
const waitTimer = setInterval(() => {
|
||||
if (!okWaitInitiated.value) return;
|
||||
|
||||
if (sec.value < 0) {
|
||||
clearInterval(waitTimer);
|
||||
}
|
||||
sec.value = sec.value - 1;
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue