diff options
Diffstat (limited to 'src/components/SettingsModal.vue')
| -rw-r--r-- | src/components/SettingsModal.vue | 231 |
1 files changed, 113 insertions, 118 deletions
diff --git a/src/components/SettingsModal.vue b/src/components/SettingsModal.vue index 4f5f05e..df8444f 100644 --- a/src/components/SettingsModal.vue +++ b/src/components/SettingsModal.vue @@ -1,8 +1,8 @@ <template> -<div class="fixed left-0 top-0 w-full h-full bg-black/40" @click="onClose"> +<div class="fixed left-0 top-0 w-full h-full bg-black/40" @click="onClose" ref="rootRef"> <div class="absolute left-1/2 -translate-x-1/2 top-1/2 -translate-y-1/2 min-w-[8cm] max-w-[80%] max-h-[80%] overflow-auto" :style="styles"> - <close-icon @click.stop="onClose" ref="closeIcon" + <close-icon @click.stop="onClose" ref="closeRef" class="absolute top-1 right-1 md:top-2 md:right-2 w-8 h-8 hover:cursor-pointer" /> <h1 class="text-xl md:text-2xl font-bold text-center py-2" :class="borderBClasses">Settings</h1> <div class="pb-2" :class="borderBClasses"> @@ -56,7 +56,7 @@ <input type="range" min="15" :max="uiOpts.breakpoint == 'sm' ? 150 : 200" v-model.number="lytOpts.minTileSz" @input="onSettingChgThrottled('LYT', 'minTileSz')" @change="onSettingChg('LYT', 'minTileSz')" - name="minTileSizeInput" ref="minTileSzInput"/> + name="minTileSizeInput" ref="minTileSzRef"/> <div class="my-auto text-right">{{lytOpts.minTileSz}} px</div> <!-- Row 2 --> <label for="maxTileSizeInput" @click="onReset('LYT', 'maxTileSz')" :class="rLabelClasses"> @@ -64,7 +64,7 @@ </label> <input type="range" min="15" max="400" v-model.number="lytOpts.maxTileSz" @input="onSettingChgThrottled('LYT', 'maxTileSz')" @change="onSettingChg('LYT', 'maxTileSz')" - name="maxTileSizeInput" ref="maxTileSzInput"/> + name="maxTileSizeInput" ref="maxTileSzRef"/> <div class="my-auto text-right">{{lytOpts.maxTileSz}} px</div> <!-- Row 3 --> <label for="tileSpacingInput" @click="onReset('LYT', 'tileSpacing')" :class="rLabelClasses"> @@ -107,129 +107,124 @@ Reset </s-button> <transition name="fade"> - <div v-if="saved" class="absolute right-1 bottom-1" ref="saveIndicator"> Saved </div> + <div v-if="saved" class="absolute right-1 bottom-1" ref="saveIndRef"> Saved </div> </transition> </div> </div> </template> -<script lang="ts"> -import {defineComponent, PropType} from 'vue'; +<script setup lang="ts"> +import {ref, computed, watch, PropType} from 'vue'; import SButton from './SButton.vue'; import CloseIcon from './icon/CloseIcon.vue'; import {UiOptions, OptionType, getDefaultLytOpts, getDefaultUiOpts} from '../lib'; import {LayoutOptions} from '../layout'; -export default defineComponent({ - props: { - lytOpts: {type: Object as PropType<LayoutOptions>, required: true}, - uiOpts: {type: Object as PropType<UiOptions>, required: true}, - }, - data(){ - return { - sweepLeaves: this.lytOpts.layoutType == 'sweep', - // For making only two of 'layoutType's values available for user selection - saved: false, // Set to true after a setting is saved - settingChgTimeout: 0, // Use to throttle some setting-change handling - }; - }, - computed: { - styles(): Record<string,string> { - return { - backgroundColor: this.uiOpts.bgColorAlt, - borderRadius: this.uiOpts.borderRadius + 'px', - boxShadow: this.uiOpts.shadowNormal, - }; - }, - borderBClasses(): string { - return 'border-b border-stone-400'; - }, - rLabelClasses(): string { // For reset-upon-click labels - return "w-fit hover:cursor-pointer hover:text-lime-600"; - }, - }, - methods: { - onClose(evt: Event){ - if (evt.target == this.$el || (this.$refs.closeIcon as typeof CloseIcon).$el.contains(evt.target)){ - this.$emit('close'); - } - }, - onSettingChg(optionType: OptionType, option: string){ - // Maintain min/max-tile-size consistency - if (optionType == 'LYT' && (option == 'minTileSz' || option == 'maxTileSz')){ - let minInput = this.$refs.minTileSzInput as HTMLInputElement; - let maxInput = this.$refs.maxTileSzInput as HTMLInputElement; - if (option == 'minTileSz' && Number(minInput.value) > Number(maxInput.value)){ - this.lytOpts.maxTileSz = this.lytOpts.minTileSz; - this.$emit('setting-chg', 'LYT', 'maxTileSz'); - } else if (option == 'maxTileSz' && Number(maxInput.value) < Number(minInput.value)){ - this.lytOpts.minTileSz = this.lytOpts.maxTileSz; - this.$emit('setting-chg', 'LYT', 'minTileSz'); - } - } - // Notify parent component - this.$emit('setting-chg', optionType, option, - {relayout: optionType == 'LYT', reinit: optionType == 'UI' && option == 'tree'}); - // Possibly make saved-indicator appear/animate - if (!this.saved){ - this.saved = true; - } else { - let el = this.$refs.saveIndicator as HTMLDivElement; - el.classList.remove('animate-flash-green'); - el.offsetWidth; // Triggers reflow - el.classList.add('animate-flash-green'); - } - }, - onSettingChgThrottled(optionType: OptionType, option: string){ - if (this.settingChgTimeout == 0){ - this.settingChgTimeout = setTimeout(() => { - this.settingChgTimeout = 0; - this.onSettingChg(optionType, option); - }, this.uiOpts.animationDelay); - } - }, - onReset(optionType: OptionType, option: string){ - // Restore the setting's default - let defaultLytOpts = getDefaultLytOpts(); - let defaultUiOpts = getDefaultUiOpts(defaultLytOpts); - if (optionType == 'LYT'){ - let lytOpt = option as keyof LayoutOptions; - if (this.lytOpts[lytOpt] == defaultLytOpts[lytOpt]){ - return; - } - (this.lytOpts[lytOpt] as any) = defaultLytOpts[lytOpt]; - if (option == 'layoutType'){ - this.sweepLeaves = this.lytOpts.layoutType == 'sweep'; - } - } else { - let uiOpt = option as keyof UiOptions; - if (this.uiOpts[uiOpt] == defaultUiOpts[uiOpt]){ - return; - } - (this.uiOpts[uiOpt] as any) = defaultUiOpts[uiOpt]; - } - // Notify parent component - this.onSettingChg(optionType, option); - }, - onResetAll(){ - // Restore default options - let defaultLytOpts = getDefaultLytOpts(); - let defaultUiOpts = getDefaultUiOpts(defaultLytOpts); - let needReinit = this.uiOpts.tree != defaultUiOpts.tree; - Object.assign(this.lytOpts, defaultLytOpts); - Object.assign(this.uiOpts, defaultUiOpts); - // Notify parent component - this.$emit('reset', needReinit); - // Clear saved-indicator - this.saved = false; - }, - }, - watch: { - sweepLeaves(newVal: boolean, oldVal: boolean){ - this.lytOpts.layoutType = newVal ? 'sweep' : 'rect'; - }, - }, - components: {SButton, CloseIcon, }, - emits: ['close', 'setting-chg', 'reset', ], +// Refs +const rootRef = ref(null as HTMLDivElement | null); +const closeRef = ref(null as typeof CloseIcon | null); +const minTileSzRef = ref(null as HTMLInputElement | null); +const maxTileSzRef = ref(null as HTMLInputElement | null); +const saveIndRef = ref(null as HTMLDivElement | null); + +// Props + events +const props = defineProps({ + lytOpts: {type: Object as PropType<LayoutOptions>, required: true}, + uiOpts: {type: Object as PropType<UiOptions>, required: true}, }); +const emit = defineEmits(['close', 'setting-chg', 'reset', ]); + +// For settings +const sweepLeaves = ref(props.lytOpts.layoutType == 'sweep'); + // For making only two of 'layoutType's values available for user selection) +watch(sweepLeaves, (newVal) => {props.lytOpts.layoutType = newVal ? 'sweep' : 'rect'}) + +// Settings change handling +const saved = ref(false); // Set to true after a setting is saved +const settingChgTimeout = ref(0); // Used to throttle some setting-change handling +function onSettingChg(optionType: OptionType, option: string){ + // Maintain min/max-tile-size consistency + if (optionType == 'LYT' && (option == 'minTileSz' || option == 'maxTileSz')){ + let minInput = minTileSzRef.value!; + let maxInput = maxTileSzRef.value!; + if (option == 'minTileSz' && Number(minInput.value) > Number(maxInput.value)){ + props.lytOpts.maxTileSz = props.lytOpts.minTileSz; + emit('setting-chg', 'LYT', 'maxTileSz'); + } else if (option == 'maxTileSz' && Number(maxInput.value) < Number(minInput.value)){ + props.lytOpts.minTileSz = props.lytOpts.maxTileSz; + emit('setting-chg', 'LYT', 'minTileSz'); + } + } + // Notify parent component + emit('setting-chg', optionType, option, + {relayout: optionType == 'LYT', reinit: optionType == 'UI' && option == 'tree'}); + // Possibly make saved-indicator appear/animate + if (!saved.value){ + saved.value = true; + } else { + let el = saveIndRef.value!; + el.classList.remove('animate-flash-green'); + el.offsetWidth; // Triggers reflow + el.classList.add('animate-flash-green'); + } +} +function onSettingChgThrottled(optionType: OptionType, option: string){ + if (settingChgTimeout.value == 0){ + settingChgTimeout.value = setTimeout(() => { + settingChgTimeout.value = 0; + onSettingChg(optionType, option); + }, props.uiOpts.animationDelay); + } +} +function onReset(optionType: OptionType, option: string){ + // Restore the setting's default + let defaultLytOpts = getDefaultLytOpts(); + let defaultUiOpts = getDefaultUiOpts(defaultLytOpts); + if (optionType == 'LYT'){ + let lytOpt = option as keyof LayoutOptions; + if (props.lytOpts[lytOpt] == defaultLytOpts[lytOpt]){ + return; + } + (props.lytOpts[lytOpt] as any) = defaultLytOpts[lytOpt]; + if (option == 'layoutType'){ + sweepLeaves.value = props.lytOpts.layoutType == 'sweep'; + } + } else { + let uiOpt = option as keyof UiOptions; + if (props.uiOpts[uiOpt] == defaultUiOpts[uiOpt]){ + return; + } + (props.uiOpts[uiOpt] as any) = defaultUiOpts[uiOpt]; + } + // Notify parent component + onSettingChg(optionType, option); +} +function onResetAll(){ + // Restore default options + let defaultLytOpts = getDefaultLytOpts(); + let defaultUiOpts = getDefaultUiOpts(defaultLytOpts); + let needReinit = props.uiOpts.tree != defaultUiOpts.tree; + Object.assign(props.lytOpts, defaultLytOpts); + Object.assign(props.uiOpts, defaultUiOpts); + // Notify parent component + emit('reset', needReinit); + // Clear saved-indicator + saved.value = false; +} + +// Close handling +function onClose(evt: Event){ + if (evt.target == rootRef.value || closeRef.value!.$el.contains(evt.target)){ + emit('close'); + } +} + +// Styles and classes +const styles = computed(() => ({ + backgroundColor: props.uiOpts.bgColorAlt, + borderRadius: props.uiOpts.borderRadius + 'px', + boxShadow: props.uiOpts.shadowNormal, +})); +const borderBClasses = 'border-b border-stone-400'; +const rLabelClasses = "w-fit hover:cursor-pointer hover:text-lime-600"; // For reset-upon-click labels </script> |
