aboutsummaryrefslogtreecommitdiff
path: root/src/components/SettingsModal.vue
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/SettingsModal.vue')
-rw-r--r--src/components/SettingsModal.vue231
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>