add option to change icon / colour
This commit is contained in:
parent
8f9bce04bc
commit
14213006e0
|
@ -6,6 +6,7 @@
|
|||
import { createEventDispatcher } from "svelte"
|
||||
import clickOutside from "../../Actions/click_outside"
|
||||
import Search from "./Search.svelte"
|
||||
import Icon from "../../Icon/Icon.svelte"
|
||||
|
||||
export let id = null
|
||||
export let disabled = false
|
||||
|
@ -159,17 +160,21 @@
|
|||
>
|
||||
{#if getOptionIcon(option, idx)}
|
||||
<span class="icon-Padding">
|
||||
<img
|
||||
src={getOptionIcon(option, idx)}
|
||||
alt="icon"
|
||||
width="20"
|
||||
height="15"
|
||||
/>
|
||||
{#if getOptionIcon(option, idx).includes("assets")}
|
||||
<img
|
||||
src={getOptionIcon(option, idx)}
|
||||
alt="icon"
|
||||
width="20"
|
||||
height="15"
|
||||
/>
|
||||
{:else}<Icon name={getOptionIcon(option, idx)} />{/if}
|
||||
</span>
|
||||
{/if}
|
||||
{#if getOptionLabel(option, idx)}
|
||||
<span class="spectrum-Menu-itemLabel">
|
||||
{getOptionLabel(option, idx)}
|
||||
</span>
|
||||
{/if}
|
||||
<span class="spectrum-Menu-itemLabel">
|
||||
{getOptionLabel(option, idx)}
|
||||
</span>
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
|
||||
focusable="false"
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
export let disabled = false
|
||||
export let id = null
|
||||
export let updateOnChange = true
|
||||
|
||||
export let quiet = false
|
||||
const dispatch = createEventDispatcher()
|
||||
let focus = false
|
||||
|
||||
|
@ -41,6 +41,7 @@
|
|||
<div class="spectrum-Search" class:is-disabled={disabled}>
|
||||
<div
|
||||
class="spectrum-Textfield"
|
||||
class:spectrum-Textfield--quiet={quiet}
|
||||
class:is-focused={focus}
|
||||
class:is-disabled={disabled}
|
||||
>
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
export let placeholder = null
|
||||
export let disabled = false
|
||||
export let updateOnChange = true
|
||||
export let quiet = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const onChange = e => {
|
||||
|
@ -23,6 +24,7 @@
|
|||
{disabled}
|
||||
{value}
|
||||
{placeholder}
|
||||
{quiet}
|
||||
on:change={onChange}
|
||||
on:click
|
||||
on:input
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import { gradient } from "actions"
|
||||
import { Heading, Button, Icon, ActionMenu, MenuItem } from "@budibase/bbui"
|
||||
import { apps } from "stores/portal"
|
||||
|
||||
export let app
|
||||
export let exportApp
|
||||
|
@ -10,14 +10,23 @@
|
|||
export let deleteApp
|
||||
export let unpublishApp
|
||||
export let releaseLock
|
||||
export let editIcon
|
||||
$: color = $apps.filter(filtered_app => app?.appId === filtered_app.appId)[0]
|
||||
.icon?.color
|
||||
$: name = $apps.filter(filtered_app => app?.appId === filtered_app.appId)[0]
|
||||
.icon?.name
|
||||
</script>
|
||||
|
||||
<div class="title">
|
||||
<div class="preview" use:gradient={{ seed: app.name }} />
|
||||
<div class="name" on:click={() => editApp(app)}>
|
||||
<Heading size="XS">
|
||||
{app.name}
|
||||
</Heading>
|
||||
<div style="display: flex;">
|
||||
<div style="color: {color || ''}">
|
||||
<Icon size="XL" name={name || "Apps"} />
|
||||
</div>
|
||||
<div class="name" on:click={() => editApp(app)}>
|
||||
<Heading size="XS">
|
||||
{app.name}
|
||||
</Heading>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="desktop" />
|
||||
|
@ -53,15 +62,11 @@
|
|||
<MenuItem on:click={() => updateApp(app)} icon="Edit">Edit</MenuItem>
|
||||
<MenuItem on:click={() => deleteApp(app)} icon="Delete">Delete</MenuItem>
|
||||
{/if}
|
||||
<MenuItem on:click={() => editIcon(app)} icon="Edit">Edit Icon</MenuItem>
|
||||
</ActionMenu>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.preview {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
border-radius: var(--border-radius-s);
|
||||
}
|
||||
.name {
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
|
@ -70,6 +75,7 @@
|
|||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
margin-left: calc(1.5 * var(--spacing-xl));
|
||||
}
|
||||
.title :global(h1:hover) {
|
||||
color: var(--spectrum-global-color-blue-600);
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
<script>
|
||||
import {
|
||||
Input,
|
||||
ModalContent,
|
||||
Body,
|
||||
Modal,
|
||||
Icon,
|
||||
ColorPicker,
|
||||
Label,
|
||||
} from "@budibase/bbui"
|
||||
import { apps } from "stores/portal"
|
||||
|
||||
export let app
|
||||
let modal
|
||||
let dirty
|
||||
let selectedIcon
|
||||
let selectedColor
|
||||
|
||||
let iconsList = [
|
||||
{ icon: "Actions", color: "" },
|
||||
{ icon: "Algorithm", color: "" },
|
||||
{ icon: "App", color: "" },
|
||||
{ icon: "Briefcase", color: "" },
|
||||
{ icon: "Money", color: "" },
|
||||
{ icon: "ShoppingCart", color: "" },
|
||||
{ icon: "Form", color: "" },
|
||||
{ icon: "Help", color: "" },
|
||||
{ icon: "Monitoring", color: "" },
|
||||
{ icon: "Sandbox", color: "" },
|
||||
{ icon: "Project", color: "" },
|
||||
{ icon: "Organisations", color: "" },
|
||||
{ icon: "Magnify", color: "" },
|
||||
{ icon: "Launch", color: "" },
|
||||
]
|
||||
export const show = () => {
|
||||
modal.show()
|
||||
}
|
||||
export const hide = () => {
|
||||
modal.hide()
|
||||
}
|
||||
|
||||
const onCancel = () => {
|
||||
hide()
|
||||
}
|
||||
|
||||
const onShow = () => {
|
||||
dirty = false
|
||||
}
|
||||
|
||||
const changeColor = val => {
|
||||
selectedColor = val
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
await apps.updateIcon(app.instance._id, {
|
||||
name: selectedIcon,
|
||||
color: selectedColor,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:this={modal} on:hide={onCancel} on:show={onShow}>
|
||||
<ModalContent
|
||||
title={"Edit Icon"}
|
||||
confirmText={"Save"}
|
||||
onConfirm={() => save()}
|
||||
>
|
||||
<div class="scrollable-icons">
|
||||
<div class="title-spacing">
|
||||
<Label>Select an Icon:</Label>
|
||||
</div>
|
||||
<div class="grid">
|
||||
{#each iconsList as item}
|
||||
<div
|
||||
class="icon-item"
|
||||
style="color: {item.icon === selectedIcon ? selectedColor : ''}"
|
||||
on:click={() => (selectedIcon = item.icon)}
|
||||
>
|
||||
<Icon name={item.icon} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="color-selection">
|
||||
<div>
|
||||
<Label>Select a Color:</Label>
|
||||
</div>
|
||||
<div class="color-selection-item">
|
||||
<ColorPicker
|
||||
value={selectedColor}
|
||||
on:change={e => changeColor(e.detail)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.scrollable-icons {
|
||||
overflow-y: auto;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-gap: 20px;
|
||||
grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
.color-selection {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.color-selection-item {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.title-spacing {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.icon-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
|
@ -1,15 +1,9 @@
|
|||
<script>
|
||||
import { writable, get as svelteGet } from "svelte/store"
|
||||
import {
|
||||
notifications,
|
||||
Input,
|
||||
ModalContent,
|
||||
Dropzone,
|
||||
Body,
|
||||
Checkbox,
|
||||
} from "@budibase/bbui"
|
||||
|
||||
import { notifications, Input, ModalContent, Dropzone } from "@budibase/bbui"
|
||||
import { store, automationStore, hostingStore } from "builderStore"
|
||||
import { admin, auth } from "stores/portal"
|
||||
import { admin, auth, users } from "stores/portal"
|
||||
import { string, mixed, object } from "yup"
|
||||
import api, { get, post } from "builderStore/api"
|
||||
import analytics, { Events } from "analytics"
|
||||
|
@ -21,7 +15,6 @@
|
|||
|
||||
export let template
|
||||
export let inline
|
||||
|
||||
const values = writable({ name: null })
|
||||
const errors = writable({})
|
||||
const touched = writable({})
|
||||
|
@ -147,16 +140,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
function getModalTitle() {
|
||||
let title = "Create App"
|
||||
if (template.fromFile) {
|
||||
title = "Import App"
|
||||
} else if (template.key) {
|
||||
title = "Create app from template"
|
||||
}
|
||||
return title
|
||||
}
|
||||
|
||||
async function onCancel() {
|
||||
template = null
|
||||
await auth.setInitInfo({})
|
||||
|
@ -187,7 +170,7 @@
|
|||
</ModalContent>
|
||||
{:else}
|
||||
<ModalContent
|
||||
title={getModalTitle()}
|
||||
title={"Name your app"}
|
||||
confirmText={template?.fromFile ? "Import app" : "Create app"}
|
||||
onConfirm={createNewApp}
|
||||
onCancel={inline ? onCancel : null}
|
||||
|
@ -207,16 +190,12 @@
|
|||
}}
|
||||
/>
|
||||
{/if}
|
||||
<Body size="S">
|
||||
Give your new app a name, and choose which groups have access (paid plans
|
||||
only).
|
||||
</Body>
|
||||
<Input
|
||||
bind:value={$values.name}
|
||||
error={$touched.name && $errors.name}
|
||||
on:blur={() => ($touched.name = true)}
|
||||
label="Name"
|
||||
placeholder={`${$auth.user.firstName}'s first app`}
|
||||
/>
|
||||
<Checkbox label="Group access" disabled value={true} text="All users" />
|
||||
</ModalContent>
|
||||
{/if}
|
||||
|
|
|
@ -1,46 +1,10 @@
|
|||
<script>
|
||||
import { Heading, Layout, Icon, Body } from "@budibase/bbui"
|
||||
import Spinner from "components/common/Spinner.svelte"
|
||||
import api from "builderStore/api"
|
||||
import { Heading, Layout, Icon } from "@budibase/bbui"
|
||||
|
||||
export let onSelect
|
||||
|
||||
async function fetchTemplates() {
|
||||
const response = await api.get("/api/templates?type=app")
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
let templatesPromise = fetchTemplates()
|
||||
</script>
|
||||
|
||||
<Layout gap="XS" noPadding>
|
||||
{#await templatesPromise}
|
||||
<div class="spinner-container">
|
||||
<Spinner size="30" />
|
||||
</div>
|
||||
{:then templates}
|
||||
{#if templates?.length > 0}
|
||||
<Body size="M">Select a template below, or start from scratch.</Body>
|
||||
{:else}
|
||||
<Body size="M">Start your app from scratch below.</Body>
|
||||
{/if}
|
||||
<div class="templates">
|
||||
{#each templates as template}
|
||||
<div class="template" on:click={() => onSelect(template)}>
|
||||
<div
|
||||
class="background-icon"
|
||||
style={`background: ${template.background};`}
|
||||
>
|
||||
<Icon name={template.icon} />
|
||||
</div>
|
||||
<Heading size="XS">{template.name}</Heading>
|
||||
<p class="detail">{template?.category?.toUpperCase()}</p>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:catch err}
|
||||
<h1 style="color:red">{err}</h1>
|
||||
{/await}
|
||||
<div class="template start-from-scratch" on:click={() => onSelect(null)}>
|
||||
<div
|
||||
class="background-icon"
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
import Spinner from "components/common/Spinner.svelte"
|
||||
import CreateAppModal from "components/start/CreateAppModal.svelte"
|
||||
import UpdateAppModal from "components/start/UpdateAppModal.svelte"
|
||||
import ChooseIconModal from "components/start/ChooseIconModal.svelte"
|
||||
|
||||
import { store, automationStore } from "builderStore"
|
||||
import api, { del, post, get } from "builderStore/api"
|
||||
import { onMount } from "svelte"
|
||||
|
@ -34,6 +36,7 @@
|
|||
let updatingModal
|
||||
let deletionModal
|
||||
let unpublishModal
|
||||
let iconModal
|
||||
let creatingApp = false
|
||||
let loaded = false
|
||||
let searchTerm = ""
|
||||
|
@ -170,6 +173,11 @@
|
|||
$goto(`../../app/${app.devId}`)
|
||||
}
|
||||
|
||||
const editIcon = app => {
|
||||
selectedApp = app
|
||||
iconModal.show()
|
||||
}
|
||||
|
||||
const exportApp = app => {
|
||||
const id = app.deployed ? app.prodId : app.devId
|
||||
const appName = encodeURIComponent(app.name)
|
||||
|
@ -279,7 +287,7 @@
|
|||
</script>
|
||||
|
||||
<Page wide>
|
||||
<Layout gap="S" noPadding>
|
||||
<Layout noPadding>
|
||||
<div class="title">
|
||||
<Heading size="S">Welcome to Budibase</Heading>
|
||||
|
||||
|
@ -287,32 +295,45 @@
|
|||
{#if cloud}
|
||||
<Button secondary on:click={initiateAppsExport}>Export apps</Button>
|
||||
{/if}
|
||||
<Button secondary on:click={initiateAppImport}>Import app</Button>
|
||||
<Button cta on:click={initiateAppCreation}>Create app</Button>
|
||||
<Button icon="Import" quiet secondary on:click={initiateAppImport}
|
||||
>Import app</Button
|
||||
>
|
||||
<Button icon="Add" cta on:click={initiateAppCreation}>Create app</Button
|
||||
>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
<Body size="XS">Manage your apps and get a head start with templates</Body>
|
||||
|
||||
<div class="title-text">
|
||||
<Body size="XS">Manage your apps and get a head start with templates</Body
|
||||
>
|
||||
</div>
|
||||
<Detail>Quick Start Templates</Detail>
|
||||
<div class="grid">
|
||||
{#each templates as val}
|
||||
<div class="template-card">
|
||||
{#each templates as item}
|
||||
<div
|
||||
on:click={() => {
|
||||
template = item
|
||||
creationModal.show()
|
||||
creatingApp = true
|
||||
}}
|
||||
class="template-card"
|
||||
>
|
||||
<div class="card-body">
|
||||
<div style="color: {val.background}" class="iconAlign">
|
||||
<div style="color: {item.background}" class="iconAlign">
|
||||
<svg
|
||||
width="26px"
|
||||
height="26px"
|
||||
class="spectrum-Icon"
|
||||
style="color:{val.background};"
|
||||
style="color:{item.background};"
|
||||
focusable="false"
|
||||
>
|
||||
<use xlink:href="#spectrum-icon-18-{val.icon}" />
|
||||
<use xlink:href="#spectrum-icon-18-{item.icon}" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="iconAlign">
|
||||
<Body weight="900" size="XS">{val.name}</Body>
|
||||
<Body weight="900" size="XS">{item.name}</Body>
|
||||
<div style="font-size: 10px;">
|
||||
<Body size="XS">{val.category.toUpperCase()}</Body>
|
||||
<Body size="XS">{item.category.toUpperCase()}</Body>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -326,6 +347,7 @@
|
|||
<div class="filter">
|
||||
<div class="select">
|
||||
<Select
|
||||
quiet
|
||||
autoWidth
|
||||
bind:value={sortBy}
|
||||
placeholder={null}
|
||||
|
@ -336,7 +358,7 @@
|
|||
]}
|
||||
/>
|
||||
<div class="desktop-search">
|
||||
<Search placeholder="Search" bind:value={searchTerm} />
|
||||
<Search quiet placeholder="Search" bind:value={searchTerm} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -348,6 +370,7 @@
|
|||
<svelte:component
|
||||
this={AppRow}
|
||||
{releaseLock}
|
||||
{editIcon}
|
||||
{app}
|
||||
{unpublishApp}
|
||||
{viewApp}
|
||||
|
@ -374,6 +397,7 @@
|
|||
{/if}
|
||||
</Layout>
|
||||
</Page>
|
||||
|
||||
<Modal
|
||||
bind:this={creationModal}
|
||||
padding={false}
|
||||
|
@ -409,6 +433,7 @@
|
|||
</ConfirmDialog>
|
||||
|
||||
<UpdateAppModal app={selectedApp} bind:this={updatingModal} />
|
||||
<ChooseIconModal app={selectedApp} bind:this={iconModal} />
|
||||
|
||||
<style>
|
||||
.title,
|
||||
|
@ -440,6 +465,11 @@
|
|||
border-radius: var(--border-radius-s);
|
||||
margin-bottom: var(--spacing-m);
|
||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
margin-top: calc(var(--spacing-xl) * -1);
|
||||
}
|
||||
|
||||
.card-body {
|
||||
|
@ -459,8 +489,8 @@
|
|||
|
||||
.select {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: 10px;
|
||||
grid-template-columns: auto auto;
|
||||
grid-gap: 50px;
|
||||
}
|
||||
.filter :global(.spectrum-ActionGroup) {
|
||||
flex-wrap: nowrap;
|
||||
|
@ -509,4 +539,8 @@
|
|||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.template-card:hover {
|
||||
background: var(--spectrum-alias-background-color-tertiary);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -65,6 +65,27 @@ export function createAppStore() {
|
|||
}
|
||||
}
|
||||
|
||||
async function updateIcon(appId, icon) {
|
||||
const response = await api.put(`/api/applications/${appId}`, { icon })
|
||||
if (response.status === 200) {
|
||||
store.update(state => {
|
||||
const updatedAppIndex = state.findIndex(
|
||||
app => app.instance._id === appId
|
||||
)
|
||||
|
||||
if (updatedAppIndex !== -1) {
|
||||
const updatedApp = state[updatedAppIndex]
|
||||
updatedApp.icon = icon
|
||||
state.apps = state.splice(updatedAppIndex, 1, updatedApp)
|
||||
}
|
||||
|
||||
return state
|
||||
})
|
||||
} else {
|
||||
throw new Error("Error updating icon")
|
||||
}
|
||||
}
|
||||
|
||||
async function update(appId, name) {
|
||||
const response = await api.put(`/api/applications/${appId}`, { name })
|
||||
if (response.status === 200) {
|
||||
|
@ -88,6 +109,7 @@ export function createAppStore() {
|
|||
subscribe: store.subscribe,
|
||||
load,
|
||||
update,
|
||||
updateIcon,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue