Merge branch 'develop' into BUDI-7189/crud_row_from_views
This commit is contained in:
commit
60faa593a2
|
@ -201,25 +201,24 @@ spec:
|
||||||
|
|
||||||
image: budibase/apps:{{ .Values.globals.appVersion | default .Chart.AppVersion }}
|
image: budibase/apps:{{ .Values.globals.appVersion | default .Chart.AppVersion }}
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
|
{{- if .Values.services.apps.startupProbe }}
|
||||||
|
{{- with .Values.services.apps.startupProbe }}
|
||||||
|
startupProbe:
|
||||||
|
{{- toYaml . | nindent 10 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.services.apps.livenessProbe }}
|
||||||
|
{{- with .Values.services.apps.livenessProbe }}
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
{{- toYaml . | nindent 10 }}
|
||||||
path: /health
|
{{- end }}
|
||||||
port: {{ .Values.services.apps.port }}
|
{{- end }}
|
||||||
initialDelaySeconds: 10
|
{{- if .Values.services.apps.readinessProbe }}
|
||||||
periodSeconds: 5
|
{{- with .Values.services.apps.readinessProbe }}
|
||||||
successThreshold: 1
|
|
||||||
failureThreshold: 3
|
|
||||||
timeoutSeconds: 3
|
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
httpGet:
|
{{- toYaml . | nindent 10 }}
|
||||||
path: /health
|
{{- end }}
|
||||||
port: {{ .Values.services.apps.port }}
|
{{- end }}
|
||||||
initialDelaySeconds: 5
|
|
||||||
periodSeconds: 5
|
|
||||||
successThreshold: 1
|
|
||||||
failureThreshold: 3
|
|
||||||
timeoutSeconds: 3
|
|
||||||
|
|
||||||
name: bbapps
|
name: bbapps
|
||||||
ports:
|
ports:
|
||||||
- containerPort: {{ .Values.services.apps.port }}
|
- containerPort: {{ .Values.services.apps.port }}
|
||||||
|
|
|
@ -40,24 +40,24 @@ spec:
|
||||||
- image: budibase/proxy:{{ .Values.globals.appVersion | default .Chart.AppVersion }}
|
- image: budibase/proxy:{{ .Values.globals.appVersion | default .Chart.AppVersion }}
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
name: proxy-service
|
name: proxy-service
|
||||||
|
{{- if .Values.services.proxy.startupProbe }}
|
||||||
|
{{- with .Values.services.proxy.startupProbe }}
|
||||||
|
startupProbe:
|
||||||
|
{{- toYaml . | nindent 10 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.services.proxy.livenessProbe }}
|
||||||
|
{{- with .Values.services.proxy.livenessProbe }}
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
{{- toYaml . | nindent 10 }}
|
||||||
path: /health
|
{{- end }}
|
||||||
port: {{ .Values.services.proxy.port }}
|
{{- end }}
|
||||||
initialDelaySeconds: 0
|
{{- if .Values.services.proxy.readinessProbe }}
|
||||||
periodSeconds: 5
|
{{- with .Values.services.proxy.readinessProbe }}
|
||||||
successThreshold: 1
|
|
||||||
failureThreshold: 2
|
|
||||||
timeoutSeconds: 3
|
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
httpGet:
|
{{- toYaml . | nindent 10 }}
|
||||||
path: /health
|
{{- end }}
|
||||||
port: {{ .Values.services.proxy.port }}
|
{{- end }}
|
||||||
initialDelaySeconds: 0
|
|
||||||
periodSeconds: 5
|
|
||||||
successThreshold: 1
|
|
||||||
failureThreshold: 2
|
|
||||||
timeoutSeconds: 3
|
|
||||||
ports:
|
ports:
|
||||||
- containerPort: {{ .Values.services.proxy.port }}
|
- containerPort: {{ .Values.services.proxy.port }}
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -190,24 +190,24 @@ spec:
|
||||||
{{ end }}
|
{{ end }}
|
||||||
image: budibase/worker:{{ .Values.globals.appVersion | default .Chart.AppVersion }}
|
image: budibase/worker:{{ .Values.globals.appVersion | default .Chart.AppVersion }}
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
|
{{- if .Values.services.worker.startupProbe }}
|
||||||
|
{{- with .Values.services.worker.startupProbe }}
|
||||||
|
startupProbe:
|
||||||
|
{{- toYaml . | nindent 10 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.services.worker.livenessProbe }}
|
||||||
|
{{- with .Values.services.worker.livenessProbe }}
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
{{- toYaml . | nindent 10 }}
|
||||||
path: /health
|
{{- end }}
|
||||||
port: {{ .Values.services.worker.port }}
|
{{- end }}
|
||||||
initialDelaySeconds: 10
|
{{- if .Values.services.worker.readinessProbe }}
|
||||||
periodSeconds: 5
|
{{- with .Values.services.worker.readinessProbe }}
|
||||||
successThreshold: 1
|
|
||||||
failureThreshold: 3
|
|
||||||
timeoutSeconds: 3
|
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
httpGet:
|
{{- toYaml . | nindent 10 }}
|
||||||
path: /health
|
{{- end }}
|
||||||
port: {{ .Values.services.worker.port }}
|
{{- end }}
|
||||||
initialDelaySeconds: 5
|
|
||||||
periodSeconds: 5
|
|
||||||
successThreshold: 1
|
|
||||||
failureThreshold: 3
|
|
||||||
timeoutSeconds: 3
|
|
||||||
name: bbworker
|
name: bbworker
|
||||||
ports:
|
ports:
|
||||||
- containerPort: {{ .Values.services.worker.port }}
|
- containerPort: {{ .Values.services.worker.port }}
|
||||||
|
|
|
@ -119,15 +119,37 @@ services:
|
||||||
port: 10000
|
port: 10000
|
||||||
replicaCount: 1
|
replicaCount: 1
|
||||||
upstreams:
|
upstreams:
|
||||||
apps: 'http://app-service.{{ .Release.Namespace }}.svc.{{ .Values.services.dns }}:{{ .Values.services.apps.port }}'
|
apps: "http://app-service.{{ .Release.Namespace }}.svc.{{ .Values.services.dns }}:{{ .Values.services.apps.port }}"
|
||||||
worker: 'http://worker-service.{{ .Release.Namespace }}.svc.{{ .Values.services.dns }}:{{ .Values.services.worker.port }}'
|
worker: "http://worker-service.{{ .Release.Namespace }}.svc.{{ .Values.services.dns }}:{{ .Values.services.worker.port }}"
|
||||||
minio: 'http://minio-service.{{ .Release.Namespace }}.svc.{{ .Values.services.dns }}:{{ .Values.services.objectStore.port }}'
|
minio: "http://minio-service.{{ .Release.Namespace }}.svc.{{ .Values.services.dns }}:{{ .Values.services.objectStore.port }}"
|
||||||
couchdb: 'http://{{ .Release.Name }}-svc-couchdb:{{ .Values.services.couchdb.port }}'
|
couchdb: "http://{{ .Release.Name }}-svc-couchdb:{{ .Values.services.couchdb.port }}"
|
||||||
resources: {}
|
resources: {}
|
||||||
# annotations:
|
startupProbe:
|
||||||
# co.elastic.logs/module: nginx
|
httpGet:
|
||||||
# co.elastic.logs/fileset.stdout: access
|
path: /health
|
||||||
# co.elastic.logs/fileset.stderr: error
|
port: 10000
|
||||||
|
scheme: HTTP
|
||||||
|
failureThreshold: 30
|
||||||
|
periodSeconds: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 10000
|
||||||
|
scheme: HTTP
|
||||||
|
enabled: true
|
||||||
|
periodSeconds: 3
|
||||||
|
failureThreshold: 1
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 10000
|
||||||
|
scheme: HTTP
|
||||||
|
failureThreshold: 3
|
||||||
|
periodSeconds: 5
|
||||||
|
# annotations:
|
||||||
|
# co.elastic.logs/module: nginx
|
||||||
|
# co.elastic.logs/fileset.stdout: access
|
||||||
|
# co.elastic.logs/fileset.stderr: error
|
||||||
|
|
||||||
apps:
|
apps:
|
||||||
port: 4002
|
port: 4002
|
||||||
|
@ -135,23 +157,67 @@ services:
|
||||||
logLevel: info
|
logLevel: info
|
||||||
httpLogging: 1
|
httpLogging: 1
|
||||||
resources: {}
|
resources: {}
|
||||||
# nodeDebug: "" # set the value of NODE_DEBUG
|
startupProbe:
|
||||||
# annotations:
|
httpGet:
|
||||||
# co.elastic.logs/multiline.type: pattern
|
path: /health
|
||||||
# co.elastic.logs/multiline.pattern: '^[[:space:]]'
|
port: 4002
|
||||||
# co.elastic.logs/multiline.negate: false
|
scheme: HTTP
|
||||||
# co.elastic.logs/multiline.match: after
|
failureThreshold: 30
|
||||||
|
periodSeconds: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 4002
|
||||||
|
scheme: HTTP
|
||||||
|
enabled: true
|
||||||
|
periodSeconds: 3
|
||||||
|
failureThreshold: 1
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 4002
|
||||||
|
scheme: HTTP
|
||||||
|
failureThreshold: 3
|
||||||
|
periodSeconds: 5
|
||||||
|
# nodeDebug: "" # set the value of NODE_DEBUG
|
||||||
|
# annotations:
|
||||||
|
# co.elastic.logs/multiline.type: pattern
|
||||||
|
# co.elastic.logs/multiline.pattern: '^[[:space:]]'
|
||||||
|
# co.elastic.logs/multiline.negate: false
|
||||||
|
# co.elastic.logs/multiline.match: after
|
||||||
worker:
|
worker:
|
||||||
port: 4003
|
port: 4003
|
||||||
replicaCount: 1
|
replicaCount: 1
|
||||||
logLevel: info
|
logLevel: info
|
||||||
httpLogging: 1
|
httpLogging: 1
|
||||||
resources: {}
|
resources: {}
|
||||||
# annotations:
|
startupProbe:
|
||||||
# co.elastic.logs/multiline.type: pattern
|
httpGet:
|
||||||
# co.elastic.logs/multiline.pattern: '^[[:space:]]'
|
path: /health
|
||||||
# co.elastic.logs/multiline.negate: false
|
port: 4003
|
||||||
# co.elastic.logs/multiline.match: after
|
scheme: HTTP
|
||||||
|
failureThreshold: 30
|
||||||
|
periodSeconds: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 4003
|
||||||
|
scheme: HTTP
|
||||||
|
enabled: true
|
||||||
|
periodSeconds: 3
|
||||||
|
failureThreshold: 1
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 4003
|
||||||
|
scheme: HTTP
|
||||||
|
failureThreshold: 3
|
||||||
|
periodSeconds: 5
|
||||||
|
# annotations:
|
||||||
|
# co.elastic.logs/multiline.type: pattern
|
||||||
|
# co.elastic.logs/multiline.pattern: '^[[:space:]]'
|
||||||
|
# co.elastic.logs/multiline.negate: false
|
||||||
|
# co.elastic.logs/multiline.match: after
|
||||||
|
|
||||||
couchdb:
|
couchdb:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.8.29-alpha.3",
|
"version": "2.8.29-alpha.7",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -85,7 +85,8 @@
|
||||||
"dayjs": "^1.10.4",
|
"dayjs": "^1.10.4",
|
||||||
"easymde": "^2.16.1",
|
"easymde": "^2.16.1",
|
||||||
"svelte-flatpickr": "3.2.3",
|
"svelte-flatpickr": "3.2.3",
|
||||||
"svelte-portal": "^1.0.0"
|
"svelte-portal": "^1.0.0",
|
||||||
|
"svelte-dnd-action": "^0.9.8"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"loader-utils": "1.4.1"
|
"loader-utils": "1.4.1"
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<script>
|
<script>
|
||||||
//import { createEventDispatcher } from "svelte"
|
|
||||||
import "@spectrum-css/popover/dist/index-vars.css"
|
import "@spectrum-css/popover/dist/index-vars.css"
|
||||||
import clickOutside from "../Actions/click_outside"
|
import clickOutside from "../Actions/click_outside"
|
||||||
import { fly } from "svelte/transition"
|
import { fly } from "svelte/transition"
|
||||||
|
|
|
@ -0,0 +1,252 @@
|
||||||
|
<script>
|
||||||
|
import { flip } from "svelte/animate"
|
||||||
|
import { dndzone } from "svelte-dnd-action"
|
||||||
|
import Icon from "../Icon/Icon.svelte"
|
||||||
|
import Popover from "../Popover/Popover.svelte"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
const flipDurationMs = 150
|
||||||
|
|
||||||
|
export let constraints
|
||||||
|
export let optionColors = {}
|
||||||
|
let options = []
|
||||||
|
|
||||||
|
let colorPopovers = []
|
||||||
|
let anchors = []
|
||||||
|
|
||||||
|
let colorsArray = [
|
||||||
|
"hsla(0, 90%, 75%, 0.3)",
|
||||||
|
"hsla(50, 80%, 75%, 0.3)",
|
||||||
|
"hsla(120, 90%, 75%, 0.3)",
|
||||||
|
"hsla(200, 90%, 75%, 0.3)",
|
||||||
|
"hsla(240, 90%, 75%, 0.3)",
|
||||||
|
"hsla(320, 90%, 75%, 0.3)",
|
||||||
|
]
|
||||||
|
$: {
|
||||||
|
if (constraints.inclusion.length) {
|
||||||
|
options = constraints.inclusion.map(value => ({
|
||||||
|
name: value,
|
||||||
|
id: Math.random(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const removeInput = idx => {
|
||||||
|
delete optionColors[options[idx].name]
|
||||||
|
constraints.inclusion = constraints.inclusion.filter((e, i) => i !== idx)
|
||||||
|
options = options.filter((e, i) => i !== idx)
|
||||||
|
colorPopovers.pop(undefined)
|
||||||
|
anchors.pop(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
const addNewInput = () => {
|
||||||
|
options = [
|
||||||
|
...options,
|
||||||
|
{ name: `Option ${constraints.inclusion.length + 1}`, id: Math.random() },
|
||||||
|
]
|
||||||
|
constraints.inclusion = [
|
||||||
|
...constraints.inclusion,
|
||||||
|
`Option ${constraints.inclusion.length + 1}`,
|
||||||
|
]
|
||||||
|
|
||||||
|
colorPopovers.push(undefined)
|
||||||
|
anchors.push(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDndConsider = e => {
|
||||||
|
options = e.detail.items
|
||||||
|
}
|
||||||
|
const handleDndFinalize = e => {
|
||||||
|
options = e.detail.items
|
||||||
|
constraints.inclusion = options.map(option => option.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleColorChange = (optionName, color, idx) => {
|
||||||
|
optionColors[optionName] = color
|
||||||
|
colorPopovers[idx].hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleNameChange = (optionName, idx, value) => {
|
||||||
|
constraints.inclusion[idx] = value
|
||||||
|
options[idx].name = value
|
||||||
|
optionColors[value] = optionColors[optionName]
|
||||||
|
delete optionColors[optionName]
|
||||||
|
}
|
||||||
|
|
||||||
|
const openColorPickerPopover = (optionIdx, target) => {
|
||||||
|
colorPopovers[optionIdx].show()
|
||||||
|
anchors[optionIdx] = target
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
// Initialize anchor arrays on mount, assuming 'options' is already populated
|
||||||
|
colorPopovers = constraints.inclusion.map(() => undefined)
|
||||||
|
anchors = constraints.inclusion.map(() => undefined)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="actions"
|
||||||
|
use:dndzone={{
|
||||||
|
items: options,
|
||||||
|
flipDurationMs,
|
||||||
|
dropTargetStyle: { outline: "none" },
|
||||||
|
}}
|
||||||
|
on:consider={handleDndConsider}
|
||||||
|
on:finalize={handleDndFinalize}
|
||||||
|
>
|
||||||
|
{#each options as option, idx (option.id)}
|
||||||
|
<div
|
||||||
|
class="no-border action-container"
|
||||||
|
animate:flip={{ duration: flipDurationMs }}
|
||||||
|
>
|
||||||
|
<div class="child drag-handle-spacing">
|
||||||
|
<Icon name="DragHandle" size="L" />
|
||||||
|
</div>
|
||||||
|
<div class="child color-picker">
|
||||||
|
<div
|
||||||
|
id="color-picker"
|
||||||
|
bind:this={anchors[idx]}
|
||||||
|
style="--color:{optionColors?.[option.name] ||
|
||||||
|
'hsla(0, 1%, 50%, 0.3)'}"
|
||||||
|
class="circle"
|
||||||
|
on:click={e => openColorPickerPopover(idx, e.target)}
|
||||||
|
>
|
||||||
|
<Popover
|
||||||
|
bind:this={colorPopovers[idx]}
|
||||||
|
anchor={anchors[idx]}
|
||||||
|
align="left"
|
||||||
|
offset={0}
|
||||||
|
style=""
|
||||||
|
popoverTarget={document.getElementById(`color-picker`)}
|
||||||
|
animate={false}
|
||||||
|
>
|
||||||
|
<div class="colors">
|
||||||
|
{#each colorsArray as color}
|
||||||
|
<div
|
||||||
|
on:click={() => handleColorChange(option.name, color, idx)}
|
||||||
|
style="--color:{color};"
|
||||||
|
class="circle circle-hover"
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="child">
|
||||||
|
<input
|
||||||
|
class="input-field"
|
||||||
|
type="text"
|
||||||
|
on:change={e => handleNameChange(option.name, idx, e.target.value)}
|
||||||
|
value={option.name}
|
||||||
|
placeholder="Option name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="child">
|
||||||
|
<Icon name="Close" hoverable size="S" on:click={removeInput(idx)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div on:click={addNewInput} class="add-option">
|
||||||
|
<Icon hoverable name="Add" />
|
||||||
|
<div>Add option</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.action-container {
|
||||||
|
background-color: var(--spectrum-alias-background-color-primary);
|
||||||
|
border-radius: 0px;
|
||||||
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
|
transition: background-color 130ms ease-in-out, color 130ms ease-in-out,
|
||||||
|
border-color 130ms ease-in-out;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.no-border {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-container:last-child {
|
||||||
|
border-bottom: 1px solid var(--spectrum-global-color-gray-300) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.child {
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
.child:hover,
|
||||||
|
.child:focus {
|
||||||
|
background: var(--spectrum-global-color-gray-200);
|
||||||
|
}
|
||||||
|
.add-option {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--spacing-m);
|
||||||
|
gap: var(--spacing-m);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-field {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background-color: transparent;
|
||||||
|
width: 100%;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.child input[type="text"] {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-field:hover,
|
||||||
|
.input-field:focus {
|
||||||
|
background: var(--spectrum-global-color-gray-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-container > :nth-child(1) {
|
||||||
|
flex-grow: 1;
|
||||||
|
justify-content: center;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-container > :nth-child(2) {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-container > :nth-child(3) {
|
||||||
|
flex-grow: 4;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.action-container > :nth-child(4) {
|
||||||
|
flex-grow: 1;
|
||||||
|
justify-content: center;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
background-color: var(--color);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-hover:hover {
|
||||||
|
border: 1px solid var(--spectrum-global-color-blue-400);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colors {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
justify-items: center;
|
||||||
|
margin: var(--spacing-m);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -21,6 +21,7 @@
|
||||||
export let offset = 5
|
export let offset = 5
|
||||||
export let customHeight
|
export let customHeight
|
||||||
export let animate = true
|
export let animate = true
|
||||||
|
export let customZindex
|
||||||
|
|
||||||
$: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum"
|
$: target = portalTarget || getContext(Context.PopoverRoot) || ".spectrum"
|
||||||
|
|
||||||
|
@ -77,8 +78,9 @@
|
||||||
}}
|
}}
|
||||||
on:keydown={handleEscape}
|
on:keydown={handleEscape}
|
||||||
class="spectrum-Popover is-open"
|
class="spectrum-Popover is-open"
|
||||||
|
class:customZindex
|
||||||
role="presentation"
|
role="presentation"
|
||||||
style="height: {customHeight}"
|
style="height: {customHeight}; --customZindex: {customZindex};"
|
||||||
transition:fly|local={{ y: -20, duration: animate ? 200 : 0 }}
|
transition:fly|local={{ y: -20, duration: animate ? 200 : 0 }}
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
|
@ -92,4 +94,8 @@
|
||||||
border-color: var(--spectrum-global-color-gray-300);
|
border-color: var(--spectrum-global-color-gray-300);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.customZindex {
|
||||||
|
z-index: var(--customZindex) !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -84,7 +84,7 @@ export { default as IconSideNavItem } from "./IconSideNav/IconSideNavItem.svelte
|
||||||
export { default as Slider } from "./Form/Slider.svelte"
|
export { default as Slider } from "./Form/Slider.svelte"
|
||||||
export { default as Accordion } from "./Accordion/Accordion.svelte"
|
export { default as Accordion } from "./Accordion/Accordion.svelte"
|
||||||
export { default as File } from "./Form/File.svelte"
|
export { default as File } from "./Form/File.svelte"
|
||||||
|
export { default as OptionSelectDnD } from "./OptionSelectDnD/OptionSelectDnD.svelte"
|
||||||
// Renderers
|
// Renderers
|
||||||
export { default as BoldRenderer } from "./Table/BoldRenderer.svelte"
|
export { default as BoldRenderer } from "./Table/BoldRenderer.svelte"
|
||||||
export { default as CodeRenderer } from "./Table/CodeRenderer.svelte"
|
export { default as CodeRenderer } from "./Table/CodeRenderer.svelte"
|
||||||
|
|
|
@ -64,6 +64,13 @@
|
||||||
<svelte:fragment slot="filter">
|
<svelte:fragment slot="filter">
|
||||||
<GridFilterButton />
|
<GridFilterButton />
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
<svelte:fragment slot="edit-column">
|
||||||
|
<GridEditColumnModal />
|
||||||
|
</svelte:fragment>
|
||||||
|
<svelte:fragment slot="add-column">
|
||||||
|
<GridAddColumnModal />
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
<svelte:fragment slot="controls">
|
<svelte:fragment slot="controls">
|
||||||
{#if isInternal}
|
{#if isInternal}
|
||||||
<GridCreateViewButton />
|
<GridCreateViewButton />
|
||||||
|
@ -77,9 +84,8 @@
|
||||||
{:else}
|
{:else}
|
||||||
<GridImportButton />
|
<GridImportButton />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<GridExportButton />
|
<GridExportButton />
|
||||||
<GridAddColumnModal />
|
|
||||||
<GridEditColumnModal />
|
|
||||||
{#if isUsersTable}
|
{#if isUsersTable}
|
||||||
<GridEditUserModal />
|
<GridEditUserModal />
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
@ -7,12 +7,12 @@
|
||||||
Toggle,
|
Toggle,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
DatePicker,
|
DatePicker,
|
||||||
ModalContent,
|
|
||||||
Context,
|
|
||||||
Modal,
|
Modal,
|
||||||
notifications,
|
notifications,
|
||||||
|
OptionSelectDnD,
|
||||||
|
Layout,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher, getContext } from "svelte"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
import { tables, datasources } from "stores/backend"
|
import { tables, datasources } from "stores/backend"
|
||||||
import { TableNames, UNEDITABLE_USER_FIELDS } from "constants"
|
import { TableNames, UNEDITABLE_USER_FIELDS } from "constants"
|
||||||
|
@ -26,12 +26,10 @@
|
||||||
SWITCHABLE_TYPES,
|
SWITCHABLE_TYPES,
|
||||||
} from "constants/backend"
|
} from "constants/backend"
|
||||||
import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils"
|
import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils"
|
||||||
import ValuesList from "components/common/ValuesList.svelte"
|
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import { truncate } from "lodash"
|
import { truncate } from "lodash"
|
||||||
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
|
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
|
||||||
import { getBindings } from "components/backend/DataTable/formula"
|
import { getBindings } from "components/backend/DataTable/formula"
|
||||||
import { getContext } from "svelte"
|
|
||||||
import JSONSchemaModal from "./JSONSchemaModal.svelte"
|
import JSONSchemaModal from "./JSONSchemaModal.svelte"
|
||||||
import { ValidColumnNameRegex } from "@budibase/shared-core"
|
import { ValidColumnNameRegex } from "@budibase/shared-core"
|
||||||
|
|
||||||
|
@ -45,11 +43,11 @@
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"]
|
const PROHIBITED_COLUMN_NAMES = ["type", "_id", "_rev", "tableId"]
|
||||||
const { hide } = getContext(Context.Modal)
|
const { dispatch: gridDispatch } = getContext("grid")
|
||||||
let fieldDefinitions = cloneDeep(FIELDS)
|
|
||||||
|
|
||||||
export let field
|
export let field
|
||||||
|
|
||||||
|
let fieldDefinitions = cloneDeep(FIELDS)
|
||||||
let originalName
|
let originalName
|
||||||
let linkEditDisabled
|
let linkEditDisabled
|
||||||
let primaryDisplay
|
let primaryDisplay
|
||||||
|
@ -61,11 +59,10 @@
|
||||||
let savingColumn
|
let savingColumn
|
||||||
let deleteColName
|
let deleteColName
|
||||||
let jsonSchemaModal
|
let jsonSchemaModal
|
||||||
|
let allowedTypes = []
|
||||||
let editableColumn = {
|
let editableColumn = {
|
||||||
type: "string",
|
type: "string",
|
||||||
constraints: fieldDefinitions.STRING.constraints,
|
constraints: fieldDefinitions.STRING.constraints,
|
||||||
|
|
||||||
// Initial value for column name in other table for linked records
|
// Initial value for column name in other table for linked records
|
||||||
fieldName: $tables.selected.name,
|
fieldName: $tables.selected.name,
|
||||||
}
|
}
|
||||||
|
@ -83,7 +80,23 @@
|
||||||
primaryDisplay =
|
primaryDisplay =
|
||||||
$tables.selected.primaryDisplay == null ||
|
$tables.selected.primaryDisplay == null ||
|
||||||
$tables.selected.primaryDisplay === editableColumn.name
|
$tables.selected.primaryDisplay === editableColumn.name
|
||||||
|
} else if (!savingColumn) {
|
||||||
|
let highestNumber = 0
|
||||||
|
Object.keys(table.schema).forEach(columnName => {
|
||||||
|
const columnNumber = extractColumnNumber(columnName)
|
||||||
|
if (columnNumber > highestNumber) {
|
||||||
|
highestNumber = columnNumber
|
||||||
|
}
|
||||||
|
return highestNumber
|
||||||
|
})
|
||||||
|
|
||||||
|
if (highestNumber >= 1) {
|
||||||
|
editableColumn.name = `Column 0${highestNumber + 1}`
|
||||||
|
} else {
|
||||||
|
editableColumn.name = "Column 01"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
allowedTypes = getAllowedTypes()
|
||||||
}
|
}
|
||||||
|
|
||||||
$: initialiseField(field, savingColumn)
|
$: initialiseField(field, savingColumn)
|
||||||
|
@ -182,6 +195,8 @@
|
||||||
indexes,
|
indexes,
|
||||||
})
|
})
|
||||||
dispatch("updatecolumns")
|
dispatch("updatecolumns")
|
||||||
|
gridDispatch("close-edit-column")
|
||||||
|
|
||||||
if (
|
if (
|
||||||
saveColumn.type === LINK_TYPE &&
|
saveColumn.type === LINK_TYPE &&
|
||||||
saveColumn.relationshipType === RelationshipType.MANY_TO_MANY
|
saveColumn.relationshipType === RelationshipType.MANY_TO_MANY
|
||||||
|
@ -203,6 +218,7 @@
|
||||||
|
|
||||||
function cancelEdit() {
|
function cancelEdit() {
|
||||||
editableColumn.name = originalName
|
editableColumn.name = originalName
|
||||||
|
gridDispatch("close-edit-column")
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteColumn() {
|
async function deleteColumn() {
|
||||||
|
@ -214,8 +230,8 @@
|
||||||
await tables.deleteField(editableColumn)
|
await tables.deleteField(editableColumn)
|
||||||
notifications.success(`Column ${editableColumn.name} deleted`)
|
notifications.success(`Column ${editableColumn.name} deleted`)
|
||||||
confirmDeleteDialog.hide()
|
confirmDeleteDialog.hide()
|
||||||
hide()
|
|
||||||
dispatch("updatecolumns")
|
dispatch("updatecolumns")
|
||||||
|
gridDispatch("close-edit-column")
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error(`Error deleting column: ${error.message}`)
|
notifications.error(`Error deleting column: ${error.message}`)
|
||||||
|
@ -251,14 +267,6 @@
|
||||||
required = req
|
required = req
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChangePrimaryDisplay(e) {
|
|
||||||
const isPrimary = e.detail
|
|
||||||
// primary display is always required
|
|
||||||
if (isPrimary) {
|
|
||||||
editableColumn.constraints.presence = { allowEmpty: false }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function openJsonSchemaEditor() {
|
function openJsonSchemaEditor() {
|
||||||
jsonSchemaModal.show()
|
jsonSchemaModal.show()
|
||||||
}
|
}
|
||||||
|
@ -272,6 +280,11 @@
|
||||||
deleteColName = ""
|
deleteColName = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractColumnNumber(columnName) {
|
||||||
|
const match = columnName.match(/Column (\d+)/)
|
||||||
|
return match ? parseInt(match[1]) : 0
|
||||||
|
}
|
||||||
|
|
||||||
function getRelationshipOptions(field) {
|
function getRelationshipOptions(field) {
|
||||||
if (!field || !field.tableId) {
|
if (!field || !field.tableId) {
|
||||||
return null
|
return null
|
||||||
|
@ -402,15 +415,8 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ModalContent
|
<Layout noPadding gap="S">
|
||||||
title={originalName ? "Edit Column" : "Create Column"}
|
|
||||||
confirmText="Save Column"
|
|
||||||
onConfirm={saveColumn}
|
|
||||||
onCancel={cancelEdit}
|
|
||||||
disabled={invalid}
|
|
||||||
>
|
|
||||||
<Input
|
<Input
|
||||||
label="Name"
|
|
||||||
bind:value={editableColumn.name}
|
bind:value={editableColumn.name}
|
||||||
disabled={uneditable ||
|
disabled={uneditable ||
|
||||||
(linkEditDisabled && editableColumn.type === LINK_TYPE)}
|
(linkEditDisabled && editableColumn.type === LINK_TYPE)}
|
||||||
|
@ -419,12 +425,12 @@
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
disabled={!typeEnabled}
|
disabled={!typeEnabled}
|
||||||
label="Type"
|
|
||||||
bind:value={editableColumn.type}
|
bind:value={editableColumn.type}
|
||||||
on:change={handleTypeChange}
|
on:change={handleTypeChange}
|
||||||
options={getAllowedTypes()}
|
options={allowedTypes}
|
||||||
getOptionLabel={field => field.name}
|
getOptionLabel={field => field.name}
|
||||||
getOptionValue={field => field.type}
|
getOptionValue={field => field.type}
|
||||||
|
getOptionIcon={field => field.icon}
|
||||||
isOptionEnabled={option => {
|
isOptionEnabled={option => {
|
||||||
if (option.type == AUTO_TYPE) {
|
if (option.type == AUTO_TYPE) {
|
||||||
return availableAutoColumnKeys?.length > 0
|
return availableAutoColumnKeys?.length > 0
|
||||||
|
@ -433,28 +439,6 @@
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{#if canBeRequired || canBeDisplay}
|
|
||||||
<div>
|
|
||||||
{#if canBeRequired}
|
|
||||||
<Toggle
|
|
||||||
value={required}
|
|
||||||
on:change={onChangeRequired}
|
|
||||||
disabled={primaryDisplay}
|
|
||||||
thin
|
|
||||||
text="Required"
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{#if canBeDisplay}
|
|
||||||
<Toggle
|
|
||||||
bind:value={primaryDisplay}
|
|
||||||
on:change={onChangePrimaryDisplay}
|
|
||||||
thin
|
|
||||||
text="Use as table display column"
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if editableColumn.type === "string"}
|
{#if editableColumn.type === "string"}
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
|
@ -462,9 +446,9 @@
|
||||||
bind:value={editableColumn.constraints.length.maximum}
|
bind:value={editableColumn.constraints.length.maximum}
|
||||||
/>
|
/>
|
||||||
{:else if editableColumn.type === "options"}
|
{:else if editableColumn.type === "options"}
|
||||||
<ValuesList
|
<OptionSelectDnD
|
||||||
label="Options (one per line)"
|
bind:constraints={editableColumn.constraints}
|
||||||
bind:values={editableColumn.constraints.inclusion}
|
bind:optionColors={editableColumn.optionColors}
|
||||||
/>
|
/>
|
||||||
{:else if editableColumn.type === "longform"}
|
{:else if editableColumn.type === "longform"}
|
||||||
<div>
|
<div>
|
||||||
|
@ -480,19 +464,28 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{:else if editableColumn.type === "array"}
|
{:else if editableColumn.type === "array"}
|
||||||
<ValuesList
|
<OptionSelectDnD
|
||||||
label="Options (one per line)"
|
bind:constraints={editableColumn.constraints}
|
||||||
bind:values={editableColumn.constraints.inclusion}
|
bind:optionColors={editableColumn.optionColors}
|
||||||
/>
|
/>
|
||||||
{:else if editableColumn.type === "datetime" && !editableColumn.autocolumn}
|
{:else if editableColumn.type === "datetime" && !editableColumn.autocolumn}
|
||||||
<DatePicker
|
<div class="split-label">
|
||||||
label="Earliest"
|
<div class="label-length">
|
||||||
bind:value={editableColumn.constraints.datetime.earliest}
|
<Label size="M">Earliest</Label>
|
||||||
/>
|
</div>
|
||||||
<DatePicker
|
<div class="input-length">
|
||||||
label="Latest"
|
<DatePicker bind:value={editableColumn.constraints.datetime.earliest} />
|
||||||
bind:value={editableColumn.constraints.datetime.latest}
|
</div>
|
||||||
/>
|
</div>
|
||||||
|
|
||||||
|
<div class="split-label">
|
||||||
|
<div class="label-length">
|
||||||
|
<Label size="M">Latest</Label>
|
||||||
|
</div>
|
||||||
|
<div class="input-length">
|
||||||
|
<DatePicker bind:value={editableColumn.constraints.datetime.latest} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{#if datasource?.source !== "ORACLE" && datasource?.source !== "SQL_SERVER"}
|
{#if datasource?.source !== "ORACLE" && datasource?.source !== "SQL_SERVER"}
|
||||||
<div>
|
<div>
|
||||||
<Label
|
<Label
|
||||||
|
@ -509,16 +502,30 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{:else if editableColumn.type === "number" && !editableColumn.autocolumn}
|
{:else if editableColumn.type === "number" && !editableColumn.autocolumn}
|
||||||
<Input
|
<div class="split-label">
|
||||||
type="number"
|
<div class="label-length">
|
||||||
label="Min Value"
|
<Label size="M">Max Value</Label>
|
||||||
bind:value={editableColumn.constraints.numericality.greaterThanOrEqualTo}
|
</div>
|
||||||
/>
|
<div class="input-length">
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
label="Max Value"
|
bind:value={editableColumn.constraints.numericality
|
||||||
bind:value={editableColumn.constraints.numericality.lessThanOrEqualTo}
|
.greaterThanOrEqualTo}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="split-label">
|
||||||
|
<div class="label-length">
|
||||||
|
<Label size="M">Max Value</Label>
|
||||||
|
</div>
|
||||||
|
<div class="input-length">
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
bind:value={editableColumn.constraints.numericality.lessThanOrEqualTo}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{:else if editableColumn.type === "link"}
|
{:else if editableColumn.type === "link"}
|
||||||
<Select
|
<Select
|
||||||
label="Table"
|
label="Table"
|
||||||
|
@ -547,32 +554,44 @@
|
||||||
/>
|
/>
|
||||||
{:else if editableColumn.type === FORMULA_TYPE}
|
{:else if editableColumn.type === FORMULA_TYPE}
|
||||||
{#if !table.sql}
|
{#if !table.sql}
|
||||||
<Select
|
<div class="split-label">
|
||||||
label="Formula type"
|
<div class="label-length">
|
||||||
bind:value={editableColumn.formulaType}
|
<Label size="M">Formula Type</Label>
|
||||||
options={[
|
</div>
|
||||||
{ label: "Dynamic", value: "dynamic" },
|
<div class="input-length">
|
||||||
{ label: "Static", value: "static" },
|
<Select
|
||||||
]}
|
bind:value={editableColumn.formulaType}
|
||||||
getOptionLabel={option => option.label}
|
options={[
|
||||||
getOptionValue={option => option.value}
|
{ label: "Dynamic", value: "dynamic" },
|
||||||
tooltip="Dynamic formula are calculated when retrieved, but cannot be filtered or sorted by,
|
{ label: "Static", value: "static" },
|
||||||
|
]}
|
||||||
|
getOptionLabel={option => option.label}
|
||||||
|
getOptionValue={option => option.value}
|
||||||
|
tooltip="Dynamic formula are calculated when retrieved, but cannot be filtered or sorted by,
|
||||||
while static formula are calculated when the row is saved."
|
while static formula are calculated when the row is saved."
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<ModalBindableInput
|
<div class="split-label">
|
||||||
title="Formula"
|
<div class="label-length">
|
||||||
label="Formula"
|
<Label size="M">Formula</Label>
|
||||||
value={editableColumn.formula}
|
</div>
|
||||||
on:change={e => {
|
<div class="input-length">
|
||||||
editableColumn = {
|
<ModalBindableInput
|
||||||
...editableColumn,
|
title="Formula"
|
||||||
formula: e.detail,
|
value={editableColumn.formula}
|
||||||
}
|
on:change={e => {
|
||||||
}}
|
editableColumn = {
|
||||||
bindings={getBindings({ table })}
|
...editableColumn,
|
||||||
allowJS
|
formula: e.detail,
|
||||||
/>
|
}
|
||||||
|
}}
|
||||||
|
bindings={getBindings({ table })}
|
||||||
|
allowJS
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{:else if editableColumn.type === JSON_TYPE}
|
{:else if editableColumn.type === JSON_TYPE}
|
||||||
<Button primary text on:click={openJsonSchemaEditor}
|
<Button primary text on:click={openJsonSchemaEditor}
|
||||||
>Open schema editor</Button
|
>Open schema editor</Button
|
||||||
|
@ -591,12 +610,28 @@
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div slot="footer">
|
{#if canBeRequired || canBeDisplay}
|
||||||
{#if !uneditable && originalName != null}
|
<div>
|
||||||
<Button warning text on:click={confirmDelete}>Delete</Button>
|
{#if canBeRequired}
|
||||||
{/if}
|
<Toggle
|
||||||
</div>
|
value={required}
|
||||||
</ModalContent>
|
on:change={onChangeRequired}
|
||||||
|
disabled={primaryDisplay}
|
||||||
|
thin
|
||||||
|
text="Required"
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
|
{#if !uneditable && originalName != null}
|
||||||
|
<Button quiet warning text on:click={confirmDelete}>Delete</Button>
|
||||||
|
{/if}
|
||||||
|
<Button secondary newStyles on:click={cancelEdit}>Cancel</Button>
|
||||||
|
<Button disabled={invalid} newStyles cta on:click={saveColumn}>Save</Button>
|
||||||
|
</div>
|
||||||
<Modal bind:this={jsonSchemaModal}>
|
<Modal bind:this={jsonSchemaModal}>
|
||||||
<JSONSchemaModal
|
<JSONSchemaModal
|
||||||
schema={editableColumn.schema}
|
schema={editableColumn.schema}
|
||||||
|
@ -607,6 +642,7 @@
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
bind:this={confirmDeleteDialog}
|
bind:this={confirmDeleteDialog}
|
||||||
okText="Delete Column"
|
okText="Delete Column"
|
||||||
|
@ -622,3 +658,24 @@
|
||||||
</p>
|
</p>
|
||||||
<Input bind:value={deleteColName} placeholder={originalName} />
|
<Input bind:value={deleteColName} placeholder={originalName} />
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-top: var(--spacing-s);
|
||||||
|
gap: var(--spacing-l);
|
||||||
|
}
|
||||||
|
.split-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-length {
|
||||||
|
flex-basis: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-length {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,15 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, onMount } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import { Modal } from "@budibase/bbui"
|
|
||||||
import CreateEditColumn from "components/backend/DataTable/modals/CreateEditColumn.svelte"
|
import CreateEditColumn from "components/backend/DataTable/modals/CreateEditColumn.svelte"
|
||||||
|
|
||||||
const { rows, subscribe } = getContext("grid")
|
const { rows } = getContext("grid")
|
||||||
|
|
||||||
let modal
|
|
||||||
|
|
||||||
onMount(() => subscribe("add-column", modal.show))
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:this={modal}>
|
<CreateEditColumn on:updatecolumns={rows.actions.refreshTableDefinition} />
|
||||||
<CreateEditColumn on:updatecolumns={rows.actions.refreshTableDefinition} />
|
|
||||||
</Modal>
|
|
||||||
|
|
|
@ -1,24 +1,19 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext, onMount } from "svelte"
|
import { getContext, onMount } from "svelte"
|
||||||
import { Modal } from "@budibase/bbui"
|
|
||||||
import CreateEditColumn from "../CreateEditColumn.svelte"
|
import CreateEditColumn from "../CreateEditColumn.svelte"
|
||||||
|
|
||||||
const { rows, subscribe } = getContext("grid")
|
const { rows, subscribe } = getContext("grid")
|
||||||
|
|
||||||
let editableColumn
|
let editableColumn
|
||||||
let editColumnModal
|
|
||||||
|
|
||||||
const editColumn = column => {
|
const editColumn = column => {
|
||||||
editableColumn = column
|
editableColumn = column
|
||||||
editColumnModal.show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => subscribe("edit-column", editColumn))
|
onMount(() => subscribe("edit-column", editColumn))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:this={editColumnModal}>
|
<CreateEditColumn
|
||||||
<CreateEditColumn
|
field={editableColumn}
|
||||||
field={editableColumn}
|
on:updatecolumns={rows.actions.refreshData}
|
||||||
on:updatecolumns={rows.actions.refreshData}
|
/>
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ export const FIELDS = {
|
||||||
STRING: {
|
STRING: {
|
||||||
name: "Text",
|
name: "Text",
|
||||||
type: "string",
|
type: "string",
|
||||||
|
icon: "Text",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "string",
|
type: "string",
|
||||||
length: {},
|
length: {},
|
||||||
|
@ -11,6 +12,7 @@ export const FIELDS = {
|
||||||
BARCODEQR: {
|
BARCODEQR: {
|
||||||
name: "Barcode/QR",
|
name: "Barcode/QR",
|
||||||
type: "barcodeqr",
|
type: "barcodeqr",
|
||||||
|
icon: "Camera",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "string",
|
type: "string",
|
||||||
length: {},
|
length: {},
|
||||||
|
@ -20,6 +22,7 @@ export const FIELDS = {
|
||||||
LONGFORM: {
|
LONGFORM: {
|
||||||
name: "Long Form Text",
|
name: "Long Form Text",
|
||||||
type: "longform",
|
type: "longform",
|
||||||
|
icon: "TextAlignLeft",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "string",
|
type: "string",
|
||||||
length: {},
|
length: {},
|
||||||
|
@ -29,6 +32,7 @@ export const FIELDS = {
|
||||||
OPTIONS: {
|
OPTIONS: {
|
||||||
name: "Options",
|
name: "Options",
|
||||||
type: "options",
|
type: "options",
|
||||||
|
icon: "Dropdown",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "string",
|
type: "string",
|
||||||
presence: false,
|
presence: false,
|
||||||
|
@ -38,6 +42,7 @@ export const FIELDS = {
|
||||||
ARRAY: {
|
ARRAY: {
|
||||||
name: "Multi-select",
|
name: "Multi-select",
|
||||||
type: "array",
|
type: "array",
|
||||||
|
icon: "Duplicate",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "array",
|
type: "array",
|
||||||
presence: false,
|
presence: false,
|
||||||
|
@ -47,6 +52,7 @@ export const FIELDS = {
|
||||||
NUMBER: {
|
NUMBER: {
|
||||||
name: "Number",
|
name: "Number",
|
||||||
type: "number",
|
type: "number",
|
||||||
|
icon: "123",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "number",
|
type: "number",
|
||||||
presence: false,
|
presence: false,
|
||||||
|
@ -56,10 +62,12 @@ export const FIELDS = {
|
||||||
BIGINT: {
|
BIGINT: {
|
||||||
name: "BigInt",
|
name: "BigInt",
|
||||||
type: "bigint",
|
type: "bigint",
|
||||||
|
icon: "TagBold",
|
||||||
},
|
},
|
||||||
BOOLEAN: {
|
BOOLEAN: {
|
||||||
name: "Boolean",
|
name: "Boolean",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
|
icon: "Boolean",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
presence: false,
|
presence: false,
|
||||||
|
@ -68,6 +76,7 @@ export const FIELDS = {
|
||||||
DATETIME: {
|
DATETIME: {
|
||||||
name: "Date/Time",
|
name: "Date/Time",
|
||||||
type: "datetime",
|
type: "datetime",
|
||||||
|
icon: "Calendar",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "string",
|
type: "string",
|
||||||
length: {},
|
length: {},
|
||||||
|
@ -81,6 +90,7 @@ export const FIELDS = {
|
||||||
ATTACHMENT: {
|
ATTACHMENT: {
|
||||||
name: "Attachment",
|
name: "Attachment",
|
||||||
type: "attachment",
|
type: "attachment",
|
||||||
|
icon: "Folder",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "array",
|
type: "array",
|
||||||
presence: false,
|
presence: false,
|
||||||
|
@ -89,6 +99,7 @@ export const FIELDS = {
|
||||||
LINK: {
|
LINK: {
|
||||||
name: "Relationship",
|
name: "Relationship",
|
||||||
type: "link",
|
type: "link",
|
||||||
|
icon: "Link",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "array",
|
type: "array",
|
||||||
presence: false,
|
presence: false,
|
||||||
|
@ -97,11 +108,13 @@ export const FIELDS = {
|
||||||
FORMULA: {
|
FORMULA: {
|
||||||
name: "Formula",
|
name: "Formula",
|
||||||
type: "formula",
|
type: "formula",
|
||||||
|
icon: "Calculator",
|
||||||
constraints: {},
|
constraints: {},
|
||||||
},
|
},
|
||||||
JSON: {
|
JSON: {
|
||||||
name: "JSON",
|
name: "JSON",
|
||||||
type: "json",
|
type: "json",
|
||||||
|
icon: "Brackets",
|
||||||
constraints: {
|
constraints: {
|
||||||
type: "object",
|
type: "object",
|
||||||
presence: false,
|
presence: false,
|
||||||
|
|
|
@ -75,6 +75,14 @@
|
||||||
{
|
{
|
||||||
"name": "Chart",
|
"name": "Chart",
|
||||||
"icon": "GraphBarVertical",
|
"icon": "GraphBarVertical",
|
||||||
"children": ["bar", "line", "area", "candlestick", "pie", "donut"]
|
"children": [
|
||||||
|
"bar",
|
||||||
|
"line",
|
||||||
|
"area",
|
||||||
|
"candlestick",
|
||||||
|
"pie",
|
||||||
|
"donut",
|
||||||
|
"histogram"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -2212,6 +2212,147 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"histogram": {
|
||||||
|
"name": "Histogram Chart",
|
||||||
|
"description": "Histogram chart",
|
||||||
|
"icon": "Histogram",
|
||||||
|
"size": {
|
||||||
|
"width": 600,
|
||||||
|
"height": 400
|
||||||
|
},
|
||||||
|
"requiredAncestors": ["dataprovider"],
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Title",
|
||||||
|
"key": "title"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dataProvider",
|
||||||
|
"label": "Provider",
|
||||||
|
"key": "dataProvider",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"label": "Data column",
|
||||||
|
"key": "valueColumn",
|
||||||
|
"dependsOn": "dataProvider",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Y axis label",
|
||||||
|
"key": "yAxisLabel",
|
||||||
|
"defaultValue": "Frequency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "X axis label",
|
||||||
|
"key": "xAxisLabel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"label": "Bucket count",
|
||||||
|
"key": "bucketCount",
|
||||||
|
"defaultValue": 10,
|
||||||
|
"min": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Data labels",
|
||||||
|
"key": "dataLabels",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Width",
|
||||||
|
"key": "width"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Height",
|
||||||
|
"key": "height",
|
||||||
|
"defaultValue": "400"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"label": "Colors",
|
||||||
|
"key": "palette",
|
||||||
|
"defaultValue": "Palette 1",
|
||||||
|
"options": [
|
||||||
|
"Custom",
|
||||||
|
"Palette 1",
|
||||||
|
"Palette 2",
|
||||||
|
"Palette 3",
|
||||||
|
"Palette 4",
|
||||||
|
"Palette 5",
|
||||||
|
"Palette 6",
|
||||||
|
"Palette 7",
|
||||||
|
"Palette 8",
|
||||||
|
"Palette 9",
|
||||||
|
"Palette 10"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C1",
|
||||||
|
"key": "c1",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C2",
|
||||||
|
"key": "c2",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C3",
|
||||||
|
"key": "c3",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C4",
|
||||||
|
"key": "c4",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "color",
|
||||||
|
"label": "C5",
|
||||||
|
"key": "c5",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "palette",
|
||||||
|
"value": "Custom"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Animate",
|
||||||
|
"key": "animate",
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Horizontal",
|
||||||
|
"key": "horizontal",
|
||||||
|
"defaultValue": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"form": {
|
"form": {
|
||||||
"name": "Form",
|
"name": "Form",
|
||||||
"icon": "Form",
|
"icon": "Form",
|
||||||
|
@ -3965,6 +4106,10 @@
|
||||||
"label": "Bar",
|
"label": "Bar",
|
||||||
"value": "bar"
|
"value": "bar"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "Histogram",
|
||||||
|
"value": "histogram"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "Line",
|
"label": "Line",
|
||||||
"value": "line"
|
"value": "line"
|
||||||
|
@ -4215,6 +4360,47 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"section": true,
|
||||||
|
"name": "Histogram Chart",
|
||||||
|
"icon": "Histogram",
|
||||||
|
"dependsOn": {
|
||||||
|
"setting": "chartType",
|
||||||
|
"value": "histogram"
|
||||||
|
},
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "field",
|
||||||
|
"label": "Value column",
|
||||||
|
"key": "valueColumn",
|
||||||
|
"dependsOn": "dataSource",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "Y axis label",
|
||||||
|
"key": "yAxisLabel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"label": "X axis label",
|
||||||
|
"key": "xAxisLabel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"label": "Horizontal",
|
||||||
|
"key": "horizontal",
|
||||||
|
"defaultValue": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number",
|
||||||
|
"label": "Bucket count",
|
||||||
|
"key": "bucketCount",
|
||||||
|
"defaultValue": 10,
|
||||||
|
"min": 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"section": true,
|
"section": true,
|
||||||
"name": "Line Chart",
|
"name": "Line Chart",
|
||||||
|
@ -5234,11 +5420,7 @@
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Hide notifications",
|
"label": "Hide notifications",
|
||||||
"key": "notificationOverride",
|
"key": "notificationOverride",
|
||||||
"defaultValue": false,
|
"defaultValue": false
|
||||||
"dependsOn": {
|
|
||||||
"setting": "showSaveButton",
|
|
||||||
"value": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,9 @@
|
||||||
export let lowColumn
|
export let lowColumn
|
||||||
export let dateColumn
|
export let dateColumn
|
||||||
|
|
||||||
|
// Histogram
|
||||||
|
export let bucketCount
|
||||||
|
|
||||||
let dataProviderId
|
let dataProviderId
|
||||||
|
|
||||||
$: colors = c1 && c2 && c3 && c4 && c5 ? [c1, c2, c3, c4, c5] : null
|
$: colors = c1 && c2 && c3 && c4 && c5 ? [c1, c2, c3, c4, c5] : null
|
||||||
|
@ -92,6 +95,7 @@
|
||||||
highColumn,
|
highColumn,
|
||||||
lowColumn,
|
lowColumn,
|
||||||
dateColumn,
|
dateColumn,
|
||||||
|
bucketCount,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -83,6 +83,7 @@
|
||||||
tableId: dataSource?.tableId,
|
tableId: dataSource?.tableId,
|
||||||
rowId: `{{ ${safe(repeaterId)}.${safe("_id")} }}`,
|
rowId: `{{ ${safe(repeaterId)}.${safe("_id")} }}`,
|
||||||
revId: `{{ ${safe(repeaterId)}.${safe("_rev")} }}`,
|
revId: `{{ ${safe(repeaterId)}.${safe("_rev")} }}`,
|
||||||
|
notificationOverride,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
<script>
|
||||||
|
import { ApexOptionsBuilder } from "./ApexOptionsBuilder"
|
||||||
|
import ApexChart from "./ApexChart.svelte"
|
||||||
|
|
||||||
|
export let title
|
||||||
|
export let dataProvider
|
||||||
|
export let valueColumn
|
||||||
|
export let xAxisLabel
|
||||||
|
export let yAxisLabel
|
||||||
|
export let height
|
||||||
|
export let width
|
||||||
|
export let dataLabels
|
||||||
|
export let animate
|
||||||
|
export let palette
|
||||||
|
export let c1, c2, c3, c4, c5
|
||||||
|
export let horizontal
|
||||||
|
export let bucketCount = 10
|
||||||
|
|
||||||
|
$: options = setUpChart(
|
||||||
|
title,
|
||||||
|
dataProvider,
|
||||||
|
valueColumn,
|
||||||
|
xAxisLabel || valueColumn,
|
||||||
|
yAxisLabel,
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
dataLabels,
|
||||||
|
animate,
|
||||||
|
palette,
|
||||||
|
horizontal,
|
||||||
|
c1 && c2 && c3 && c4 && c5 ? [c1, c2, c3, c4, c5] : null,
|
||||||
|
customColor,
|
||||||
|
bucketCount
|
||||||
|
)
|
||||||
|
|
||||||
|
$: customColor = palette === "Custom"
|
||||||
|
|
||||||
|
const setUpChart = (
|
||||||
|
title,
|
||||||
|
dataProvider,
|
||||||
|
valueColumn,
|
||||||
|
xAxisLabel, //freqAxisLabel
|
||||||
|
yAxisLabel, //valueAxisLabel
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
dataLabels,
|
||||||
|
animate,
|
||||||
|
palette,
|
||||||
|
horizontal,
|
||||||
|
colors,
|
||||||
|
customColor,
|
||||||
|
bucketCount
|
||||||
|
) => {
|
||||||
|
const allCols = [valueColumn]
|
||||||
|
if (
|
||||||
|
!dataProvider ||
|
||||||
|
!dataProvider.rows?.length ||
|
||||||
|
allCols.find(x => x == null)
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch data
|
||||||
|
const { schema, rows } = dataProvider
|
||||||
|
const reducer = row => (valid, column) => valid && row[column] != null
|
||||||
|
const hasAllColumns = row => allCols.reduce(reducer(row), true)
|
||||||
|
const data = rows.filter(row => hasAllColumns(row)).slice(0, 100)
|
||||||
|
if (!schema || !data.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialise default chart
|
||||||
|
let builder = new ApexOptionsBuilder()
|
||||||
|
.type("bar")
|
||||||
|
.title(title)
|
||||||
|
.width(width)
|
||||||
|
.height(height)
|
||||||
|
.xLabel(horizontal ? yAxisLabel : xAxisLabel)
|
||||||
|
.yLabel(horizontal ? xAxisLabel : yAxisLabel)
|
||||||
|
.dataLabels(dataLabels)
|
||||||
|
.animate(animate)
|
||||||
|
.palette(palette)
|
||||||
|
.horizontal(horizontal)
|
||||||
|
.colors(customColor ? colors : null)
|
||||||
|
|
||||||
|
if (horizontal) {
|
||||||
|
builder = builder.setOption(["plotOptions", "bar", "barHeight"], "90%")
|
||||||
|
} else {
|
||||||
|
builder = builder.setOption(["plotOptions", "bar", "columnWidth"], "99%")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull occurences of the value.
|
||||||
|
let flatlist = data.map(row => {
|
||||||
|
return row[valueColumn]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Build range buckets
|
||||||
|
let interval = Math.max(...flatlist) / bucketCount
|
||||||
|
let counts = Array(bucketCount).fill(0)
|
||||||
|
|
||||||
|
// Assign row data to a bucket
|
||||||
|
let buckets = flatlist.reduce((acc, val) => {
|
||||||
|
let dest = Math.min(Math.floor(val / interval), bucketCount - 1)
|
||||||
|
acc[dest] = acc[dest] + 1
|
||||||
|
return acc
|
||||||
|
}, counts)
|
||||||
|
|
||||||
|
const rangeLabel = bucketIdx => {
|
||||||
|
return `${Math.floor(interval * bucketIdx)} - ${Math.floor(
|
||||||
|
interval * (bucketIdx + 1)
|
||||||
|
)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const series = [
|
||||||
|
{
|
||||||
|
name: yAxisLabel,
|
||||||
|
data: Array.from({ length: buckets.length }, (_, i) => ({
|
||||||
|
x: rangeLabel(i),
|
||||||
|
y: buckets[i],
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
builder = builder.setOption(["xaxis", "labels"], {
|
||||||
|
formatter: x => {
|
||||||
|
return x + ""
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
builder = builder.series(series)
|
||||||
|
|
||||||
|
return builder.getOptions()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ApexChart {options} />
|
|
@ -4,3 +4,4 @@ export { default as pie } from "./PieChart.svelte"
|
||||||
export { default as donut } from "./DonutChart.svelte"
|
export { default as donut } from "./DonutChart.svelte"
|
||||||
export { default as area } from "./AreaChart.svelte"
|
export { default as area } from "./AreaChart.svelte"
|
||||||
export { default as candlestick } from "./CandleStickChart.svelte"
|
export { default as candlestick } from "./CandleStickChart.svelte"
|
||||||
|
export { default as histogram } from "./HistogramChart.svelte"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext, onMount, tick } from "svelte"
|
||||||
import GridCell from "./GridCell.svelte"
|
import GridCell from "./GridCell.svelte"
|
||||||
import { Icon, Popover, Menu, MenuItem } from "@budibase/bbui"
|
import { Icon, Popover, Menu, MenuItem, clickOutside } from "@budibase/bbui"
|
||||||
import { getColumnIcon } from "../lib/utils"
|
import { getColumnIcon } from "../lib/utils"
|
||||||
|
|
||||||
export let column
|
export let column
|
||||||
|
@ -16,6 +16,7 @@
|
||||||
sort,
|
sort,
|
||||||
renderedColumns,
|
renderedColumns,
|
||||||
dispatch,
|
dispatch,
|
||||||
|
subscribe,
|
||||||
config,
|
config,
|
||||||
ui,
|
ui,
|
||||||
columns,
|
columns,
|
||||||
|
@ -32,7 +33,9 @@
|
||||||
|
|
||||||
let anchor
|
let anchor
|
||||||
let open = false
|
let open = false
|
||||||
|
let editIsOpen = false
|
||||||
let timeout
|
let timeout
|
||||||
|
let popover
|
||||||
|
|
||||||
$: sortedBy = column.name === $sort.column
|
$: sortedBy = column.name === $sort.column
|
||||||
$: canMoveLeft = orderable && idx > 0
|
$: canMoveLeft = orderable && idx > 0
|
||||||
|
@ -44,11 +47,16 @@
|
||||||
? "high-low"
|
? "high-low"
|
||||||
: "Z-A"
|
: "Z-A"
|
||||||
|
|
||||||
const editColumn = () => {
|
const editColumn = async () => {
|
||||||
|
editIsOpen = true
|
||||||
|
await tick()
|
||||||
dispatch("edit-column", column.schema)
|
dispatch("edit-column", column.schema)
|
||||||
open = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cancelEdit = () => {
|
||||||
|
popover.hide()
|
||||||
|
editIsOpen = false
|
||||||
|
}
|
||||||
const onMouseDown = e => {
|
const onMouseDown = e => {
|
||||||
if (e.button === 0 && orderable) {
|
if (e.button === 0 && orderable) {
|
||||||
timeout = setTimeout(() => {
|
timeout = setTimeout(() => {
|
||||||
|
@ -109,6 +117,7 @@
|
||||||
columns.actions.saveChanges()
|
columns.actions.saveChanges()
|
||||||
open = false
|
open = false
|
||||||
}
|
}
|
||||||
|
onMount(() => subscribe("close-edit-column", cancelEdit))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -157,57 +166,74 @@
|
||||||
|
|
||||||
<Popover
|
<Popover
|
||||||
bind:open
|
bind:open
|
||||||
|
bind:this={popover}
|
||||||
{anchor}
|
{anchor}
|
||||||
align="right"
|
align="right"
|
||||||
offset={0}
|
offset={0}
|
||||||
popoverTarget={document.getElementById(`grid-${rand}`)}
|
popoverTarget={document.getElementById(`grid-${rand}`)}
|
||||||
animate={false}
|
animate={false}
|
||||||
|
customZindex={100}
|
||||||
>
|
>
|
||||||
<Menu>
|
{#if editIsOpen}
|
||||||
<MenuItem
|
<div
|
||||||
icon="Edit"
|
use:clickOutside={() => {
|
||||||
on:click={editColumn}
|
editIsOpen = false
|
||||||
disabled={!$config.allowSchemaChanges || column.schema.disabled}
|
}}
|
||||||
|
class="content"
|
||||||
>
|
>
|
||||||
Edit column
|
<slot />
|
||||||
</MenuItem>
|
</div>
|
||||||
<MenuItem
|
{:else}
|
||||||
icon="Label"
|
<Menu>
|
||||||
on:click={makeDisplayColumn}
|
<MenuItem
|
||||||
disabled={idx === "sticky" ||
|
icon="Edit"
|
||||||
!$config.allowSchemaChanges ||
|
on:click={editColumn}
|
||||||
bannedDisplayColumnTypes.includes(column.schema.type)}
|
disabled={!$config.allowSchemaChanges || column.schema.disabled}
|
||||||
>
|
>
|
||||||
Use as display column
|
Edit column
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon="SortOrderUp"
|
icon="Label"
|
||||||
on:click={sortAscending}
|
on:click={makeDisplayColumn}
|
||||||
disabled={column.name === $sort.column && $sort.order === "ascending"}
|
disabled={idx === "sticky" ||
|
||||||
>
|
!$config.allowSchemaChanges ||
|
||||||
Sort {ascendingLabel}
|
bannedDisplayColumnTypes.includes(column.schema.type)}
|
||||||
</MenuItem>
|
>
|
||||||
<MenuItem
|
Use as display column
|
||||||
icon="SortOrderDown"
|
</MenuItem>
|
||||||
on:click={sortDescending}
|
<MenuItem
|
||||||
disabled={column.name === $sort.column && $sort.order === "descending"}
|
icon="SortOrderUp"
|
||||||
>
|
on:click={sortAscending}
|
||||||
Sort {descendingLabel}
|
disabled={column.name === $sort.column && $sort.order === "ascending"}
|
||||||
</MenuItem>
|
>
|
||||||
<MenuItem disabled={!canMoveLeft} icon="ChevronLeft" on:click={moveLeft}>
|
Sort {ascendingLabel}
|
||||||
Move left
|
</MenuItem>
|
||||||
</MenuItem>
|
<MenuItem
|
||||||
<MenuItem disabled={!canMoveRight} icon="ChevronRight" on:click={moveRight}>
|
icon="SortOrderDown"
|
||||||
Move right
|
on:click={sortDescending}
|
||||||
</MenuItem>
|
disabled={column.name === $sort.column && $sort.order === "descending"}
|
||||||
<MenuItem
|
>
|
||||||
disabled={idx === "sticky" || !$config.showControls}
|
Sort {descendingLabel}
|
||||||
icon="VisibilityOff"
|
</MenuItem>
|
||||||
on:click={hideColumn}
|
<MenuItem disabled={!canMoveLeft} icon="ChevronLeft" on:click={moveLeft}>
|
||||||
>
|
Move left
|
||||||
Hide column
|
</MenuItem>
|
||||||
</MenuItem>
|
<MenuItem
|
||||||
</Menu>
|
disabled={!canMoveRight}
|
||||||
|
icon="ChevronRight"
|
||||||
|
on:click={moveRight}
|
||||||
|
>
|
||||||
|
Move right
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
disabled={idx === "sticky" || !$config.showControls}
|
||||||
|
icon="VisibilityOff"
|
||||||
|
on:click={hideColumn}
|
||||||
|
>
|
||||||
|
Hide column
|
||||||
|
</MenuItem>
|
||||||
|
</Menu>
|
||||||
|
{/if}
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@ -255,4 +281,13 @@
|
||||||
.header-cell:hover .sort-indicator {
|
.header-cell:hover .sort-indicator {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: 300px;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
background: var(--spectrum-alias-background-color-secondary);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
let focusedOptionIdx = null
|
let focusedOptionIdx = null
|
||||||
|
|
||||||
$: options = schema?.constraints?.inclusion || []
|
$: options = schema?.constraints?.inclusion || []
|
||||||
|
$: optionColors = schema?.optionColors || {}
|
||||||
$: editable = focused && !readonly
|
$: editable = focused && !readonly
|
||||||
$: values = Array.isArray(value) ? value : [value].filter(x => x != null)
|
$: values = Array.isArray(value) ? value : [value].filter(x => x != null)
|
||||||
$: {
|
$: {
|
||||||
|
@ -93,7 +94,7 @@
|
||||||
on:click={editable ? open : null}
|
on:click={editable ? open : null}
|
||||||
>
|
>
|
||||||
{#each values as val}
|
{#each values as val}
|
||||||
{@const color = getOptionColor(val)}
|
{@const color = optionColors[val] || getOptionColor(val)}
|
||||||
{#if color}
|
{#if color}
|
||||||
<div class="badge text" style="--color: {color}">
|
<div class="badge text" style="--color: {color}">
|
||||||
<span>
|
<span>
|
||||||
|
@ -121,7 +122,7 @@
|
||||||
use:clickOutside={close}
|
use:clickOutside={close}
|
||||||
>
|
>
|
||||||
{#each options as option, idx}
|
{#each options as option, idx}
|
||||||
{@const color = getOptionColor(option)}
|
{@const color = optionColors[option] || getOptionColor(option)}
|
||||||
<div
|
<div
|
||||||
class="option"
|
class="option"
|
||||||
on:click={() => toggleOption(option)}
|
on:click={() => toggleOption(option)}
|
||||||
|
|
|
@ -139,9 +139,20 @@
|
||||||
{#if $loaded}
|
{#if $loaded}
|
||||||
<div class="grid-data-outer" use:clickOutside={ui.actions.blur}>
|
<div class="grid-data-outer" use:clickOutside={ui.actions.blur}>
|
||||||
<div class="grid-data-inner">
|
<div class="grid-data-inner">
|
||||||
<StickyColumn />
|
<StickyColumn>
|
||||||
|
<svelte:fragment slot="edit-column">
|
||||||
|
<slot name="edit-column" />
|
||||||
|
</svelte:fragment>
|
||||||
|
</StickyColumn>
|
||||||
<div class="grid-data-content">
|
<div class="grid-data-content">
|
||||||
<HeaderRow />
|
<HeaderRow>
|
||||||
|
<svelte:fragment slot="add-column">
|
||||||
|
<slot name="add-column" />
|
||||||
|
</svelte:fragment>
|
||||||
|
<svelte:fragment slot="edit-column">
|
||||||
|
<slot name="edit-column" />
|
||||||
|
</svelte:fragment>
|
||||||
|
</HeaderRow>
|
||||||
<GridBody />
|
<GridBody />
|
||||||
</div>
|
</div>
|
||||||
{#if $canAddRows}
|
{#if $canAddRows}
|
||||||
|
|
|
@ -1,34 +1,22 @@
|
||||||
<script>
|
<script>
|
||||||
|
import NewColumnButton from "./NewColumnButton.svelte"
|
||||||
|
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import GridScrollWrapper from "./GridScrollWrapper.svelte"
|
import GridScrollWrapper from "./GridScrollWrapper.svelte"
|
||||||
import HeaderCell from "../cells/HeaderCell.svelte"
|
import HeaderCell from "../cells/HeaderCell.svelte"
|
||||||
import { Icon, TempTooltip, TooltipType } from "@budibase/bbui"
|
import { TempTooltip, TooltipType } from "@budibase/bbui"
|
||||||
|
|
||||||
const {
|
const { renderedColumns, config, hasNonAutoColumn, tableId, loading } =
|
||||||
renderedColumns,
|
getContext("grid")
|
||||||
dispatch,
|
|
||||||
scroll,
|
|
||||||
hiddenColumnsWidth,
|
|
||||||
width,
|
|
||||||
config,
|
|
||||||
hasNonAutoColumn,
|
|
||||||
tableId,
|
|
||||||
loading,
|
|
||||||
} = getContext("grid")
|
|
||||||
|
|
||||||
$: columnsWidth = $renderedColumns.reduce(
|
|
||||||
(total, col) => total + col.width,
|
|
||||||
0
|
|
||||||
)
|
|
||||||
$: end = $hiddenColumnsWidth + columnsWidth - 1 - $scroll.left
|
|
||||||
$: left = Math.min($width - 40, end)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<GridScrollWrapper scrollHorizontally>
|
<GridScrollWrapper scrollHorizontally>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{#each $renderedColumns as column, idx}
|
{#each $renderedColumns as column, idx}
|
||||||
<HeaderCell {column} {idx} />
|
<HeaderCell {column} {idx}>
|
||||||
|
<slot name="edit-column" />
|
||||||
|
</HeaderCell>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</GridScrollWrapper>
|
</GridScrollWrapper>
|
||||||
|
@ -39,13 +27,9 @@
|
||||||
type={TooltipType.Info}
|
type={TooltipType.Info}
|
||||||
condition={!$hasNonAutoColumn && !$loading}
|
condition={!$hasNonAutoColumn && !$loading}
|
||||||
>
|
>
|
||||||
<div
|
<NewColumnButton>
|
||||||
class="add"
|
<slot name="add-column" />
|
||||||
style="left:{left}px;"
|
</NewColumnButton>
|
||||||
on:click={() => dispatch("add-column")}
|
|
||||||
>
|
|
||||||
<Icon name="Add" />
|
|
||||||
</div>
|
|
||||||
</TempTooltip>
|
</TempTooltip>
|
||||||
{/key}
|
{/key}
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -61,21 +45,4 @@
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
.add {
|
|
||||||
height: var(--default-row-height);
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
width: 40px;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
border-left: var(--cell-border);
|
|
||||||
border-right: var(--cell-border);
|
|
||||||
border-bottom: var(--cell-border);
|
|
||||||
background: var(--grid-background-alt);
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
.add:hover {
|
|
||||||
background: var(--spectrum-global-color-gray-200);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
<script>
|
||||||
|
import { getContext, onMount } from "svelte"
|
||||||
|
import { Icon, Popover, clickOutside } from "@budibase/bbui"
|
||||||
|
|
||||||
|
const { renderedColumns, scroll, hiddenColumnsWidth, width, subscribe } =
|
||||||
|
getContext("grid")
|
||||||
|
|
||||||
|
let anchor
|
||||||
|
let open = false
|
||||||
|
$: columnsWidth = $renderedColumns.reduce(
|
||||||
|
(total, col) => (total += col.width),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
$: end = $hiddenColumnsWidth + columnsWidth - 1 - $scroll.left
|
||||||
|
$: left = Math.min($width - 40, end)
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
open = false
|
||||||
|
}
|
||||||
|
onMount(() => subscribe("close-edit-column", close))
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="add-column-button"
|
||||||
|
bind:this={anchor}
|
||||||
|
class="add"
|
||||||
|
style="left:{left}px"
|
||||||
|
on:click={() => (open = true)}
|
||||||
|
>
|
||||||
|
<Icon name="Add" />
|
||||||
|
</div>
|
||||||
|
<Popover
|
||||||
|
bind:open
|
||||||
|
{anchor}
|
||||||
|
align="right"
|
||||||
|
offset={0}
|
||||||
|
popoverTarget={document.getElementById(`add-column-button`)}
|
||||||
|
animate={false}
|
||||||
|
customZindex={100}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
use:clickOutside={() => {
|
||||||
|
open = false
|
||||||
|
}}
|
||||||
|
class="content"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.add {
|
||||||
|
height: var(--default-row-height);
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 40px;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
border-left: var(--cell-border);
|
||||||
|
border-right: var(--cell-border);
|
||||||
|
border-bottom: var(--cell-border);
|
||||||
|
background: var(--grid-background-alt);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
.add:hover {
|
||||||
|
background: var(--spectrum-global-color-gray-200);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
width: 300px;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
z-index: 2;
|
||||||
|
background: var(--spectrum-alias-background-color-secondary);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -57,7 +57,9 @@
|
||||||
disabled={!$renderedRows.length}
|
disabled={!$renderedRows.length}
|
||||||
/>
|
/>
|
||||||
{#if $stickyColumn}
|
{#if $stickyColumn}
|
||||||
<HeaderCell column={$stickyColumn} orderable={false} idx="sticky" />
|
<HeaderCell column={$stickyColumn} orderable={false} idx="sticky">
|
||||||
|
<slot name="edit-column" />
|
||||||
|
</HeaderCell>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue