add nova
This commit is contained in:
82
nova/resources/js/fields/Detail/AudioField.vue
Normal file
82
nova/resources/js/fields/Detail/AudioField.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<audio
|
||||
v-if="hasPreviewableAudio"
|
||||
v-bind="defaultAttributes"
|
||||
class="w-full"
|
||||
:src="field.previewUrl"
|
||||
controls
|
||||
controlslist="nodownload"
|
||||
/>
|
||||
|
||||
<span v-if="!hasPreviewableAudio">—</span>
|
||||
|
||||
<p v-if="shouldShowToolbar" class="flex items-center text-sm mt-3">
|
||||
<a
|
||||
v-if="field.downloadable"
|
||||
:dusk="field.attribute + '-download-link'"
|
||||
@keydown.enter.prevent="download"
|
||||
@click.prevent="download"
|
||||
tabindex="0"
|
||||
class="cursor-pointer text-gray-500 inline-flex items-center"
|
||||
>
|
||||
<Icon
|
||||
class="mr-2"
|
||||
type="download"
|
||||
view-box="0 0 24 24"
|
||||
width="16"
|
||||
height="16"
|
||||
/>
|
||||
<span class="class mt-1">{{ __('Download') }}</span>
|
||||
</a>
|
||||
</p>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import isNil from 'lodash/isNil'
|
||||
import { FieldValue } from '@/mixins'
|
||||
|
||||
export default {
|
||||
mixins: [FieldValue],
|
||||
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Download the linked file
|
||||
*/
|
||||
download() {
|
||||
const { resourceName, resourceId } = this
|
||||
const attribute = this.field.attribute
|
||||
|
||||
let link = document.createElement('a')
|
||||
link.href = `/nova-api/${resourceName}/${resourceId}/download/${attribute}`
|
||||
link.download = 'download'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasPreviewableAudio() {
|
||||
return !isNil(this.field.previewUrl)
|
||||
},
|
||||
|
||||
shouldShowToolbar() {
|
||||
return Boolean(this.field.downloadable && this.fieldHasValue)
|
||||
},
|
||||
|
||||
defaultAttributes() {
|
||||
return {
|
||||
src: this.field.previewUrl,
|
||||
autoplay: this.field.autoplay,
|
||||
preload: this.field.preload,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
19
nova/resources/js/fields/Detail/BadgeField.vue
Normal file
19
nova/resources/js/fields/Detail/BadgeField.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<Badge class="mt-1" :label="field.label" :extra-classes="field.typeClass">
|
||||
<template #icon>
|
||||
<span v-if="field.icon" class="mr-1 -ml-1">
|
||||
<Icon :solid="true" :type="field.icon" />
|
||||
</span>
|
||||
</template>
|
||||
</Badge>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
}
|
||||
</script>
|
||||
40
nova/resources/js/fields/Detail/BelongsToField.vue
Normal file
40
nova/resources/js/fields/Detail/BelongsToField.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<span v-if="field.viewable && field.value">
|
||||
<RelationPeek
|
||||
v-if="field.peekable && field.hasFieldsToPeekAt"
|
||||
:resource-name="field.resourceName"
|
||||
:resource-id="field.belongsToId"
|
||||
:resource="resource"
|
||||
>
|
||||
<Link
|
||||
@click.stop
|
||||
:href="
|
||||
$url(`/resources/${field.resourceName}/${field.belongsToId}`)
|
||||
"
|
||||
class="link-default"
|
||||
>
|
||||
{{ field.value }}
|
||||
</Link>
|
||||
</RelationPeek>
|
||||
|
||||
<Link
|
||||
v-else
|
||||
:href="$url(`/resources/${field.resourceName}/${field.belongsToId}`)"
|
||||
class="link-default"
|
||||
>
|
||||
{{ field.value }}
|
||||
</Link>
|
||||
</span>
|
||||
<p v-else-if="field.value">{{ field.value }}</p>
|
||||
<p v-else>—</p>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
}
|
||||
</script>
|
||||
31
nova/resources/js/fields/Detail/BelongsToManyField.vue
Normal file
31
nova/resources/js/fields/Detail/BelongsToManyField.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<ResourceIndex
|
||||
:field="field"
|
||||
:resource-name="field.resourceName"
|
||||
:via-resource="resourceName"
|
||||
:via-resource-id="resourceId"
|
||||
:via-relationship="field.belongsToManyRelationship"
|
||||
:relationship-type="'belongsToMany'"
|
||||
@actionExecuted="actionExecuted"
|
||||
:load-cards="false"
|
||||
:initialPerPage="field.perPage || 5"
|
||||
:should-override-meta="false"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ['actionExecuted'],
|
||||
|
||||
props: ['resourceName', 'resourceId', 'resource', 'field'],
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Handle the actionExecuted event and pass it up the chain.
|
||||
*/
|
||||
actionExecuted() {
|
||||
this.$emit('actionExecuted')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
33
nova/resources/js/fields/Detail/BooleanField.vue
Normal file
33
nova/resources/js/fields/Detail/BooleanField.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<Icon
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
height="24"
|
||||
:type="type"
|
||||
:class="color"
|
||||
/>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
|
||||
computed: {
|
||||
label() {
|
||||
return this.field.value == true ? this.__('Yes') : this.__('No')
|
||||
},
|
||||
|
||||
type() {
|
||||
return this.field.value == true ? 'check-circle' : 'x-circle'
|
||||
},
|
||||
|
||||
color() {
|
||||
return this.field.value == true ? 'text-green-500' : 'text-red-500'
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
57
nova/resources/js/fields/Detail/BooleanGroupField.vue
Normal file
57
nova/resources/js/fields/Detail/BooleanGroupField.vue
Normal file
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<ul v-if="value.length > 0" class="space-y-2">
|
||||
<li
|
||||
v-for="option in value"
|
||||
:class="classes[option.checked]"
|
||||
class="flex items-center rounded-full font-bold text-sm leading-tight space-x-2"
|
||||
>
|
||||
<IconBoolean class="flex-none" :value="option.checked" />
|
||||
<span>{{ option.label }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<span v-else>{{ this.field.noValueText }}</span>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import filter from 'lodash/filter'
|
||||
import map from 'lodash/map'
|
||||
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
|
||||
data: () => ({
|
||||
value: [],
|
||||
classes: {
|
||||
true: 'text-green-500',
|
||||
false: 'text-red-500',
|
||||
},
|
||||
}),
|
||||
|
||||
created() {
|
||||
this.field.value = this.field.value || {}
|
||||
|
||||
this.value = filter(
|
||||
map(this.field.options, o => {
|
||||
return {
|
||||
name: o.name,
|
||||
label: o.label,
|
||||
checked: this.field.value[o.name] || false,
|
||||
}
|
||||
}),
|
||||
o => {
|
||||
if (this.field.hideFalseValues === true && o.checked === false) {
|
||||
return false
|
||||
} else if (this.field.hideTrueValues === true && o.checked === true) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
)
|
||||
},
|
||||
}
|
||||
</script>
|
||||
51
nova/resources/js/fields/Detail/CodeField.vue
Normal file
51
nova/resources/js/fields/Detail/CodeField.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<div
|
||||
v-if="fieldValue"
|
||||
class="form-input form-control-bordered px-0 overflow-hidden"
|
||||
>
|
||||
<textarea ref="theTextarea" />
|
||||
</div>
|
||||
<p v-else>—</p>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CodeMirror from 'codemirror'
|
||||
import { FieldValue } from '@/mixins'
|
||||
import isNull from 'lodash/isNull'
|
||||
|
||||
export default {
|
||||
mixins: [FieldValue],
|
||||
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
|
||||
codemirror: null,
|
||||
|
||||
/**
|
||||
* Mount the component.
|
||||
*/
|
||||
mounted() {
|
||||
const fieldValue = this.fieldValue
|
||||
|
||||
if (!isNull(fieldValue)) {
|
||||
const config = {
|
||||
tabSize: 4,
|
||||
indentWithTabs: true,
|
||||
lineWrapping: true,
|
||||
lineNumbers: true,
|
||||
theme: 'dracula',
|
||||
...this.field.options,
|
||||
readOnly: true,
|
||||
tabindex: '-1', // The editor is for display only and should not be tabbable.
|
||||
}
|
||||
|
||||
this.codemirror = CodeMirror.fromTextArea(this.$refs.theTextarea, config)
|
||||
this.codemirror?.getDoc().setValue(fieldValue)
|
||||
this.codemirror?.setSize('100%', this.field.height)
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
20
nova/resources/js/fields/Detail/ColorField.vue
Normal file
20
nova/resources/js/fields/Detail/ColorField.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<div
|
||||
class="rounded-lg inline-flex items-center justify-center border border-60 p-1"
|
||||
>
|
||||
<span
|
||||
class="block w-6 h-6"
|
||||
:style="{ borderRadius: '5px', backgroundColor: field.value }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
}
|
||||
</script>
|
||||
9
nova/resources/js/fields/Detail/CurrencyField.vue
Normal file
9
nova/resources/js/fields/Detail/CurrencyField.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
}
|
||||
</script>
|
||||
37
nova/resources/js/fields/Detail/DateField.vue
Normal file
37
nova/resources/js/fields/Detail/DateField.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<p v-if="fieldHasValue || usesCustomizedDisplay" :title="field.value">
|
||||
{{ formattedDate }}
|
||||
</p>
|
||||
<p v-else>—</p>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DateTime } from 'luxon'
|
||||
import { FieldValue } from '@/mixins'
|
||||
|
||||
export default {
|
||||
mixins: [FieldValue],
|
||||
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
|
||||
computed: {
|
||||
formattedDate() {
|
||||
if (this.field.usesCustomizedDisplay) {
|
||||
return this.field.displayedAs
|
||||
}
|
||||
|
||||
let isoDate = DateTime.fromISO(this.field.value)
|
||||
|
||||
return isoDate.toLocaleString({
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
44
nova/resources/js/fields/Detail/DateTimeField.vue
Normal file
44
nova/resources/js/fields/Detail/DateTimeField.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<p v-if="fieldHasValue || usesCustomizedDisplay" :title="field.value">
|
||||
{{ formattedDateTime }}
|
||||
</p>
|
||||
<p v-else>—</p>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { DateTime } from 'luxon'
|
||||
import { FieldValue } from '@/mixins'
|
||||
|
||||
export default {
|
||||
mixins: [FieldValue],
|
||||
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
|
||||
computed: {
|
||||
formattedDateTime() {
|
||||
if (this.usesCustomizedDisplay) {
|
||||
return this.field.displayedAs
|
||||
}
|
||||
|
||||
return DateTime.fromISO(this.field.value)
|
||||
.setZone(this.timezone)
|
||||
.toLocaleString({
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
timeZoneName: 'short',
|
||||
})
|
||||
},
|
||||
|
||||
timezone() {
|
||||
return Nova.config('userTimezone') || Nova.config('timezone')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
35
nova/resources/js/fields/Detail/EmailField.vue
Normal file
35
nova/resources/js/fields/Detail/EmailField.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<p v-if="fieldHasValue" class="flex items-center">
|
||||
<a :href="`mailto:${field.value}`" class="link-default">
|
||||
{{ fieldValue }}
|
||||
</a>
|
||||
|
||||
<CopyButton
|
||||
v-if="fieldHasValue && field.copyable && !shouldDisplayAsHtml"
|
||||
@click.prevent.stop="copy"
|
||||
v-tooltip="__('Copy to clipboard')"
|
||||
class="mx-0"
|
||||
/>
|
||||
</p>
|
||||
<p v-else>—</p>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { CopiesToClipboard, FieldValue } from '@/mixins'
|
||||
|
||||
export default {
|
||||
mixins: [CopiesToClipboard, FieldValue],
|
||||
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
|
||||
methods: {
|
||||
copy() {
|
||||
this.copyValueToClipboard(this.field.value)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
88
nova/resources/js/fields/Detail/FileField.vue
Normal file
88
nova/resources/js/fields/Detail/FileField.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<ImageLoader
|
||||
v-if="shouldShowLoader"
|
||||
:src="imageUrl"
|
||||
:maxWidth="field.maxWidth || field.detailWidth"
|
||||
:rounded="field.rounded"
|
||||
:aspect="field.aspect"
|
||||
/>
|
||||
|
||||
<span v-if="fieldValue && !imageUrl" class="break-words">
|
||||
{{ fieldValue }}
|
||||
</span>
|
||||
|
||||
<span v-if="!fieldValue && !imageUrl">—</span>
|
||||
|
||||
<p v-if="shouldShowToolbar" class="flex items-center text-sm mt-3">
|
||||
<a
|
||||
v-if="field.downloadable"
|
||||
:dusk="field.attribute + '-download-link'"
|
||||
@keydown.enter.prevent="download"
|
||||
@click.prevent="download"
|
||||
tabindex="0"
|
||||
class="cursor-pointer text-gray-500 inline-flex items-center"
|
||||
>
|
||||
<Icon
|
||||
class="mr-2"
|
||||
type="download"
|
||||
view-box="0 0 24 24"
|
||||
width="16"
|
||||
height="16"
|
||||
/>
|
||||
<span class="class mt-1">{{ __('Download') }}</span>
|
||||
</a>
|
||||
</p>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { FieldValue } from '@/mixins'
|
||||
|
||||
export default {
|
||||
mixins: [FieldValue],
|
||||
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Download the linked file
|
||||
*/
|
||||
download() {
|
||||
const { resourceName, resourceId } = this
|
||||
const attribute = this.fieldAttribute
|
||||
|
||||
let link = document.createElement('a')
|
||||
link.href = `/nova-api/${resourceName}/${resourceId}/download/${attribute}`
|
||||
link.download = 'download'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasValue() {
|
||||
return Boolean(this.field.value || this.imageUrl)
|
||||
},
|
||||
|
||||
shouldShowLoader() {
|
||||
return this.imageUrl
|
||||
},
|
||||
|
||||
shouldShowToolbar() {
|
||||
return Boolean(this.field.downloadable && this.hasValue)
|
||||
},
|
||||
|
||||
imageUrl() {
|
||||
return this.field.previewUrl || this.field.thumbnailUrl
|
||||
},
|
||||
|
||||
isVaporField() {
|
||||
return this.field.component === 'vapor-file-field'
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
37
nova/resources/js/fields/Detail/HasManyField.vue
Normal file
37
nova/resources/js/fields/Detail/HasManyField.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<ResourceIndex
|
||||
:field="field"
|
||||
:resource-name="field.resourceName"
|
||||
:via-resource="resourceName"
|
||||
:via-resource-id="resourceId"
|
||||
:via-relationship="field.hasManyRelationship"
|
||||
:relationship-type="'hasMany'"
|
||||
@actionExecuted="actionExecuted"
|
||||
:load-cards="false"
|
||||
:initialPerPage="field.perPage || 5"
|
||||
:should-override-meta="false"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapProps } from '@/mixins'
|
||||
|
||||
export default {
|
||||
emits: ['actionExecuted'],
|
||||
|
||||
props: {
|
||||
...mapProps(['resourceId', 'field']),
|
||||
resourceName: {},
|
||||
resource: {},
|
||||
},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Handle the actionExecuted event and pass it up the chain.
|
||||
*/
|
||||
actionExecuted() {
|
||||
this.$emit('actionExecuted')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
31
nova/resources/js/fields/Detail/HasManyThroughField.vue
Normal file
31
nova/resources/js/fields/Detail/HasManyThroughField.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<ResourceIndex
|
||||
:field="field"
|
||||
:resource-name="field.resourceName"
|
||||
:via-resource="resourceName"
|
||||
:via-resource-id="resourceId"
|
||||
:via-relationship="field.hasManyThroughRelationship"
|
||||
:relationship-type="'hasManyThrough'"
|
||||
@actionExecuted="actionExecuted"
|
||||
:load-cards="false"
|
||||
:initialPerPage="field.perPage || 5"
|
||||
:should-override-meta="false"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ['actionExecuted'],
|
||||
|
||||
props: ['resourceName', 'resourceId', 'resource', 'field'],
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Handle the actionExecuted event and pass it up the chain.
|
||||
*/
|
||||
actionExecuted() {
|
||||
this.$emit('actionExecuted')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
70
nova/resources/js/fields/Detail/HasOneField.vue
Normal file
70
nova/resources/js/fields/Detail/HasOneField.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="field.authorizedToView"
|
||||
class="relative"
|
||||
:dusk="field.resourceName + '-index-component'"
|
||||
:data-relationship="viaRelationship"
|
||||
>
|
||||
<template v-if="!hasRelation">
|
||||
<Heading :level="1" class="mb-3 flex items-center">{{
|
||||
field.singularLabel
|
||||
}}</Heading>
|
||||
<Card>
|
||||
<IndexEmptyDialog
|
||||
:create-button-label="createButtonLabel"
|
||||
:singular-name="singularName"
|
||||
:resource-name="field.resourceName"
|
||||
:via-resource="resourceName"
|
||||
:via-resource-id="viaResourceId"
|
||||
:via-relationship="viaRelationship"
|
||||
:relationship-type="field.relationshipType"
|
||||
:authorized-to-create="authorizedToCreate"
|
||||
:authorized-to-relate="true"
|
||||
/>
|
||||
</Card>
|
||||
</template>
|
||||
<div v-else>
|
||||
<ResourceDetail
|
||||
:resource-name="field.resourceName"
|
||||
:resource-id="field.hasOneId"
|
||||
:via-resource="resourceName"
|
||||
:via-resource-id="viaResourceId"
|
||||
:via-relationship="viaRelationship"
|
||||
:relationship-type="field.relationshipType"
|
||||
:show-view-link="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['resourceName', 'resourceId', 'resource', 'field'],
|
||||
|
||||
computed: {
|
||||
authorizedToCreate() {
|
||||
return this.field.authorizedToCreate
|
||||
},
|
||||
|
||||
createButtonLabel() {
|
||||
return this.field.createButtonLabel
|
||||
},
|
||||
|
||||
hasRelation() {
|
||||
return this.field.hasOneId != null
|
||||
},
|
||||
|
||||
singularName() {
|
||||
return this.field.singularLabel
|
||||
},
|
||||
|
||||
viaResourceId() {
|
||||
return this.resource.id.value
|
||||
},
|
||||
|
||||
viaRelationship() {
|
||||
return this.field.hasOneRelationship
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
70
nova/resources/js/fields/Detail/HasOneThroughField.vue
Normal file
70
nova/resources/js/fields/Detail/HasOneThroughField.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="field.authorizedToView"
|
||||
class="relative"
|
||||
:dusk="field.resourceName + '-index-component'"
|
||||
:data-relationship="viaRelationship"
|
||||
>
|
||||
<template v-if="!hasRelation">
|
||||
<Heading :level="1" class="mb-3 flex items-center">{{
|
||||
field.singularLabel
|
||||
}}</Heading>
|
||||
<Card>
|
||||
<IndexEmptyDialog
|
||||
:create-button-label="createButtonLabel"
|
||||
:singular-name="singularName"
|
||||
:resource-name="field.resourceName"
|
||||
:via-resource="resourceName"
|
||||
:via-resource-id="viaResourceId"
|
||||
:via-relationship="viaRelationship"
|
||||
:relationship-type="field.relationshipType"
|
||||
:authorized-to-create="false"
|
||||
:authorized-to-relate="false"
|
||||
/>
|
||||
</Card>
|
||||
</template>
|
||||
<div v-else>
|
||||
<ResourceDetail
|
||||
:resource-name="field.resourceName"
|
||||
:resource-id="field.hasOneThroughId"
|
||||
:via-resource="resourceName"
|
||||
:via-resource-id="viaResourceId"
|
||||
:via-relationship="viaRelationship"
|
||||
:relationship-type="field.relationshipType"
|
||||
:show-view-link="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['resourceName', 'resourceId', 'resource', 'field'],
|
||||
|
||||
computed: {
|
||||
authorizedToCreate() {
|
||||
return this.field.authorizedToCreate
|
||||
},
|
||||
|
||||
createButtonLabel() {
|
||||
return this.field.createButtonLabel
|
||||
},
|
||||
|
||||
hasRelation() {
|
||||
return this.field.hasOneThroughId != null
|
||||
},
|
||||
|
||||
singularName() {
|
||||
return this.field.singularLabel
|
||||
},
|
||||
|
||||
viaResourceId() {
|
||||
return this.resource.id.value
|
||||
},
|
||||
|
||||
viaRelationship() {
|
||||
return this.field.hasOneThroughRelationship
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
44
nova/resources/js/fields/Detail/HeadingField.vue
Normal file
44
nova/resources/js/fields/Detail/HeadingField.vue
Normal file
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div
|
||||
class="-mx-6"
|
||||
:class="{
|
||||
'border-t border-gray-100 dark:border-gray-700': index !== 0,
|
||||
'-mt-2': index === 0,
|
||||
}"
|
||||
>
|
||||
<div class="w-full py-4 px-6">
|
||||
<slot name="value">
|
||||
<Heading :level="3" v-if="fieldValue && !shouldDisplayAsHtml">
|
||||
{{ fieldValue }}
|
||||
</Heading>
|
||||
<div
|
||||
v-else-if="fieldValue && shouldDisplayAsHtml"
|
||||
v-html="field.value"
|
||||
></div>
|
||||
<p v-else>—</p>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import filled from '@/util/filled'
|
||||
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
|
||||
computed: {
|
||||
fieldValue() {
|
||||
if (!filled(this.field.value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return String(this.field.value)
|
||||
},
|
||||
|
||||
shouldDisplayAsHtml() {
|
||||
return this.field.asHtml
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
9
nova/resources/js/fields/Detail/HiddenField.vue
Normal file
9
nova/resources/js/fields/Detail/HiddenField.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div class="hidden" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
}
|
||||
</script>
|
||||
9
nova/resources/js/fields/Detail/IdField.vue
Normal file
9
nova/resources/js/fields/Detail/IdField.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
}
|
||||
</script>
|
||||
48
nova/resources/js/fields/Detail/KeyValueField.vue
Normal file
48
nova/resources/js/fields/Detail/KeyValueField.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<FormKeyValueTable
|
||||
v-if="theData.length > 0"
|
||||
:edit-mode="false"
|
||||
class="overflow-hidden"
|
||||
>
|
||||
<FormKeyValueHeader
|
||||
:key-label="field.keyLabel"
|
||||
:value-label="field.valueLabel"
|
||||
/>
|
||||
|
||||
<div
|
||||
class="bg-gray-50 dark:bg-gray-700 overflow-hidden key-value-items"
|
||||
>
|
||||
<FormKeyValueItem
|
||||
v-for="(item, index) in theData"
|
||||
:index="index"
|
||||
:item="item"
|
||||
:disabled="true"
|
||||
:key="item.key"
|
||||
/>
|
||||
</div>
|
||||
</FormKeyValueTable>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import map from 'lodash/map'
|
||||
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
|
||||
data: () => ({ theData: [] }),
|
||||
|
||||
created() {
|
||||
this.theData = map(
|
||||
Object.entries(this.field.value || {}),
|
||||
([key, value]) => ({
|
||||
key: `${key}`,
|
||||
value,
|
||||
})
|
||||
)
|
||||
},
|
||||
}
|
||||
</script>
|
||||
19
nova/resources/js/fields/Detail/MarkdownField.vue
Normal file
19
nova/resources/js/fields/Detail/MarkdownField.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<Excerpt :content="excerpt" :should-show="field.shouldShow" />
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
|
||||
computed: {
|
||||
excerpt() {
|
||||
return this.field.previewFor
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
26
nova/resources/js/fields/Detail/MorphToActionTargetField.vue
Normal file
26
nova/resources/js/fields/Detail/MorphToActionTargetField.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<Link
|
||||
v-if="field.viewable && field.value"
|
||||
:href="$url(`/resources/${field.resourceName}/${field.morphToId}`)"
|
||||
class="no-underline font-bold link-default"
|
||||
>
|
||||
{{ field.name }}: {{ field.value }} ({{ field.resourceLabel }})
|
||||
</Link>
|
||||
<p v-else-if="field.morphToId && field.resourceLabel !== null">
|
||||
{{ field.name }}: {{ field.morphToId }} ({{ field.resourceLabel }})
|
||||
</p>
|
||||
<p v-else-if="field.morphToId && field.resourceLabel === null">
|
||||
{{ field.morphToType }}: {{ field.morphToId }}
|
||||
</p>
|
||||
<p v-else>—</p>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['index', 'resourceName', 'resourceId', 'field'],
|
||||
}
|
||||
</script>
|
||||
41
nova/resources/js/fields/Detail/MorphToField.vue
Normal file
41
nova/resources/js/fields/Detail/MorphToField.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field" :field-name="field.name">
|
||||
<template #value>
|
||||
<span v-if="field.viewable && field.value">
|
||||
<RelationPeek
|
||||
v-if="field.peekable && field.hasFieldsToPeekAt"
|
||||
:resource-name="field.resourceName"
|
||||
:resource-id="field.morphToId"
|
||||
:resource="resource"
|
||||
>
|
||||
<Link
|
||||
@click.stop
|
||||
:href="$url(`/resources/${field.resourceName}/${field.morphToId}`)"
|
||||
class="link-default"
|
||||
>
|
||||
{{ field.resourceLabel }}: {{ field.value }}
|
||||
</Link>
|
||||
</RelationPeek>
|
||||
|
||||
<Link
|
||||
v-else
|
||||
:href="$url(`/resources/${field.resourceName}/${field.morphToId}`)"
|
||||
class="link-default"
|
||||
>
|
||||
{{ field.resourceLabel }}: {{ field.value }}
|
||||
</Link>
|
||||
</span>
|
||||
|
||||
<p v-else-if="field.value">
|
||||
{{ field.resourceLabel || field.morphToType }}: {{ field.value }}
|
||||
</p>
|
||||
<p v-else>—</p>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
}
|
||||
</script>
|
||||
31
nova/resources/js/fields/Detail/MorphToManyField.vue
Normal file
31
nova/resources/js/fields/Detail/MorphToManyField.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<ResourceIndex
|
||||
:field="field"
|
||||
:resource-name="field.resourceName"
|
||||
:via-resource="resourceName"
|
||||
:via-resource-id="resourceId"
|
||||
:via-relationship="field.morphToManyRelationship"
|
||||
:relationship-type="'morphToMany'"
|
||||
@actionExecuted="actionExecuted"
|
||||
:load-cards="false"
|
||||
:initialPerPage="field.perPage || 5"
|
||||
:should-override-meta="false"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
emits: ['actionExecuted'],
|
||||
|
||||
props: ['resourceName', 'resourceId', 'resource', 'field'],
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Handle the actionExecuted event and pass it up the chain.
|
||||
*/
|
||||
actionExecuted() {
|
||||
this.$emit('actionExecuted')
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
36
nova/resources/js/fields/Detail/MultiSelectField.vue
Normal file
36
nova/resources/js/fields/Detail/MultiSelectField.vue
Normal file
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template v-slot:value>
|
||||
<span
|
||||
v-for="item in fieldValues"
|
||||
v-text="item"
|
||||
class="inline-block text-sm mb-1 mr-2 px-2 py-0 bg-primary-500 text-white dark:text-gray-900 rounded"
|
||||
/>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { FieldValue } from '@/mixins'
|
||||
import forEach from 'lodash/forEach'
|
||||
|
||||
export default {
|
||||
mixins: [FieldValue],
|
||||
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
|
||||
computed: {
|
||||
fieldValues() {
|
||||
let selected = []
|
||||
|
||||
forEach(this.field.options, option => {
|
||||
if (this.isEqualsToValue(option.value)) {
|
||||
selected.push(option.label)
|
||||
}
|
||||
})
|
||||
|
||||
return selected
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
110
nova/resources/js/fields/Detail/Panel.vue
Normal file
110
nova/resources/js/fields/Detail/Panel.vue
Normal file
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div>
|
||||
<slot>
|
||||
<div class="flex items-center">
|
||||
<Heading :level="1" v-text="panel.name" />
|
||||
|
||||
<button
|
||||
v-if="panel.collapsable"
|
||||
@click="toggleCollapse"
|
||||
class="rounded border border-transparent h-6 w-6 ml-1 inline-flex items-center justify-center focus:outline-none focus:ring focus:ring-primary-200"
|
||||
:aria-label="__('Toggle Collapsed')"
|
||||
:aria-expanded="collapsed === false ? 'true' : 'false'"
|
||||
>
|
||||
<CollapseButton :collapsed="collapsed" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p
|
||||
v-if="panel.helpText && !collapsed"
|
||||
class="text-gray-500 text-sm font-semibold italic"
|
||||
:class="panel.helpText ? 'mt-1' : 'mt-3'"
|
||||
v-html="panel.helpText"
|
||||
/>
|
||||
</slot>
|
||||
|
||||
<Card
|
||||
class="mt-3 py-2 px-6 divide-y divide-gray-100 dark:divide-gray-700"
|
||||
v-if="!collapsed && fields.length > 0"
|
||||
>
|
||||
<component
|
||||
:key="index"
|
||||
v-for="(field, index) in fields"
|
||||
:index="index"
|
||||
:is="resolveComponentName(field)"
|
||||
:resource-name="resourceName"
|
||||
:resource-id="resourceId"
|
||||
:resource="resource"
|
||||
:field="field"
|
||||
@actionExecuted="actionExecuted"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="shouldShowShowAllFieldsButton"
|
||||
class="-mx-6 border-t border-gray-100 dark:border-gray-700 text-center rounded-b"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="block w-full text-sm link-default font-bold py-2 -mb-2"
|
||||
@click="showAllFields"
|
||||
>
|
||||
{{ __('Show All Fields') }}
|
||||
</button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Collapsable, BehavesAsPanel } from '@/mixins'
|
||||
|
||||
export default {
|
||||
mixins: [Collapsable, BehavesAsPanel],
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* Resolve the component name.
|
||||
*/
|
||||
resolveComponentName(field) {
|
||||
return field.prefixComponent
|
||||
? 'detail-' + field.component
|
||||
: field.component
|
||||
},
|
||||
|
||||
/**
|
||||
* Show all of the Panel's fields.
|
||||
*/
|
||||
showAllFields() {
|
||||
return (this.panel.limit = 0)
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
localStorageKey() {
|
||||
return `nova.panels.${this.panel.attribute}.collapsed`
|
||||
},
|
||||
|
||||
collapsedByDefault() {
|
||||
return this.panel?.collapsedByDefault ?? false
|
||||
},
|
||||
|
||||
/**
|
||||
* Limits the visible fields.
|
||||
*/
|
||||
fields() {
|
||||
if (this.panel.limit > 0) {
|
||||
return this.panel.fields.slice(0, this.panel.limit)
|
||||
}
|
||||
|
||||
return this.panel.fields
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if should display the 'Show all fields' button.
|
||||
*/
|
||||
shouldShowShowAllFieldsButton() {
|
||||
return this.panel.limit > 0
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
15
nova/resources/js/fields/Detail/PasswordField.vue
Normal file
15
nova/resources/js/fields/Detail/PasswordField.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<p>
|
||||
·········
|
||||
</p>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
}
|
||||
</script>
|
||||
7
nova/resources/js/fields/Detail/PlaceField.vue
Normal file
7
nova/resources/js/fields/Detail/PlaceField.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script>
|
||||
import TextField from '@/fields/Detail/TextField'
|
||||
|
||||
export default {
|
||||
extends: TextField,
|
||||
}
|
||||
</script>
|
||||
27
nova/resources/js/fields/Detail/RelationshipPanel.vue
Normal file
27
nova/resources/js/fields/Detail/RelationshipPanel.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div>
|
||||
<component
|
||||
:key="`${field.attribute}:${resourceId}`"
|
||||
:is="`detail-${field.component}`"
|
||||
:resource-name="resourceName"
|
||||
:resource-id="resourceId"
|
||||
:resource="resource"
|
||||
:field="field"
|
||||
@actionExecuted="actionExecuted"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { BehavesAsPanel } from '@/mixins'
|
||||
|
||||
export default {
|
||||
mixins: [BehavesAsPanel],
|
||||
|
||||
computed: {
|
||||
field() {
|
||||
return this.panel.fields[0]
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
9
nova/resources/js/fields/Detail/SelectField.vue
Normal file
9
nova/resources/js/fields/Detail/SelectField.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
}
|
||||
</script>
|
||||
7
nova/resources/js/fields/Detail/SlugField.vue
Normal file
7
nova/resources/js/fields/Detail/SlugField.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script>
|
||||
import TextField from '@/fields/Detail/TextField'
|
||||
|
||||
export default {
|
||||
extends: TextField,
|
||||
}
|
||||
</script>
|
||||
91
nova/resources/js/fields/Detail/SparklineField.vue
Normal file
91
nova/resources/js/fields/Detail/SparklineField.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<div
|
||||
ref="chart"
|
||||
class="ct-chart"
|
||||
:style="{ width: chartWidth, height: chartHeight }"
|
||||
/>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Chartist from 'chartist'
|
||||
import 'chartist/dist/chartist.min.css'
|
||||
|
||||
// Default chart diameters.
|
||||
const defaultHeight = 120
|
||||
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
|
||||
data: () => ({ chartist: null }),
|
||||
|
||||
watch: {
|
||||
'field.data': function (newData, oldData) {
|
||||
this.renderChart()
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
renderChart() {
|
||||
this.chartist.update(this.field.data)
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.chartist = new Chartist[this.chartStyle](
|
||||
this.$refs.chart,
|
||||
{ series: [this.field.data] },
|
||||
{
|
||||
height: this.chartHeight,
|
||||
width: this.chartWidth,
|
||||
showPoint: false,
|
||||
fullWidth: true,
|
||||
chartPadding: { top: 0, right: 0, bottom: 0, left: 0 },
|
||||
axisX: { showGrid: false, showLabel: false, offset: 0 },
|
||||
axisY: { showGrid: false, showLabel: false, offset: 0 },
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
computed: {
|
||||
/**
|
||||
* Determine if the field has a value other than null.
|
||||
*/
|
||||
hasData() {
|
||||
return this.field.data.length > 0
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine the chart style.
|
||||
*/
|
||||
chartStyle() {
|
||||
const validTypes = ['line', 'bar']
|
||||
let chartStyle = this.field.chartStyle.toLowerCase()
|
||||
|
||||
// Line and Bar are the only valid types.
|
||||
if (!validTypes.includes(chartStyle)) return 'Line'
|
||||
|
||||
return chartStyle.charAt(0).toUpperCase() + chartStyle.slice(1)
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine the chart height.
|
||||
*/
|
||||
chartHeight() {
|
||||
if (this.field.height) return `${this.field.height}px`
|
||||
|
||||
return `${defaultHeight}px`
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine the chart width.
|
||||
*/
|
||||
chartWidth() {
|
||||
if (this.field.width) return `${this.field.width}px`
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
35
nova/resources/js/fields/Detail/StackField.vue
Normal file
35
nova/resources/js/fields/Detail/StackField.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<div
|
||||
v-if="hasValue"
|
||||
:class="`text-${field.textAlign}`"
|
||||
class="leading-normal"
|
||||
>
|
||||
<component
|
||||
v-for="line in field.lines"
|
||||
:key="line.value"
|
||||
:is="`index-${line.component}`"
|
||||
:field="line"
|
||||
:resourceName="resourceName"
|
||||
/>
|
||||
</div>
|
||||
<p v-else>—</p>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
|
||||
computed: {
|
||||
/**
|
||||
* Determine if the field has a value other than null.
|
||||
*/
|
||||
hasValue() {
|
||||
return this.field.lines
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
38
nova/resources/js/fields/Detail/StatusField.vue
Normal file
38
nova/resources/js/fields/Detail/StatusField.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<Badge
|
||||
v-if="fieldHasValue"
|
||||
class="whitespace-nowrap inline-flex items-center"
|
||||
:class="field.typeClass"
|
||||
>
|
||||
<span class="mr-1 -ml-1">
|
||||
<Loader v-if="field.type == 'loading'" width="20" class="mr-1" />
|
||||
<Icon
|
||||
v-if="field.type == 'failed'"
|
||||
:solid="true"
|
||||
type="exclamation-circle"
|
||||
/>
|
||||
<Icon
|
||||
v-if="field.type == 'success'"
|
||||
:solid="true"
|
||||
type="check-circle"
|
||||
/>
|
||||
</span>
|
||||
{{ fieldValue }}
|
||||
</Badge>
|
||||
|
||||
<span v-else>—</span>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { FieldValue } from '@/mixins'
|
||||
|
||||
export default {
|
||||
mixins: [FieldValue],
|
||||
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
}
|
||||
</script>
|
||||
28
nova/resources/js/fields/Detail/TagField.vue
Normal file
28
nova/resources/js/fields/Detail/TagField.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<div v-if="field.value.length > 0">
|
||||
<TagGroup
|
||||
v-if="field.style === 'group'"
|
||||
:tags="field.value"
|
||||
:resource-name="field.resourceName"
|
||||
:editable="false"
|
||||
:with-preview="field.withPreview"
|
||||
/>
|
||||
<TagList
|
||||
v-if="field.style === 'list'"
|
||||
:tags="field.value"
|
||||
:resource-name="field.resourceName"
|
||||
:editable="false"
|
||||
:with-preview="field.withPreview"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
}
|
||||
</script>
|
||||
9
nova/resources/js/fields/Detail/TextField.vue
Normal file
9
nova/resources/js/fields/Detail/TextField.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
}
|
||||
</script>
|
||||
17
nova/resources/js/fields/Detail/TextareaField.vue
Normal file
17
nova/resources/js/fields/Detail/TextareaField.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<Excerpt
|
||||
:content="field.value"
|
||||
:plain-text="true"
|
||||
:should-show="field.shouldShow"
|
||||
/>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
}
|
||||
</script>
|
||||
13
nova/resources/js/fields/Detail/TrixField.vue
Normal file
13
nova/resources/js/fields/Detail/TrixField.vue
Normal file
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template #value>
|
||||
<Excerpt :content="field.value" :should-show="field.shouldShow" />
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
}
|
||||
</script>
|
||||
31
nova/resources/js/fields/Detail/UrlField.vue
Normal file
31
nova/resources/js/fields/Detail/UrlField.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<PanelItem :index="index" :field="field">
|
||||
<template v-slot:value>
|
||||
<p v-if="fieldHasValue && !shouldDisplayAsHtml">
|
||||
<a
|
||||
class="link-default"
|
||||
:href="field.value"
|
||||
rel="noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
{{ fieldValue }}
|
||||
</a>
|
||||
</p>
|
||||
<div
|
||||
v-else-if="fieldValue && shouldDisplayAsHtml"
|
||||
v-html="fieldValue"
|
||||
></div>
|
||||
<p v-else>—</p>
|
||||
</template>
|
||||
</PanelItem>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { FieldValue } from '@/mixins'
|
||||
|
||||
export default {
|
||||
mixins: [FieldValue],
|
||||
|
||||
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
|
||||
}
|
||||
</script>
|
||||
7
nova/resources/js/fields/Detail/VaporAudioField.vue
Normal file
7
nova/resources/js/fields/Detail/VaporAudioField.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script>
|
||||
import AudioField from '@/fields/Detail/AudioField'
|
||||
|
||||
export default {
|
||||
extends: AudioField,
|
||||
}
|
||||
</script>
|
||||
7
nova/resources/js/fields/Detail/VaporFileField.vue
Normal file
7
nova/resources/js/fields/Detail/VaporFileField.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<script>
|
||||
import FileField from '@/fields/Detail/FileField'
|
||||
|
||||
export default {
|
||||
extends: FileField,
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user