This commit is contained in:
2024-09-01 18:54:23 +05:00
parent 76d18365a5
commit 061f09eca1
1597 changed files with 109451 additions and 1 deletions

View File

@@ -0,0 +1,46 @@
<template>
<div :class="alignmentClass" class="flex">
<audio
v-if="hasPreviewableAudio"
v-bind="defaultAttributes"
class="rounded rounded-full"
:src="field.previewUrl"
controls
controlslist="nodownload"
/>
<p v-else :class="`text-${field.textAlign}`">&mdash;</p>
</div>
</template>
<script>
import isNil from 'lodash/isNil'
import { FieldValue } from '@/mixins'
export default {
mixins: [FieldValue],
props: ['viaResource', 'viaResourceId', 'resourceName', 'field'],
computed: {
hasPreviewableAudio() {
return !isNil(this.field.previewUrl)
},
defaultAttributes() {
return {
autoplay: false,
preload: this.field.preload,
}
},
alignmentClass() {
return {
left: 'items-center justify-start',
center: 'items-center justify-center',
right: 'items-center justify-end',
}[this.field.textAlign]
},
},
}
</script>

View File

@@ -0,0 +1,17 @@
<template>
<div>
<Badge :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>
</div>
</template>
<script>
export default {
props: ['resourceName', 'viaResource', 'viaResourceId', 'field'],
}
</script>

View File

@@ -0,0 +1,43 @@
<template>
<div :class="`text-${field.textAlign}`">
<span>
<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
@click.stop
:href="$url(`/resources/${field.resourceName}/${field.belongsToId}`)"
class="link-default"
>
{{ field.value }}
</Link>
</span>
<span v-else-if="field.value">{{ field.value }}</span>
<span v-else>&mdash;</span>
</span>
</div>
</template>
<script setup>
const props = defineProps({
resource: { type: Object },
resourceName: { type: String },
field: { type: Object },
})
</script>

View File

@@ -0,0 +1,11 @@
<template>
<div :class="`text-${field.textAlign}`">
<IconBoolean :value="field.value" />
</div>
</template>
<script>
export default {
props: ['resourceName', 'field'],
}
</script>

View File

@@ -0,0 +1,75 @@
<template>
<div :class="`text-${field.textAlign}`">
<Dropdown>
<Button variant="link">
{{ __('View') }}
</Button>
<template #menu>
<DropdownMenu width="auto">
<ul v-if="value.length > 0" class="max-w-xxs space-y-2 py-3 px-4">
<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 class="ml-1">{{ option.label }}</span>
</li>
</ul>
<span
v-else
class="max-w-xxs space-2 py-3 px-4 rounded-full text-sm leading-tight"
>
{{ field.noValueText }}
</span>
</DropdownMenu>
</template>
</Dropdown>
</div>
</template>
<script>
import filter from 'lodash/filter'
import map from 'lodash/map'
import { Button } from 'laravel-nova-ui'
export default {
components: {
Button,
},
props: ['resourceName', '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>

View File

@@ -0,0 +1,19 @@
<template>
<div :class="`text-${field.textAlign}`">
<span
class="rounded inline-flex items-center justify-center border border-60"
:style="{ borderRadius: '4px', padding: '2px' }"
>
<span
class="block w-4 h-4"
:style="{ borderRadius: '2px', backgroundColor: field.value }"
/>
</span>
</div>
</template>
<script>
export default {
props: ['resourceName', 'field'],
}
</script>

View File

@@ -0,0 +1,19 @@
<template>
<div>
<template v-if="fieldValue">
<div v-if="shouldDisplayAsHtml" @click.stop v-html="fieldValue"></div>
<span v-else>{{ fieldValue }}</span>
</template>
<p v-else>&mdash;</p>
</div>
</template>
<script>
import { FieldValue } from '@/mixins'
export default {
mixins: [FieldValue],
props: ['resourceName', 'field'],
}
</script>

View File

@@ -0,0 +1,37 @@
<template>
<div>
<div :class="`text-${field.textAlign}`">
<span v-if="fieldHasValue" class="whitespace-nowrap">
{{ formattedDate }}
</span>
<span v-else>&mdash;</span>
</div>
</div>
</template>
<script>
import { DateTime } from 'luxon'
import { FieldValue } from '@/mixins'
export default {
mixins: [FieldValue],
props: ['resourceName', '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>

View File

@@ -0,0 +1,46 @@
<template>
<div :class="`text-${field.textAlign}`">
<span
v-if="fieldHasValue || usesCustomizedDisplay"
class="whitespace-nowrap"
:title="field.value"
>
{{ formattedDate }}
</span>
<span v-else>&mdash;</span>
</div>
</template>
<script>
import { DateTime } from 'luxon'
import { FieldValue } from '@/mixins'
export default {
mixins: [FieldValue],
props: ['resourceName', 'field'],
computed: {
formattedDate() {
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>

View File

@@ -0,0 +1,38 @@
<template>
<div :class="`text-${field.textAlign}`">
<p v-if="fieldHasValue" class="flex items-center">
<a
v-if="fieldHasValue"
@click.stop
:href="`mailto:${field.value}`"
class="link-default whitespace-nowrap"
>
{{ 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>&mdash;</p>
</div>
</template>
<script>
import { CopiesToClipboard, FieldValue } from '@/mixins'
export default {
mixins: [CopiesToClipboard, FieldValue],
props: ['resourceName', 'field'],
methods: {
copy() {
this.copyValueToClipboard(this.field.value)
},
},
}
</script>

View File

@@ -0,0 +1,58 @@
<template>
<div :class="alignmentClass" class="flex">
<ImageLoader
v-if="shouldShowLoader"
:src="imageUrl"
:max-width="field.maxWidth || field.indexWidth"
:rounded="field.rounded"
:aspect="field.aspect"
/>
<span
v-if="usesCustomizedDisplay && !imageUrl"
class="break-words"
v-tooltip="field.value"
>
{{ field.displayedAs }}
</span>
<p
v-if="!usesCustomizedDisplay && !imageUrl"
:class="`text-${field.textAlign}`"
v-tooltip="field.value"
>
&mdash;
</p>
</div>
</template>
<script>
import { FieldValue } from '@/mixins'
import { computed } from 'vue'
export default {
mixins: [FieldValue],
props: ['viaResource', 'viaResourceId', 'resourceName', 'field'],
data: () => ({
loading: false,
}),
computed: {
shouldShowLoader() {
return this.imageUrl
},
imageUrl() {
return this.field?.thumbnailUrl || this.field?.previewUrl
},
alignmentClass() {
return {
left: 'items-center justify-start',
center: 'items-center justify-center',
right: 'items-center justify-end',
}[this.field.textAlign]
},
},
}
</script>

View File

@@ -0,0 +1,9 @@
<template>
<span />
</template>
<script>
export default {
props: ['field', 'viaResource', 'viaResourceId', 'resourceName'],
}
</script>

View File

@@ -0,0 +1,9 @@
<template>
<div class="hidden" />
</template>
<script>
export default {
props: ['resourceName', 'field'],
}
</script>

View File

@@ -0,0 +1,37 @@
<template>
<div :class="`text-${field.textAlign}`">
<Link
@click.stop
v-if="fieldHasValue && !isPivot && authorizedToView"
:href="$url(`/resources/${resourceName}/${field.value}`)"
class="link-default"
>
{{ fieldValue }}
</Link>
<p v-else-if="fieldHasValue || isPivot">
{{ field.pivotValue || fieldValue }}
</p>
<p v-else>&mdash;</p>
</div>
</template>
<script>
import isNil from 'lodash/isNil'
import { FieldValue } from '@/mixins'
export default {
mixins: [FieldValue],
props: ['resource', 'resourceName', 'field'],
computed: {
isPivot() {
return !isNil(this.field.pivotValue)
},
authorizedToView() {
return this.resource?.authorizedToView ?? false
},
},
}
</script>

View File

@@ -0,0 +1,21 @@
<template>
<div :class="`text-${field.textAlign}`">
<template v-if="fieldValue">
<div v-if="shouldDisplayAsHtml" @click.stop v-html="fieldValue"></div>
<span v-else class="whitespace-nowrap" :class="field.classes">
{{ fieldValue }}
</span>
</template>
<p v-else>&mdash;</p>
</div>
</template>
<script>
import { FieldValue } from '@/mixins'
export default {
mixins: [FieldValue],
props: ['resourceName', 'field'],
}
</script>

View File

@@ -0,0 +1,34 @@
<template>
<Link
@click.stop
v-if="field.viewable && field.value && !isResourceBeingViewed"
:href="$url(`/resources/${field.resourceName}/${field.morphToId}`)"
class="no-underline text-primary-500 font-bold"
:class="`text-${field.textAlign}`"
>
{{ field.resourceLabel }}: {{ field.value }}
</Link>
<span v-else-if="field.value">
{{ field.resourceLabel || field.morphToType }}: {{ field.value }}
</span>
<span v-else>&mdash;</span>
</template>
<script>
export default {
props: ['resourceName', 'viaResource', 'viaResourceId', 'field'],
computed: {
/**
* Determine if the resource being viewed matches the field's value.
*/
isResourceBeingViewed() {
return (
this.field.morphToType == this.viaResource &&
this.field.morphToId == this.viaResourceId
)
},
},
}
</script>

View File

@@ -0,0 +1,43 @@
<template>
<div :class="`text-${field.textAlign}`">
<span>
<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
@click.stop
:href="$url(`/resources/${field.resourceName}/${field.morphToId}`)"
class="link-default"
>
{{ field.resourceLabel }}: {{ field.value }}
</Link>
</span>
<span v-else-if="field.value">
{{ field.resourceLabel || field.morphToType }}: {{ field.value }}
</span>
<span v-else>&mdash;</span>
</span>
</div>
</template>
<script setup>
const props = defineProps({
resource: { type: Object },
resourceName: { type: String },
field: { type: Object },
})
</script>

View File

@@ -0,0 +1,41 @@
<template>
<div>
<template v-if="hasValues">
<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>
<p v-else>&mdash;</p>
</div>
</template>
<script>
import { FieldValue } from '@/mixins'
import forEach from 'lodash/forEach'
export default {
mixins: [FieldValue],
props: ['resourceName', 'field'],
computed: {
hasValues() {
return this.fieldValues.length > 0
},
fieldValues() {
let selected = []
forEach(this.field.options, option => {
if (this.isEqualsToValue(option.value)) {
selected.push(option.label)
}
})
return selected
},
},
}
</script>

View File

@@ -0,0 +1,13 @@
<template>
<div :class="`text-${field.textAlign}`">
<span class="font-bold">
&middot; &middot; &middot; &middot; &middot; &middot; &middot; &middot;
</span>
</div>
</template>
<script>
export default {
props: ['resourceName', 'field'],
}
</script>

View File

@@ -0,0 +1,7 @@
<script>
import TextField from '@/fields/Index/TextField'
export default {
extends: TextField,
}
</script>

View File

@@ -0,0 +1,19 @@
<template>
<div :class="`text-${field.textAlign}`">
<template v-if="fieldValue">
<div v-if="shouldDisplayAsHtml" @click.stop v-html="fieldValue"></div>
<span v-else class="whitespace-nowrap">{{ fieldValue }}</span>
</template>
<p v-else>&mdash;</p>
</div>
</template>
<script>
import { FieldValue } from '@/mixins'
export default {
mixins: [FieldValue],
props: ['resourceName', 'field'],
}
</script>

View File

@@ -0,0 +1,7 @@
<script>
import TextField from '@/fields/Index/TextField'
export default {
extends: TextField,
}
</script>

View File

@@ -0,0 +1,88 @@
<template>
<div v-if="hasData">
<div
ref="chart"
class="ct-chart"
:style="{ width: chartWidth, height: chartHeight }"
/>
</div>
</template>
<script>
import Chartist from 'chartist'
import 'chartist/dist/chartist.min.css'
// Default chart diameters.
const defaultHeight = 50
const defaultWidth = 100
export default {
props: ['resourceName', '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() {
return this.field.height || defaultHeight
},
/**
* Determine the chart width.
*/
chartWidth() {
return this.field.width || defaultWidth
},
},
}
</script>

View File

@@ -0,0 +1,32 @@
<template>
<div :class="`text-${field.textAlign}`">
<template v-if="hasValue">
<div class="leading-normal">
<component
:key="line.value"
v-for="line in field.lines"
class="whitespace-nowrap"
:is="`index-${line.component}`"
:field="line"
:resourceName="resourceName"
/>
</div>
</template>
<p v-else>&mdash;</p>
</div>
</template>
<script>
export default {
props: ['resourceName', 'field'],
computed: {
/**
* Determine if the field has a value other than null.
*/
hasValue() {
return this.field.lines
},
},
}
</script>

View File

@@ -0,0 +1,41 @@
<template>
<div class="flex items-center">
<Badge class="whitespace-nowrap flex items-center" :class="typeClasses">
<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>
</div>
</template>
<script>
import { FieldValue } from '@/mixins'
export default {
mixins: [FieldValue],
props: ['resourceName', 'field'],
computed: {
typeClasses() {
return [
this.field.textAlign === 'center' && 'mx-auto',
this.field.textAlign === 'right' && 'ml-auto mr-0',
this.field.textAlign === 'left' && 'ml-0 mr-auto',
this.field.typeClass,
]
},
},
}
</script>

View File

@@ -0,0 +1,43 @@
<template>
<div :class="`text-${field.textAlign}`">
<Dropdown v-if="field.value.length > 0">
<Button variant="link">
{{ __('View') }}
</Button>
<template #menu>
<DropdownMenu width="auto">
<div class="p-2">
<TagList
v-if="field.style === 'list'"
:tags="field.value"
:resource-name="field.resourceName"
:editable="false"
:with-preview="field.withPreview"
/>
<TagGroup
v-if="field.style === 'group'"
:tags="field.value"
:resource-name="field.resourceName"
:editable="false"
:with-preview="field.withPreview"
/>
</div>
</DropdownMenu>
</template>
</Dropdown>
<p v-else>&mdash;</p>
</div>
</template>
<script>
import { Button } from 'laravel-nova-ui'
export default {
components: {
Button,
},
props: ['index', 'resource', 'resourceName', 'resourceId', 'field'],
}
</script>

View File

@@ -0,0 +1,45 @@
<template>
<div :class="`text-${field.textAlign}`">
<template v-if="fieldValue">
<CopyButton
v-if="fieldHasValue && field.copyable && !shouldDisplayAsHtml"
@click.prevent.stop="copy"
v-tooltip="__('Copy to clipboard')"
>
<span ref="theFieldValue">
{{ fieldValue }}
</span>
</CopyButton>
<span
v-else-if="fieldHasValue && !field.copyable && !shouldDisplayAsHtml"
class="whitespace-nowrap"
>
{{ fieldValue }}
</span>
<div
@click.stop
v-else-if="fieldHasValue && !field.copyable && shouldDisplayAsHtml"
v-html="fieldValue"
/>
<p v-else>&mdash;</p>
</template>
<p v-else>&mdash;</p>
</div>
</template>
<script>
import { CopiesToClipboard, FieldValue } from '@/mixins'
export default {
mixins: [CopiesToClipboard, FieldValue],
props: ['resourceName', 'field'],
methods: {
copy() {
this.copyValueToClipboard(this.field.value)
},
},
}
</script>

View File

@@ -0,0 +1,29 @@
<template>
<div :class="`text-${field.textAlign}`">
<template v-if="fieldHasValue">
<div v-if="shouldDisplayAsHtml" @click.stop v-html="fieldValue"></div>
<span v-else class="whitespace-nowrap">
<a
class="link-default"
:href="field.value"
rel="noreferrer noopener"
target="_blank"
@click.stop
>
{{ fieldValue }}
</a>
</span>
</template>
<p v-else>&mdash;</p>
</div>
</template>
<script>
import { FieldValue } from '@/mixins'
export default {
mixins: [FieldValue],
props: ['resourceName', 'field'],
}
</script>

View File

@@ -0,0 +1,7 @@
<script>
import AudioField from '@/fields/Index/AudioField'
export default {
extends: AudioField,
}
</script>

View File

@@ -0,0 +1,7 @@
<script>
import FileField from '@/fields/Index/FileField'
export default {
extends: FileField,
}
</script>