diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2022-12-27 14:36:33 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2022-12-27 14:36:33 +0900 |
| commit | 9384f5399da39e53855beb8e7f8ded1aa56bf72e (patch) | |
| tree | ce5959571a981b9c4047da3c7b3fd080aa44222c /packages/frontend/src/components/MkPollEditor.vue | |
| parent | wip: retention for dashboard (diff) | |
| download | sharkey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.tar.gz sharkey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.tar.bz2 sharkey-9384f5399da39e53855beb8e7f8ded1aa56bf72e.zip | |
rename: client -> frontend
Diffstat (limited to 'packages/frontend/src/components/MkPollEditor.vue')
| -rw-r--r-- | packages/frontend/src/components/MkPollEditor.vue | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/packages/frontend/src/components/MkPollEditor.vue b/packages/frontend/src/components/MkPollEditor.vue new file mode 100644 index 0000000000..556abc5fd0 --- /dev/null +++ b/packages/frontend/src/components/MkPollEditor.vue @@ -0,0 +1,219 @@ +<template> +<div class="zmdxowus"> + <p v-if="choices.length < 2" class="caution"> + <i class="ti ti-alert-triangle"></i>{{ i18n.ts._poll.noOnlyOneChoice }} + </p> + <ul> + <li v-for="(choice, i) in choices" :key="i"> + <MkInput class="input" small :model-value="choice" :placeholder="$t('_poll.choiceN', { n: i + 1 })" @update:model-value="onInput(i, $event)"> + </MkInput> + <button class="_button" @click="remove(i)"> + <i class="ti ti-x"></i> + </button> + </li> + </ul> + <MkButton v-if="choices.length < 10" class="add" @click="add">{{ i18n.ts.add }}</MkButton> + <MkButton v-else class="add" disabled>{{ i18n.ts._poll.noMore }}</MkButton> + <MkSwitch v-model="multiple">{{ i18n.ts._poll.canMultipleVote }}</MkSwitch> + <section> + <div> + <MkSelect v-model="expiration" small> + <template #label>{{ i18n.ts._poll.expiration }}</template> + <option value="infinite">{{ i18n.ts._poll.infinite }}</option> + <option value="at">{{ i18n.ts._poll.at }}</option> + <option value="after">{{ i18n.ts._poll.after }}</option> + </MkSelect> + <section v-if="expiration === 'at'"> + <MkInput v-model="atDate" small type="date" class="input"> + <template #label>{{ i18n.ts._poll.deadlineDate }}</template> + </MkInput> + <MkInput v-model="atTime" small type="time" class="input"> + <template #label>{{ i18n.ts._poll.deadlineTime }}</template> + </MkInput> + </section> + <section v-else-if="expiration === 'after'"> + <MkInput v-model="after" small type="number" class="input"> + <template #label>{{ i18n.ts._poll.duration }}</template> + </MkInput> + <MkSelect v-model="unit" small> + <option value="second">{{ i18n.ts._time.second }}</option> + <option value="minute">{{ i18n.ts._time.minute }}</option> + <option value="hour">{{ i18n.ts._time.hour }}</option> + <option value="day">{{ i18n.ts._time.day }}</option> + </MkSelect> + </section> + </div> + </section> +</div> +</template> + +<script lang="ts" setup> +import { ref, watch } from 'vue'; +import MkInput from './form/input.vue'; +import MkSelect from './form/select.vue'; +import MkSwitch from './form/switch.vue'; +import MkButton from './MkButton.vue'; +import { formatDateTimeString } from '@/scripts/format-time-string'; +import { addTime } from '@/scripts/time'; +import { i18n } from '@/i18n'; + +const props = defineProps<{ + modelValue: { + expiresAt: string; + expiredAfter: number; + choices: string[]; + multiple: boolean; + }; +}>(); +const emit = defineEmits<{ + (ev: 'update:modelValue', v: { + expiresAt: string; + expiredAfter: number; + choices: string[]; + multiple: boolean; + }): void; +}>(); + +const choices = ref(props.modelValue.choices); +const multiple = ref(props.modelValue.multiple); +const expiration = ref('infinite'); +const atDate = ref(formatDateTimeString(addTime(new Date(), 1, 'day'), 'yyyy-MM-dd')); +const atTime = ref('00:00'); +const after = ref(0); +const unit = ref('second'); + +if (props.modelValue.expiresAt) { + expiration.value = 'at'; + atDate.value = atTime.value = props.modelValue.expiresAt; +} else if (typeof props.modelValue.expiredAfter === 'number') { + expiration.value = 'after'; + after.value = props.modelValue.expiredAfter / 1000; +} else { + expiration.value = 'infinite'; +} + +function onInput(i, value) { + choices.value[i] = value; +} + +function add() { + choices.value.push(''); + // TODO + // nextTick(() => { + // (this.$refs.choices as any).childNodes[this.choices.length - 1].childNodes[0].focus(); + // }); +} + +function remove(i) { + choices.value = choices.value.filter((_, _i) => _i !== i); +} + +function get() { + const calcAt = () => { + return new Date(`${atDate.value} ${atTime.value}`).getTime(); + }; + + const calcAfter = () => { + let base = parseInt(after.value); + switch (unit.value) { + case 'day': base *= 24; + // fallthrough + case 'hour': base *= 60; + // fallthrough + case 'minute': base *= 60; + // fallthrough + case 'second': return base *= 1000; + default: return null; + } + }; + + return { + choices: choices.value, + multiple: multiple.value, + ...( + expiration.value === 'at' ? { expiresAt: calcAt() } : + expiration.value === 'after' ? { expiredAfter: calcAfter() } : {} + ), + }; +} + +watch([choices, multiple, expiration, atDate, atTime, after, unit], () => emit('update:modelValue', get()), { + deep: true, +}); +</script> + +<style lang="scss" scoped> +.zmdxowus { + padding: 8px 16px; + + > .caution { + margin: 0 0 8px 0; + font-size: 0.8em; + color: #f00; + + > i { + margin-right: 4px; + } + } + + > ul { + display: block; + margin: 0; + padding: 0; + list-style: none; + + > li { + display: flex; + margin: 8px 0; + padding: 0; + width: 100%; + + > .input { + flex: 1; + } + + > button { + width: 32px; + padding: 4px 0; + } + } + } + + > .add { + margin: 8px 0; + z-index: 1; + } + + > section { + margin: 16px 0 0 0; + + > div { + margin: 0 8px; + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 12px; + + &:last-child { + flex: 1 0 auto; + + > div { + flex-grow: 1; + } + + > section { + // MAGIC: Prevent div above from growing unless wrapped to its own line + flex-grow: 9999; + align-items: end; + display: flex; + gap: 4px; + + > .input { + flex: 1 1 auto; + } + } + } + } + } +} +</style> |