mirror of
https://github.com/speatzle/nfsense.git
synced 2025-05-11 02:48:21 +00:00
Enhance EnumInput and Forms
- Soft-Nesting Layout to show structure without too much x-overhead - Forms now use subgrid where possible - Generalized form style to cover more cases and remove redundancy - Minor definitions fix - Made NicerForm a global component - Updated Test Page to use global components
This commit is contained in:
parent
864ca6defd
commit
bc83309d6d
6 changed files with 47 additions and 40 deletions
|
@ -1,6 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Index, MaybeIndex, equals } from '../../util';
|
import { Index, MaybeIndex, equals } from '../../util';
|
||||||
import NicerForm, { Fields } from './NicerForm.vue';
|
import { Fields } from './NicerForm.vue';
|
||||||
|
|
||||||
export type Variant = {
|
export type Variant = {
|
||||||
fields?: Fields,
|
fields?: Fields,
|
||||||
|
@ -21,9 +21,11 @@ const props = withDefaults(defineProps<{
|
||||||
|
|
||||||
// One-Way Bindings
|
// One-Way Bindings
|
||||||
variants?: Variants,
|
variants?: Variants,
|
||||||
|
label?: string,
|
||||||
}>(), {
|
}>(), {
|
||||||
modelValue: null,
|
modelValue: null,
|
||||||
variants: () => ({}),
|
variants: () => ({}),
|
||||||
|
label: '',
|
||||||
});
|
});
|
||||||
const { variants } = $(props);
|
const { variants } = $(props);
|
||||||
|
|
||||||
|
@ -64,14 +66,15 @@ watchEffect(() => {
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="enum-input">
|
<div class="form">
|
||||||
|
<label v-text="label"/>
|
||||||
<div class="pillbar">
|
<div class="pillbar">
|
||||||
<button class="variant" v-for="[index, variant] of Object.entries(variants)" :key="index" :class="{selected: currentVariant === index}" @click="() => currentVariant = index">
|
<button v-for="[index, variant] of Object.entries(variants)" :key="index" :class="{selected: currentVariant === index}" @click="() => currentVariant = index">
|
||||||
<component v-if="variant.icon" :is="variant.icon"/>
|
<component v-if="variant.icon" :is="variant.icon"/>
|
||||||
<template v-else>{{ variant.display }}</template>
|
<template v-else>{{ variant.display }}</template>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<NicerForm class="enum-fields" v-if="currentVariant && variants[currentVariant]?.fields" :fields="variants[currentVariant].fields" v-model="formValue" :key="currentVariant"/>
|
<NicerForm v-if="currentVariant && variants[currentVariant]?.fields" :fields="variants[currentVariant].fields" v-model="formValue" :key="currentVariant"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
@ -79,9 +82,6 @@ watchEffect(() => {
|
||||||
flex-flow: nowrap;
|
flex-flow: nowrap;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
}
|
}
|
||||||
.variant { padding: 0.25rem; gap: 0.25rem; }
|
.pillbar > button { padding: 0.25rem; gap: 0.25rem; }
|
||||||
.selected { background-color: var(--cl-bg-sl); }
|
.pillbar > .selected { background-color: var(--cl-bg-sl); }
|
||||||
.enum-fields {
|
|
||||||
padding-left: 0px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
|
@ -50,21 +50,12 @@ watch($$(modelValue), (val) => {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="form">
|
<div class="form">
|
||||||
<component v-if="heading" :is="`h${headingLevel}`" :text="heading"/>
|
<component v-if="heading" :is="`h${headingLevel}`">{{ heading }}</component>
|
||||||
|
<div class="form inner-form">
|
||||||
<template v-for="[index, field] of Object.entries(fields)" :key="index">
|
<template v-for="[index, field] of Object.entries(fields)" :key="index">
|
||||||
<label v-if="field.label" v-text="field.label"/>
|
<label v-if="field.label && field.is !== 'EnumInput'" v-text="field.label"/>
|
||||||
<component :is="field.is" v-model="modelValue[index]" v-bind="field.props"/>
|
<component :is="field.is" v-model="modelValue[index]" v-bind="field.is === 'EnumInput' ? Object.assign({label: field.label}, field.props) : field.props"/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
label::after {
|
|
||||||
content: ":";
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
border-left: 1px solid var(--cl-fg);
|
|
||||||
border-bottom: 1px solid var(--cl-fg);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -49,7 +49,7 @@ const GetAddresses: SearchProvider = async (o) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const GetServices: SearchProvider = async (o) => {
|
const GetServices: SearchProvider = async (o) => {
|
||||||
let res = await apiCall('Object.services.list', {});
|
let res = await apiCall('object.services.list', {});
|
||||||
if (res.Error === null) {
|
if (res.Error === null) {
|
||||||
console.debug('services', res.Data);
|
console.debug('services', res.Data);
|
||||||
let obj = {} as Options;
|
let obj = {} as Options;
|
||||||
|
|
|
@ -25,19 +25,32 @@ input {
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
form, .form {
|
/* Universal Form Style-Component */
|
||||||
|
:is(form, .form) {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
:is(form, .form) > :is(button, .button, h1) {
|
:is(form, .form) :is(form, .form) { /* Subform and EnumInput */
|
||||||
grid-column: 1 / 3;
|
grid-column: 1 / 3; /* Maintenance: This column-end must match the column count of the host form, +1 */
|
||||||
|
grid-template-columns: subgrid; /* All descendants of a form align to that same form */
|
||||||
|
padding: 0px; /* To keep alignment, no padding is needed for those descendants */
|
||||||
|
}
|
||||||
|
:is(form, .form) :is(form, .form) > .inner-form { /* Soft-Nesting for Subform and EnumInput */
|
||||||
|
border-left: 1px solid var(--cl-fg);
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
}
|
||||||
|
:is(form, .form) > :is(button, .button, h1, h2, h3, h4, h5, h6) {
|
||||||
|
grid-column: 1 / 3; /* Full-Span children */
|
||||||
}
|
}
|
||||||
:is(form, .form) > label {
|
:is(form, .form) > label {
|
||||||
grid-column: 1;
|
grid-column: 1; /* Re-align unbalanced grid => allows the last column(s) to be optional */
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
}
|
}
|
||||||
|
:is(form, .form) > label::after {
|
||||||
|
content: ":";
|
||||||
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import './global-styles/mlfe.css';
|
||||||
import './global-styles/transitions.css';
|
import './global-styles/transitions.css';
|
||||||
import 'vue-toast-notification/dist/theme-default.css';
|
import 'vue-toast-notification/dist/theme-default.css';
|
||||||
|
|
||||||
|
import NicerForm from './components/inputs/NicerForm.vue';
|
||||||
import PillBar from './components/inputs/PillBar.vue';
|
import PillBar from './components/inputs/PillBar.vue';
|
||||||
import TextBox from './components/inputs/TextBox.vue';
|
import TextBox from './components/inputs/TextBox.vue';
|
||||||
import EnumInput from './components/inputs/EnumInput.vue';
|
import EnumInput from './components/inputs/EnumInput.vue';
|
||||||
|
@ -34,6 +35,7 @@ app.use(head);
|
||||||
app.use(ToastPlugin);
|
app.use(ToastPlugin);
|
||||||
|
|
||||||
// Global Components
|
// Global Components
|
||||||
|
app.component('NicerForm', NicerForm);
|
||||||
app.component('PillBar', PillBar);
|
app.component('PillBar', PillBar);
|
||||||
app.component('TextBox', TextBox);
|
app.component('TextBox', TextBox);
|
||||||
app.component('NumberBox', NumberBox);
|
app.component('NumberBox', NumberBox);
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import SingleSelect from '../components/inputs/SingleSelect.vue';
|
|
||||||
import { SearchProvider, Options } from '../components/inputs/DropdownInput.vue';
|
import { SearchProvider, Options } from '../components/inputs/DropdownInput.vue';
|
||||||
import MultiSelect from '../components/inputs/MultiSelect.vue';
|
|
||||||
import NicerForm from '../components/inputs/NicerForm.vue';
|
|
||||||
import EnumInput from '../components/inputs/EnumInput.vue';
|
|
||||||
import TextBox from '../components/inputs/TextBox.vue';
|
|
||||||
|
|
||||||
const testValues: Options = {
|
const testValues: Options = {
|
||||||
1: { display: 'Option 1' },
|
1: { display: 'Option 1' },
|
||||||
|
@ -28,18 +23,24 @@ function genSP(indexIsChar: boolean): SearchProvider {
|
||||||
<div>
|
<div>
|
||||||
<PageHeader title="Test Page"/>
|
<PageHeader title="Test Page"/>
|
||||||
<NicerForm :fields="{
|
<NicerForm :fields="{
|
||||||
Single: { is: SingleSelect, label: 'SingleSelect', props: { options: testValues, searchProvider: genSP(true) } },
|
Single: { is: 'SingleSelect', label: 'SingleSelect', props: { options: testValues, searchProvider: genSP(true) } },
|
||||||
Multiple: { is: MultiSelect, label: 'Multiselect', props: { options: testValues, searchProvider: genSP(false) } },
|
Multiple: { is: 'MultiSelect', label: 'Multiselect', props: { options: testValues, searchProvider: genSP(false) } },
|
||||||
IP: { is: EnumInput, label: 'IP Address', props: { variants: {
|
IP: { is: 'EnumInput', label: 'IP Address', props: { variants: {
|
||||||
'dhcp': { display: 'DHCP' },
|
'dhcp': { display: 'DHCP' },
|
||||||
'static-ipv4': {
|
'static-ipv4': {
|
||||||
display: 'Static (IPV4)',
|
display: 'Static (IPV4)',
|
||||||
fields: {
|
fields: {
|
||||||
ip: { is: TextBox, label: 'IP Address + CIDR' },
|
ip: { is: 'TextBox', label: 'IP Address (CIDR)' },
|
||||||
gw: { is: TextBox, label: 'Gateway Address' },
|
gw: { is: 'TextBox', label: 'Gateway Address' },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}},
|
}}},
|
||||||
|
Subform: { is: 'NicerForm', props: { heading: 'Subform', fields: {
|
||||||
|
Text: { is: 'TextBox', label: 'Text' },
|
||||||
|
Subform2: { is: 'NicerForm', props: { heading: 'Subform2', fields: {
|
||||||
|
Text: { is: 'TextBox', label: 'Text' },
|
||||||
|
} } },
|
||||||
|
} } },
|
||||||
}" v-model="vm"/>
|
}" v-model="vm"/>
|
||||||
{{ vm }}
|
{{ vm }}
|
||||||
<button @click="() => { vm.Multiple = [1]; }">Click me</button>
|
<button @click="() => { vm.Multiple = [1]; }">Click me</button>
|
||||||
|
|
Loading…
Add table
Reference in a new issue