Merge branch 'master' into table-width-setting
This commit is contained in:
commit
ba6747f9f8
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.26.3",
|
"version": "2.26.4",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
|
|
@ -155,6 +155,8 @@ export default function positionDropdown(element, opts) {
|
||||||
applyXStrategy(Strategies.StartToEnd)
|
applyXStrategy(Strategies.StartToEnd)
|
||||||
} else if (align === "left-outside") {
|
} else if (align === "left-outside") {
|
||||||
applyXStrategy(Strategies.EndToStart)
|
applyXStrategy(Strategies.EndToStart)
|
||||||
|
} else if (align === "center") {
|
||||||
|
applyXStrategy(Strategies.MidPoint)
|
||||||
} else {
|
} else {
|
||||||
applyXStrategy(Strategies.StartToStart)
|
applyXStrategy(Strategies.StartToStart)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,8 @@
|
||||||
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
|
import LinkedRowSelector from "components/common/LinkedRowSelector.svelte"
|
||||||
import Editor from "../../integration/QueryEditor.svelte"
|
import Editor from "../../integration/QueryEditor.svelte"
|
||||||
|
|
||||||
export let defaultValue
|
|
||||||
export let meta
|
export let meta
|
||||||
export let value = defaultValue || (meta.type === "boolean" ? false : "")
|
export let value
|
||||||
export let readonly
|
export let readonly
|
||||||
export let error
|
export let error
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { datasources, tables, integrations, appStore } from "stores/builder"
|
import { datasources, tables, integrations, appStore } from "stores/builder"
|
||||||
|
import { admin } from "stores/portal"
|
||||||
import EditRolesButton from "./buttons/EditRolesButton.svelte"
|
import EditRolesButton from "./buttons/EditRolesButton.svelte"
|
||||||
import { TableNames } from "constants"
|
import { TableNames } from "constants"
|
||||||
import { Grid } from "@budibase/frontend-core"
|
import { Grid } from "@budibase/frontend-core"
|
||||||
|
@ -63,6 +64,7 @@
|
||||||
schemaOverrides={isUsersTable ? userSchemaOverrides : null}
|
schemaOverrides={isUsersTable ? userSchemaOverrides : null}
|
||||||
showAvatars={false}
|
showAvatars={false}
|
||||||
on:updatedatasource={handleGridTableUpdate}
|
on:updatedatasource={handleGridTableUpdate}
|
||||||
|
isCloud={$admin.cloud}
|
||||||
>
|
>
|
||||||
<svelte:fragment slot="filter">
|
<svelte:fragment slot="filter">
|
||||||
{#if isUsersTable && $appStore.features.disableUserMetadata}
|
{#if isUsersTable && $appStore.features.disableUserMetadata}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { viewsV2 } from "stores/builder"
|
import { viewsV2 } from "stores/builder"
|
||||||
|
import { admin } from "stores/portal"
|
||||||
import { Grid } from "@budibase/frontend-core"
|
import { Grid } from "@budibase/frontend-core"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
import GridCreateEditRowModal from "components/backend/DataTable/modals/grid/GridCreateEditRowModal.svelte"
|
import GridCreateEditRowModal from "components/backend/DataTable/modals/grid/GridCreateEditRowModal.svelte"
|
||||||
|
@ -26,6 +27,7 @@
|
||||||
allowDeleteRows
|
allowDeleteRows
|
||||||
showAvatars={false}
|
showAvatars={false}
|
||||||
on:updatedatasource={handleGridViewUpdate}
|
on:updatedatasource={handleGridViewUpdate}
|
||||||
|
isCloud={$admin.cloud}
|
||||||
>
|
>
|
||||||
<svelte:fragment slot="filter">
|
<svelte:fragment slot="filter">
|
||||||
<GridFilterButton />
|
<GridFilterButton />
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { it, expect, describe, vi } from "vitest"
|
||||||
|
import Dropzone from "./Dropzone.svelte"
|
||||||
|
import { render, fireEvent } from "@testing-library/svelte"
|
||||||
|
import { notifications } from "@budibase/bbui"
|
||||||
|
import { admin } from "stores/portal"
|
||||||
|
|
||||||
|
vi.spyOn(notifications, "error").mockImplementation(() => {})
|
||||||
|
|
||||||
|
describe("Dropzone", () => {
|
||||||
|
let instance = null
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("that the Dropzone is rendered", () => {
|
||||||
|
instance = render(Dropzone, {})
|
||||||
|
expect(instance).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Ensure the correct error message is shown when uploading the file in cloud", async () => {
|
||||||
|
admin.subscribe = vi.fn().mockImplementation(callback => {
|
||||||
|
callback({ cloud: true })
|
||||||
|
return () => {}
|
||||||
|
})
|
||||||
|
instance = render(Dropzone, { props: { fileSizeLimit: 1000000 } }) // 1MB
|
||||||
|
const fileInput = instance.getByLabelText("Select a file to upload")
|
||||||
|
const file = new File(["hello".repeat(2000000)], "hello.png", {
|
||||||
|
type: "image/png",
|
||||||
|
})
|
||||||
|
await fireEvent.change(fileInput, { target: { files: [file] } })
|
||||||
|
expect(notifications.error).toHaveBeenCalledWith(
|
||||||
|
"Files cannot exceed 1MB. Please try again with smaller files."
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("Ensure the file size error message is not shown when running on self host", async () => {
|
||||||
|
admin.subscribe = vi.fn().mockImplementation(callback => {
|
||||||
|
callback({ cloud: false })
|
||||||
|
return () => {}
|
||||||
|
})
|
||||||
|
instance = render(Dropzone, { props: { fileSizeLimit: 1000000 } }) // 1MB
|
||||||
|
const fileInput = instance.getByLabelText("Select a file to upload")
|
||||||
|
const file = new File(["hello".repeat(2000000)], "hello.png", {
|
||||||
|
type: "image/png",
|
||||||
|
})
|
||||||
|
await fireEvent.change(fileInput, { target: { files: [file] } })
|
||||||
|
expect(notifications.error).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,9 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { Dropzone, notifications } from "@budibase/bbui"
|
import { Dropzone, notifications } from "@budibase/bbui"
|
||||||
|
import { admin } from "stores/portal"
|
||||||
import { API } from "api"
|
import { API } from "api"
|
||||||
|
|
||||||
export let value = []
|
export let value = []
|
||||||
export let label
|
export let label
|
||||||
|
export let fileSizeLimit = undefined
|
||||||
|
|
||||||
const BYTES_IN_MB = 1000000
|
const BYTES_IN_MB = 1000000
|
||||||
|
|
||||||
|
@ -34,5 +36,6 @@
|
||||||
{label}
|
{label}
|
||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
{processFiles}
|
{processFiles}
|
||||||
{handleFileTooLarge}
|
handleFileTooLarge={$admin.cloud ? handleFileTooLarge : null}
|
||||||
|
{fileSizeLimit}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
export let app
|
export let app
|
||||||
export let color
|
export let color
|
||||||
export let autoSave = false
|
export let autoSave = false
|
||||||
|
export let disabled = false
|
||||||
|
|
||||||
let modal
|
let modal
|
||||||
</script>
|
</script>
|
||||||
|
@ -14,12 +15,16 @@
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div class="editable-icon">
|
<div class="editable-icon">
|
||||||
|
{#if !disabled}
|
||||||
<div class="hover" on:click={modal.show}>
|
<div class="hover" on:click={modal.show}>
|
||||||
<Icon name="Edit" {size} color="var(--spectrum-global-color-gray-600)" />
|
<Icon name="Edit" {size} color="var(--spectrum-global-color-gray-600)" />
|
||||||
</div>
|
</div>
|
||||||
<div class="normal">
|
<div class="normal">
|
||||||
<Icon {name} {size} {color} />
|
<Icon name={name || "Apps"} {size} {color} />
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
|
<Icon {name} {size} {color} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal bind:this={modal}>
|
<Modal bind:this={modal}>
|
||||||
|
|
|
@ -0,0 +1,214 @@
|
||||||
|
<script>
|
||||||
|
import { Button, Label, Icon, Input, notifications } from "@budibase/bbui"
|
||||||
|
import { AppStatus } from "constants"
|
||||||
|
import { appStore, initialise } from "stores/builder"
|
||||||
|
import { appsStore } from "stores/portal"
|
||||||
|
import { API } from "api"
|
||||||
|
import { writable } from "svelte/store"
|
||||||
|
import { createValidationStore } from "helpers/validation/yup"
|
||||||
|
import * as appValidation from "helpers/validation/yup/app"
|
||||||
|
import EditableIcon from "components/common/EditableIcon.svelte"
|
||||||
|
import { isEqual } from "lodash"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
|
export let alignActions = "left"
|
||||||
|
|
||||||
|
const values = writable({})
|
||||||
|
const validation = createValidationStore()
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
let updating = false
|
||||||
|
let edited = false
|
||||||
|
let initialised = false
|
||||||
|
|
||||||
|
$: filteredApps = $appsStore.apps.filter(app => app.devId == $appStore.appId)
|
||||||
|
$: app = filteredApps.length ? filteredApps[0] : {}
|
||||||
|
$: appDeployed = app?.status === AppStatus.DEPLOYED
|
||||||
|
|
||||||
|
$: appName = $appStore.name
|
||||||
|
$: appURL = $appStore.url
|
||||||
|
$: appIconName = $appStore.icon?.name
|
||||||
|
$: appIconColor = $appStore.icon?.color
|
||||||
|
|
||||||
|
$: appMeta = {
|
||||||
|
name: appName,
|
||||||
|
url: appURL,
|
||||||
|
iconName: appIconName,
|
||||||
|
iconColor: appIconColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
const initForm = appMeta => {
|
||||||
|
edited = false
|
||||||
|
values.set({
|
||||||
|
...appMeta,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!initialised) {
|
||||||
|
setupValidation()
|
||||||
|
initialised = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const validate = (vals, appMeta) => {
|
||||||
|
const { url } = vals || {}
|
||||||
|
validation.check({
|
||||||
|
...vals,
|
||||||
|
url: url?.[0] === "/" ? url.substring(1, url.length) : url,
|
||||||
|
})
|
||||||
|
edited = !isEqual(vals, appMeta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// On app/apps update, reset the state.
|
||||||
|
$: initForm(appMeta)
|
||||||
|
$: validate($values, appMeta)
|
||||||
|
|
||||||
|
const resolveAppUrl = (template, name) => {
|
||||||
|
let parsedName
|
||||||
|
const resolvedName = resolveAppName(null, name)
|
||||||
|
parsedName = resolvedName ? resolvedName.toLowerCase() : ""
|
||||||
|
const parsedUrl = parsedName ? parsedName.replace(/\s+/g, "-") : ""
|
||||||
|
return encodeURI(parsedUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
const nameToUrl = appName => {
|
||||||
|
let resolvedUrl = resolveAppUrl(null, appName)
|
||||||
|
tidyUrl(resolvedUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolveAppName = (template, name) => {
|
||||||
|
if (template && !name) {
|
||||||
|
return template.name
|
||||||
|
}
|
||||||
|
return name ? name.trim() : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const tidyUrl = url => {
|
||||||
|
if (url && !url.startsWith("/")) {
|
||||||
|
url = `/${url}`
|
||||||
|
}
|
||||||
|
$values.url = url === "" ? null : url
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateIcon = e => {
|
||||||
|
const { name, color } = e.detail
|
||||||
|
$values.iconColor = color
|
||||||
|
$values.iconName = name
|
||||||
|
}
|
||||||
|
|
||||||
|
const setupValidation = async () => {
|
||||||
|
appValidation.name(validation, {
|
||||||
|
apps: $appsStore.apps,
|
||||||
|
currentApp: app,
|
||||||
|
})
|
||||||
|
appValidation.url(validation, {
|
||||||
|
apps: $appsStore.apps,
|
||||||
|
currentApp: app,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateApp() {
|
||||||
|
try {
|
||||||
|
await appsStore.save($appStore.appId, {
|
||||||
|
name: $values.name?.trim(),
|
||||||
|
url: $values.url?.trim(),
|
||||||
|
icon: {
|
||||||
|
name: $values.iconName,
|
||||||
|
color: $values.iconColor,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await initialiseApp()
|
||||||
|
notifications.success("App update successful")
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
notifications.error("Error updating app")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialiseApp = async () => {
|
||||||
|
const applicationPkg = await API.fetchAppPackage($appStore.appId)
|
||||||
|
await initialise(applicationPkg)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="form">
|
||||||
|
<div class="fields">
|
||||||
|
<div class="field">
|
||||||
|
<Label size="L">Name</Label>
|
||||||
|
<Input
|
||||||
|
bind:value={$values.name}
|
||||||
|
error={$validation.touched.name && $validation.errors.name}
|
||||||
|
on:blur={() => ($validation.touched.name = true)}
|
||||||
|
on:change={nameToUrl($values.name)}
|
||||||
|
disabled={appDeployed}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<Label size="L">URL</Label>
|
||||||
|
<Input
|
||||||
|
bind:value={$values.url}
|
||||||
|
error={$validation.touched.url && $validation.errors.url}
|
||||||
|
on:blur={() => ($validation.touched.url = true)}
|
||||||
|
on:change={tidyUrl($values.url)}
|
||||||
|
placeholder={$values.url
|
||||||
|
? $values.url
|
||||||
|
: `/${resolveAppUrl(null, $values.name)}`}
|
||||||
|
disabled={appDeployed}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<Label size="L">Icon</Label>
|
||||||
|
<EditableIcon
|
||||||
|
{app}
|
||||||
|
size="XL"
|
||||||
|
name={$values.iconName}
|
||||||
|
color={$values.iconColor}
|
||||||
|
on:change={updateIcon}
|
||||||
|
disabled={appDeployed}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="actions" class:right={alignActions === "right"}>
|
||||||
|
{#if !appDeployed}
|
||||||
|
<Button
|
||||||
|
cta
|
||||||
|
on:click={async () => {
|
||||||
|
updating = true
|
||||||
|
await updateApp()
|
||||||
|
updating = false
|
||||||
|
dispatch("updated")
|
||||||
|
}}
|
||||||
|
disabled={appDeployed || updating || !edited || !$validation.valid}
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
{:else}
|
||||||
|
<div class="edit-info">
|
||||||
|
<Icon size="S" name="Info" /> Unpublish your app to edit name and URL
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.actions.right {
|
||||||
|
justify-content: end;
|
||||||
|
}
|
||||||
|
.fields {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: var(--spacing-l);
|
||||||
|
}
|
||||||
|
.field {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 80px 220px;
|
||||||
|
grid-gap: var(--spacing-l);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.edit-info {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,68 @@
|
||||||
|
<script>
|
||||||
|
import { Popover, Layout, Icon } from "@budibase/bbui"
|
||||||
|
import UpdateAppForm from "./UpdateAppForm.svelte"
|
||||||
|
|
||||||
|
let formPopover
|
||||||
|
let formPopoverAnchor
|
||||||
|
let formPopoverOpen = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={formPopoverAnchor}>
|
||||||
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
|
<div
|
||||||
|
class="app-heading"
|
||||||
|
class:editing={formPopoverOpen}
|
||||||
|
on:click={() => {
|
||||||
|
formPopover.show()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
<span class="edit-icon">
|
||||||
|
<Icon size="S" name="Edit" color={"var(--grey-7)"} />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Popover
|
||||||
|
customZindex={998}
|
||||||
|
bind:this={formPopover}
|
||||||
|
align="center"
|
||||||
|
anchor={formPopoverAnchor}
|
||||||
|
offset={20}
|
||||||
|
on:close={() => {
|
||||||
|
formPopoverOpen = false
|
||||||
|
}}
|
||||||
|
on:open={() => {
|
||||||
|
formPopoverOpen = true
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Layout noPadding gap="M">
|
||||||
|
<div class="popover-content">
|
||||||
|
<UpdateAppForm
|
||||||
|
on:updated={() => {
|
||||||
|
formPopover.hide()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.popover-content {
|
||||||
|
padding: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
.app-heading {
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-s);
|
||||||
|
}
|
||||||
|
.edit-icon {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.app-heading:hover .edit-icon,
|
||||||
|
.app-heading.editing .edit-icon {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -8,13 +8,11 @@
|
||||||
ActionButton,
|
ActionButton,
|
||||||
Icon,
|
Icon,
|
||||||
Link,
|
Link,
|
||||||
Modal,
|
|
||||||
StatusLight,
|
StatusLight,
|
||||||
AbsTooltip,
|
AbsTooltip,
|
||||||
} from "@budibase/bbui"
|
} from "@budibase/bbui"
|
||||||
import RevertModal from "components/deploy/RevertModal.svelte"
|
import RevertModal from "components/deploy/RevertModal.svelte"
|
||||||
import VersionModal from "components/deploy/VersionModal.svelte"
|
import VersionModal from "components/deploy/VersionModal.svelte"
|
||||||
import UpdateAppModal from "components/start/UpdateAppModal.svelte"
|
|
||||||
import { processStringSync } from "@budibase/string-templates"
|
import { processStringSync } from "@budibase/string-templates"
|
||||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||||
import analytics, { Events, EventSource } from "analytics"
|
import analytics, { Events, EventSource } from "analytics"
|
||||||
|
@ -26,7 +24,6 @@
|
||||||
isOnlyUser,
|
isOnlyUser,
|
||||||
appStore,
|
appStore,
|
||||||
deploymentStore,
|
deploymentStore,
|
||||||
initialise,
|
|
||||||
sortedScreens,
|
sortedScreens,
|
||||||
} from "stores/builder"
|
} from "stores/builder"
|
||||||
import TourWrap from "components/portal/onboarding/TourWrap.svelte"
|
import TourWrap from "components/portal/onboarding/TourWrap.svelte"
|
||||||
|
@ -37,7 +34,6 @@
|
||||||
export let loaded
|
export let loaded
|
||||||
|
|
||||||
let unpublishModal
|
let unpublishModal
|
||||||
let updateAppModal
|
|
||||||
let revertModal
|
let revertModal
|
||||||
let versionModal
|
let versionModal
|
||||||
let appActionPopover
|
let appActionPopover
|
||||||
|
@ -61,11 +57,6 @@
|
||||||
$: canPublish = !publishing && loaded && $sortedScreens.length > 0
|
$: canPublish = !publishing && loaded && $sortedScreens.length > 0
|
||||||
$: lastDeployed = getLastDeployedString($deploymentStore, lastOpened)
|
$: lastDeployed = getLastDeployedString($deploymentStore, lastOpened)
|
||||||
|
|
||||||
const initialiseApp = async () => {
|
|
||||||
const applicationPkg = await API.fetchAppPackage($appStore.devId)
|
|
||||||
await initialise(applicationPkg)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getLastDeployedString = deployments => {
|
const getLastDeployedString = deployments => {
|
||||||
return deployments?.length
|
return deployments?.length
|
||||||
? processStringSync("Published {{ duration time 'millisecond' }} ago", {
|
? processStringSync("Published {{ duration time 'millisecond' }} ago", {
|
||||||
|
@ -247,16 +238,12 @@
|
||||||
appActionPopover.hide()
|
appActionPopover.hide()
|
||||||
if (isPublished) {
|
if (isPublished) {
|
||||||
viewApp()
|
viewApp()
|
||||||
} else {
|
|
||||||
updateAppModal.show()
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{$appStore.url}
|
{$appStore.url}
|
||||||
{#if isPublished}
|
{#if isPublished}
|
||||||
<Icon size="S" name="LinkOut" />
|
<Icon size="S" name="LinkOut" />
|
||||||
{:else}
|
|
||||||
<Icon size="S" name="Edit" />
|
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
</Body>
|
</Body>
|
||||||
|
@ -330,20 +317,6 @@
|
||||||
Are you sure you want to unpublish the app <b>{selectedApp?.name}</b>?
|
Are you sure you want to unpublish the app <b>{selectedApp?.name}</b>?
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
|
|
||||||
<Modal bind:this={updateAppModal} padding={false} width="600px">
|
|
||||||
<UpdateAppModal
|
|
||||||
app={{
|
|
||||||
name: $appStore.name,
|
|
||||||
url: $appStore.url,
|
|
||||||
icon: $appStore.icon,
|
|
||||||
appId: $appStore.appId,
|
|
||||||
}}
|
|
||||||
onUpdateComplete={async () => {
|
|
||||||
await initialiseApp()
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<RevertModal bind:this={revertModal} />
|
<RevertModal bind:this={revertModal} />
|
||||||
<VersionModal hideIcon bind:this={versionModal} />
|
<VersionModal hideIcon bind:this={versionModal} />
|
||||||
|
|
||||||
|
|
|
@ -1,151 +0,0 @@
|
||||||
<script>
|
|
||||||
import { writable, get as svelteGet } from "svelte/store"
|
|
||||||
import {
|
|
||||||
notifications,
|
|
||||||
Input,
|
|
||||||
ModalContent,
|
|
||||||
Layout,
|
|
||||||
Label,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { appsStore } from "stores/portal"
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
import { createValidationStore } from "helpers/validation/yup"
|
|
||||||
import * as appValidation from "helpers/validation/yup/app"
|
|
||||||
import EditableIcon from "../common/EditableIcon.svelte"
|
|
||||||
|
|
||||||
export let app
|
|
||||||
export let onUpdateComplete
|
|
||||||
|
|
||||||
$: appIdParts = app.appId ? app.appId?.split("_") : []
|
|
||||||
$: appId = appIdParts.slice(-1)[0]
|
|
||||||
|
|
||||||
const values = writable({
|
|
||||||
name: app.name,
|
|
||||||
url: app.url,
|
|
||||||
iconName: app.icon?.name,
|
|
||||||
iconColor: app.icon?.color,
|
|
||||||
})
|
|
||||||
const validation = createValidationStore()
|
|
||||||
|
|
||||||
$: {
|
|
||||||
const { url } = $values
|
|
||||||
|
|
||||||
validation.check({
|
|
||||||
...$values,
|
|
||||||
url: url?.[0] === "/" ? url.substring(1, url.length) : url,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const setupValidation = async () => {
|
|
||||||
const applications = svelteGet(appsStore).apps
|
|
||||||
appValidation.name(validation, {
|
|
||||||
apps: applications,
|
|
||||||
currentApp: {
|
|
||||||
...app,
|
|
||||||
appId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
appValidation.url(validation, {
|
|
||||||
apps: applications,
|
|
||||||
currentApp: {
|
|
||||||
...app,
|
|
||||||
appId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
// init validation
|
|
||||||
const { url } = $values
|
|
||||||
validation.check({
|
|
||||||
...$values,
|
|
||||||
url: url?.[0] === "/" ? url.substring(1, url.length) : url,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateApp() {
|
|
||||||
try {
|
|
||||||
await appsStore.save(app.appId, {
|
|
||||||
name: $values.name?.trim(),
|
|
||||||
url: $values.url?.trim(),
|
|
||||||
icon: {
|
|
||||||
name: $values.iconName,
|
|
||||||
color: $values.iconColor,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if (typeof onUpdateComplete == "function") {
|
|
||||||
onUpdateComplete()
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error)
|
|
||||||
notifications.error("Error updating app")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolveAppUrl = (template, name) => {
|
|
||||||
let parsedName
|
|
||||||
const resolvedName = resolveAppName(null, name)
|
|
||||||
parsedName = resolvedName ? resolvedName.toLowerCase() : ""
|
|
||||||
const parsedUrl = parsedName ? parsedName.replace(/\s+/g, "-") : ""
|
|
||||||
return encodeURI(parsedUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolveAppName = (template, name) => {
|
|
||||||
if (template && !name) {
|
|
||||||
return template.name
|
|
||||||
}
|
|
||||||
return name ? name.trim() : null
|
|
||||||
}
|
|
||||||
|
|
||||||
const tidyUrl = url => {
|
|
||||||
if (url && !url.startsWith("/")) {
|
|
||||||
url = `/${url}`
|
|
||||||
}
|
|
||||||
$values.url = url === "" ? null : url
|
|
||||||
}
|
|
||||||
|
|
||||||
const nameToUrl = appName => {
|
|
||||||
let resolvedUrl = resolveAppUrl(null, appName)
|
|
||||||
tidyUrl(resolvedUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateIcon = e => {
|
|
||||||
const { name, color } = e.detail
|
|
||||||
$values.iconColor = color
|
|
||||||
$values.iconName = name
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(setupValidation)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<ModalContent
|
|
||||||
title="Edit name and URL"
|
|
||||||
confirmText="Save"
|
|
||||||
onConfirm={updateApp}
|
|
||||||
disabled={!$validation.valid}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
bind:value={$values.name}
|
|
||||||
error={$validation.touched.name && $validation.errors.name}
|
|
||||||
on:blur={() => ($validation.touched.name = true)}
|
|
||||||
on:change={nameToUrl($values.name)}
|
|
||||||
label="Name"
|
|
||||||
/>
|
|
||||||
<Layout noPadding gap="XS">
|
|
||||||
<Label>Icon</Label>
|
|
||||||
<EditableIcon
|
|
||||||
{app}
|
|
||||||
size="XL"
|
|
||||||
name={$values.iconName}
|
|
||||||
color={$values.iconColor}
|
|
||||||
on:change={updateIcon}
|
|
||||||
/>
|
|
||||||
</Layout>
|
|
||||||
<Input
|
|
||||||
bind:value={$values.url}
|
|
||||||
error={$validation.touched.url && $validation.errors.url}
|
|
||||||
on:blur={() => ($validation.touched.url = true)}
|
|
||||||
on:change={tidyUrl($values.url)}
|
|
||||||
label="URL"
|
|
||||||
placeholder={$values.url
|
|
||||||
? $values.url
|
|
||||||
: `/${resolveAppUrl(null, $values.name)}`}
|
|
||||||
/>
|
|
||||||
</ModalContent>
|
|
|
@ -19,11 +19,10 @@ export const name = (validation, { apps, currentApp } = { apps: [] }) => {
|
||||||
// exit early, above validator will fail
|
// exit early, above validator will fail
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if (currentApp) {
|
|
||||||
// filter out the current app if present
|
|
||||||
apps = apps.filter(app => app.appId !== currentApp.appId)
|
|
||||||
}
|
|
||||||
return !apps
|
return !apps
|
||||||
|
.filter(app => {
|
||||||
|
return app.appId !== currentApp?.appId
|
||||||
|
})
|
||||||
.map(app => app.name)
|
.map(app => app.name)
|
||||||
.some(appName => appName.toLowerCase() === value.toLowerCase())
|
.some(appName => appName.toLowerCase() === value.toLowerCase())
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
import { TOUR_KEYS } from "components/portal/onboarding/tours.js"
|
import { TOUR_KEYS } from "components/portal/onboarding/tours.js"
|
||||||
import PreviewOverlay from "./_components/PreviewOverlay.svelte"
|
import PreviewOverlay from "./_components/PreviewOverlay.svelte"
|
||||||
import EnterpriseBasicTrialModal from "components/portal/onboarding/EnterpriseBasicTrialModal.svelte"
|
import EnterpriseBasicTrialModal from "components/portal/onboarding/EnterpriseBasicTrialModal.svelte"
|
||||||
|
import UpdateAppTopNav from "components/common/UpdateAppTopNav.svelte"
|
||||||
|
|
||||||
export let application
|
export let application
|
||||||
|
|
||||||
|
@ -164,7 +165,11 @@
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
<div class="topcenternav">
|
<div class="topcenternav">
|
||||||
<Heading size="XS">{$appStore.name}</Heading>
|
<div class="app-name">
|
||||||
|
<UpdateAppTopNav {application}>
|
||||||
|
<Heading noPadding size="XS">{$appStore.name}</Heading>
|
||||||
|
</UpdateAppTopNav>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="toprightnav">
|
<div class="toprightnav">
|
||||||
<span>
|
<span>
|
||||||
|
@ -253,7 +258,6 @@
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
padding: 0px var(--spacing-m);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.topleftnav {
|
.topleftnav {
|
||||||
|
|
|
@ -1,30 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import {
|
import { Layout, Divider, Heading, Body } from "@budibase/bbui"
|
||||||
Layout,
|
import UpdateAppForm from "components/common/UpdateAppForm.svelte"
|
||||||
Divider,
|
|
||||||
Heading,
|
|
||||||
Body,
|
|
||||||
Button,
|
|
||||||
Label,
|
|
||||||
Modal,
|
|
||||||
Icon,
|
|
||||||
} from "@budibase/bbui"
|
|
||||||
import { AppStatus } from "constants"
|
|
||||||
import { appStore, initialise } from "stores/builder"
|
|
||||||
import { appsStore } from "stores/portal"
|
|
||||||
import UpdateAppModal from "components/start/UpdateAppModal.svelte"
|
|
||||||
import { API } from "api"
|
|
||||||
|
|
||||||
let updatingModal
|
|
||||||
|
|
||||||
$: filteredApps = $appsStore.apps.filter(app => app.devId == $appStore.appId)
|
|
||||||
$: app = filteredApps.length ? filteredApps[0] : {}
|
|
||||||
$: appDeployed = app?.status === AppStatus.DEPLOYED
|
|
||||||
|
|
||||||
const initialiseApp = async () => {
|
|
||||||
const applicationPkg = await API.fetchAppPackage($appStore.appId)
|
|
||||||
await initialise(applicationPkg)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Layout noPadding>
|
<Layout noPadding>
|
||||||
|
@ -33,61 +9,5 @@
|
||||||
<Body>Edit your app's name and URL</Body>
|
<Body>Edit your app's name and URL</Body>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
<UpdateAppForm />
|
||||||
<Layout noPadding gap="XXS">
|
|
||||||
<Label size="L">Name</Label>
|
|
||||||
<Body>{$appStore?.name}</Body>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<Layout noPadding gap="XS">
|
|
||||||
<Label size="L">Icon</Label>
|
|
||||||
<div class="icon">
|
|
||||||
<Icon
|
|
||||||
size="L"
|
|
||||||
name={$appStore?.icon?.name || "Apps"}
|
|
||||||
color={$appStore?.icon?.color}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
|
|
||||||
<Layout noPadding gap="XXS">
|
|
||||||
<Label size="L">URL</Label>
|
|
||||||
<Body>{$appStore.url}</Body>
|
|
||||||
</Layout>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<Button
|
|
||||||
cta
|
|
||||||
on:click={() => {
|
|
||||||
updatingModal.show()
|
|
||||||
}}
|
|
||||||
disabled={appDeployed}
|
|
||||||
tooltip={appDeployed
|
|
||||||
? "You must unpublish your app to make changes"
|
|
||||||
: null}
|
|
||||||
>
|
|
||||||
Edit
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Layout>
|
|
||||||
|
|
||||||
<Modal bind:this={updatingModal} padding={false} width="600px">
|
|
||||||
<UpdateAppModal
|
|
||||||
app={{
|
|
||||||
name: $appStore.name,
|
|
||||||
url: $appStore.url,
|
|
||||||
icon: $appStore.icon,
|
|
||||||
appId: $appStore.appId,
|
|
||||||
}}
|
|
||||||
onUpdateComplete={async () => {
|
|
||||||
await initialiseApp()
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.icon {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -131,7 +131,7 @@ export class AppsStore extends BudiStore {
|
||||||
if (updatedAppIndex !== -1) {
|
if (updatedAppIndex !== -1) {
|
||||||
let updatedApp = state.apps[updatedAppIndex]
|
let updatedApp = state.apps[updatedAppIndex]
|
||||||
updatedApp = { ...updatedApp, ...value }
|
updatedApp = { ...updatedApp, ...value }
|
||||||
state.apps = state.apps.splice(updatedAppIndex, 1, updatedApp)
|
state.apps.splice(updatedAppIndex, 1, updatedApp)
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
})
|
})
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
const context = getContext("context")
|
const context = getContext("context")
|
||||||
const component = getContext("component")
|
const component = getContext("component")
|
||||||
|
const { environmentStore } = getContext("sdk")
|
||||||
const {
|
const {
|
||||||
styleable,
|
styleable,
|
||||||
API,
|
API,
|
||||||
|
@ -182,6 +183,7 @@
|
||||||
notifySuccess={notificationStore.actions.success}
|
notifySuccess={notificationStore.actions.success}
|
||||||
notifyError={notificationStore.actions.error}
|
notifyError={notificationStore.actions.error}
|
||||||
buttons={enrichedButtons}
|
buttons={enrichedButtons}
|
||||||
|
isCloud={$environmentStore.cloud}
|
||||||
on:rowclick={e => onRowClick?.({ row: e.detail })}
|
on:rowclick={e => onRowClick?.({ row: e.detail })}
|
||||||
on:columnresize={onColumnResize}
|
on:columnresize={onColumnResize}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
let fieldState
|
let fieldState
|
||||||
let fieldApi
|
let fieldApi
|
||||||
|
|
||||||
const { API, notificationStore } = getContext("sdk")
|
const { API, notificationStore, environmentStore } = getContext("sdk")
|
||||||
const formContext = getContext("form")
|
const formContext = getContext("form")
|
||||||
const BYTES_IN_MB = 1000000
|
const BYTES_IN_MB = 1000000
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@
|
||||||
error={fieldState.error}
|
error={fieldState.error}
|
||||||
on:change={handleChange}
|
on:change={handleChange}
|
||||||
{processFiles}
|
{processFiles}
|
||||||
{handleFileTooLarge}
|
handleFileTooLarge={$environmentStore.cloud ? handleFileTooLarge : null}
|
||||||
{handleTooManyFiles}
|
{handleTooManyFiles}
|
||||||
{maximum}
|
{maximum}
|
||||||
{extensions}
|
{extensions}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
export let schema
|
export let schema
|
||||||
export let maximum
|
export let maximum
|
||||||
|
|
||||||
const { API, notifications } = getContext("grid")
|
const { API, notifications, props } = getContext("grid")
|
||||||
const imageExtensions = ["png", "tiff", "gif", "raw", "jpg", "jpeg"]
|
const imageExtensions = ["png", "tiff", "gif", "raw", "jpg", "jpeg"]
|
||||||
|
|
||||||
let isOpen = false
|
let isOpen = false
|
||||||
|
@ -106,7 +106,7 @@
|
||||||
on:change={e => onChange(e.detail)}
|
on:change={e => onChange(e.detail)}
|
||||||
maximum={maximum || schema.constraints?.length?.maximum}
|
maximum={maximum || schema.constraints?.length?.maximum}
|
||||||
{processFiles}
|
{processFiles}
|
||||||
{handleFileTooLarge}
|
handleFileTooLarge={$props.isCloud ? handleFileTooLarge : null}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</GridPopover>
|
</GridPopover>
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
export let notifySuccess = null
|
export let notifySuccess = null
|
||||||
export let notifyError = null
|
export let notifyError = null
|
||||||
export let buttons = null
|
export let buttons = null
|
||||||
|
export let isCloud = null
|
||||||
|
|
||||||
// Unique identifier for DOM nodes inside this instance
|
// Unique identifier for DOM nodes inside this instance
|
||||||
const gridID = `grid-${Math.random().toString().slice(2)}`
|
const gridID = `grid-${Math.random().toString().slice(2)}`
|
||||||
|
@ -108,6 +109,7 @@
|
||||||
notifySuccess,
|
notifySuccess,
|
||||||
notifyError,
|
notifyError,
|
||||||
buttons,
|
buttons,
|
||||||
|
isCloud,
|
||||||
})
|
})
|
||||||
$: minHeight =
|
$: minHeight =
|
||||||
Padding + SmallRowHeight + $rowHeight + (showControls ? ControlsHeight : 0)
|
Padding + SmallRowHeight + $rowHeight + (showControls ? ControlsHeight : 0)
|
||||||
|
|
|
@ -242,7 +242,7 @@ export async function outputProcessing<T extends Row[] | Row>(
|
||||||
}
|
}
|
||||||
return attachment
|
return attachment
|
||||||
}
|
}
|
||||||
if (typeof row[property] === "string") {
|
if (typeof row[property] === "string" && row[property].length) {
|
||||||
row[property] = JSON.parse(row[property])
|
row[property] = JSON.parse(row[property])
|
||||||
}
|
}
|
||||||
if (Array.isArray(row[property])) {
|
if (Array.isArray(row[property])) {
|
||||||
|
|
Loading…
Reference in New Issue