Skip to content

Commit

Permalink
Add support for AutoViewForm
Browse files Browse the repository at this point in the history
  • Loading branch information
mythz committed Oct 6, 2024
1 parent a370b4e commit 38a19bf
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/components/AutoQueryGrid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
</template>
</AutoEditForm>
</div>
<div v-else-if="edit">
<slot v-if="slots.viewform" name="viewform" :model="edit" :apis="apis" :done="editDone"></slot>
<AutoViewForm v-else :model="edit" :apis="apis" :done="editDone" />
</div>
<slot v-if="slots.toolbar" name="toolbar"></slot>
<div v-else-if="show('toolbar')">
<QueryPrefs v-if="showQueryPrefs" :columns="viewModelColumns" :prefs="apiPrefs" @done="showQueryPrefs=false" @save="saveApiPrefs" />
Expand Down
127 changes: 127 additions & 0 deletions src/components/AutoViewForm.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<template>
<div>
<div v-if="!typeName">
<p class="text-red-700">Could not create view for unknown <b>type</b> {{ typeName }}</p>
</div>
<div v-else-if="formStyle=='card'" :class="panelClass">
<div :class="formClass">
<div>
<div v-if="$slots['heading']"><slot name="heading"></slot></div>
<h3 v-else :class="headingClass">{{ title }}</h3>

<div v-if="$slots['subheading']"><slot name="subheading"></slot></div>
<p v-else-if="subHeading" :class="subHeadingClass">{{ subHeading }}</p>
<p v-else-if="metaType?.notes" :class="['notes',subHeadingClass]" v-html="metaType?.notes"></p>
</div>
<MarkupModel :value="model" />
</div>
</div>
<div v-else class="relative z-10" aria-labelledby="slide-over-title" role="dialog" aria-modal="true">
<div class="fixed inset-0"></div>
<div class="fixed inset-0 overflow-hidden">
<div @mousedown="close" class="absolute inset-0 overflow-hidden">
<div @mousedown.stop="" class="pointer-events-none fixed inset-y-0 right-0 flex pl-10">
<div :class="['pointer-events-auto w-screen xl:max-w-3xl md:max-w-xl max-w-lg',transition1]">
<div :class="formClass">
<div class="flex min-h-0 flex-1 flex-col overflow-auto">
<div class="flex-1">
<!-- Header -->
<div class="bg-gray-50 dark:bg-gray-900 px-4 py-6 sm:px-6">
<div class="flex items-start justify-between space-x-3">
<div class="space-y-1">
<div v-if="$slots['heading']"><slot name="heading"></slot></div>
<h3 v-else :class="headingClass">{{ title }}</h3>

<div v-if="$slots['subheading']"><slot name="subheading"></slot></div>
<p v-else-if="subHeading" :class="subHeadingClass">{{ subHeading }}</p>
<p v-else-if="metaType?.notes" :class="['notes',subHeadingClass]" v-html="metaType?.notes"></p>
</div>
<div class="flex h-7 items-center">
<CloseButton button-class="bg-gray-50 dark:bg-gray-900" @close="close"/>
</div>
</div>
</div>
<MarkupModel :value="model" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { useMetadata, Apis } from '@/use/metadata'
import { form } from './css'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { transition } from '@/use/utils'
import { Sole } from '@/use/config'
import { humanize } from '@servicestack/client'
const props = withDefaults(defineProps<{
model: any
apis?: Apis,
typeName?: string,
done?: Function,
formStyle?: "slideOver" | "card"
panelClass?: string
formClass?: string
headingClass?: string
subHeadingClass?: string
heading?: string
subHeading?: string
}>(), {
formStyle: "slideOver",
})
const emit = defineEmits<{
(e:'done'): void
}>()
const { typeOf } = useMetadata()
const typeName = computed(() => props.typeName ?? props.apis!.dataModel!.name)
const metaType = computed(() => typeOf(typeName.value))
const panelClass = computed(() => props.panelClass || form.panelClass(props.formStyle))
const formClass = computed(() => props.formClass || form.formClass(props.formStyle))
const headingClass = computed(() => props.headingClass || form.headingClass(props.formStyle))
const subHeadingClass = computed(() => props.subHeadingClass || form.subHeadingClass(props.formStyle))
const title = computed(() => props.heading || typeOf(typeName.value)?.description ||
(props.model?.id ? `${humanize(typeName.value)} ${props.model.id}` : 'View ' + humanize(typeName.value)))
if (Sole.interceptors.has('AutoViewForm.new')) Sole.interceptors.invoke('AutoViewForm.new', { props })
function done() {
if (props.done) {
props.done()
}
}
/* SlideOver */
const show = ref(false)
const transition1 = ref('')
const rule1 = {
entering: { cls: 'transform transition ease-in-out duration-500 sm:duration-700', from: 'translate-x-full', to: 'translate-x-0' },
leaving: { cls: 'transform transition ease-in-out duration-500 sm:duration-700', from: 'translate-x-0', to: 'translate-x-full' }
}
watch(show, () => {
transition(rule1, transition1, show.value)
if (!show.value) setTimeout(done, 700)
})
show.value = true
function close() {
if (props.formStyle == 'slideOver') {
show.value = false
} else {
done()
}
}
const globalKeyHandler = (e:KeyboardEvent) => { if (e.key === 'Escape') close() }
onMounted(() => window.addEventListener('keydown', globalKeyHandler))
onUnmounted(() => window.removeEventListener('keydown', globalKeyHandler))
</script>
34 changes: 34 additions & 0 deletions src/components/MarkupFormat.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<template>
<a v-if="type=='link'" :href="value" class="text-indigo-600">{{value}}</a>
<a v-else-if="type=='image'" :href="value" :title="value" class="inline-block">
<Icon :src="value" :class="imageClass" />
</a>
<HtmlFormat v-else :value="value" />
</template>
<script setup lang="ts">
import { useFiles } from '@/use/files'
const props = withDefaults(defineProps<{
value: any,
imageClass?: string
}>(), {
imageClass: 'w-8 h-8',
})
const { getMimeType } = useFiles()
const v = props.value
let type:string = typeof props.value
const mimeType = type === 'string' && v.length ? getMimeType(v) : null
if (type === 'string' && v.length) {
const url = v.startsWith('https://') || v.startsWith('http://')
const path = url || v[0] === '/'
if (path && mimeType?.startsWith('image/')) {
type = 'image'
} else if (url) {
type = 'link'
}
}
</script>
41 changes: 41 additions & 0 deletions src/components/MarkupModel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<template>
<table class="my-2 w-full">
<tr v-for="(v,k) in basic" class="leading-7">
<th class="px-2 text-left align-top">{{humanize(k as string)}}</th>
<td colspan="align-top"><MarkupFormat :value="v" /></td>
</tr>
<template v-for="(v,k) in complex">
<tr class="my-2 leading-7">
<td colspan="2" class="px-2 bg-indigo-700 text-white">{{humanize(k as string)}}</td>
</tr>
<tr class="leading-7">
<td colspan="2" class="px-2 align-top"><MarkupFormat :value="v" /></td>
</tr>
</template>
</table>
</template>

<script setup lang="ts">
import { humanize } from '@servicestack/client'
const props = defineProps<{
value: any,
imageClass?: string
}>()
const fields = Object.keys(props.value)
const basic:{[k:string]:any} = {}
const complex:{[k:string]:any} = {}
fields.forEach(k => {
const v = props.value[k]
const t = typeof v
if (v == null || t === 'function' || t === 'symbol') {
basic[k] = `(${v == null ? 'null' : 't'})`
}
else if (t === 'object') {
complex[k] = v
} else {
basic[k] = v
}
})
</script>
6 changes: 6 additions & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,16 @@ import AutoFormFields from './AutoFormFields.vue'
import AutoForm from './AutoForm.vue'
import AutoCreateForm from './AutoCreateForm.vue'
import AutoEditForm from './AutoEditForm.vue'
import AutoViewForm from './AutoViewForm.vue'
import ConfirmDelete from './ConfirmDelete.vue'
import FormLoading from './FormLoading.vue'

import DataGrid from './DataGrid.vue'
import CellFormat from './CellFormat.vue'
import PreviewFormat from './PreviewFormat.vue'
import HtmlFormat from './HtmlFormat.vue'
import MarkupFormat from './MarkupFormat.vue'
import MarkupModel from './MarkupModel.vue'

import CloseButton from './CloseButton.vue'
import SlideOver from './SlideOver.vue'
Expand Down Expand Up @@ -98,13 +101,16 @@ export default {
AutoForm,
AutoCreateForm,
AutoEditForm,
AutoViewForm,
ConfirmDelete,
FormLoading,

DataGrid,
CellFormat,
PreviewFormat,
HtmlFormat,
MarkupFormat,
MarkupModel,

CloseButton,
SlideOver,
Expand Down
1 change: 1 addition & 0 deletions src/use/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export class Apis implements AutoQueryApis

get AnyQuery() { return this.Query || this.QueryInto }
get AnyUpdate() { return this.Patch || this.Update }
get dataModel() { return this.AnyQuery?.dataModel }

toArray() {
let to = [this.Query, this.QueryInto, this.Create, this.Update, this.Patch, this.Delete]
Expand Down

0 comments on commit 38a19bf

Please sign in to comment.