add option to change icon / colour

This commit is contained in:
Peter Clement 2021-12-08 18:51:24 +00:00
parent 8f9bce04bc
commit 14213006e0
9 changed files with 237 additions and 98 deletions

View File

@ -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"

View File

@ -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}
>

View File

@ -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

View File

@ -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);

View File

@ -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>

View File

@ -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}

View File

@ -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"

View File

@ -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>

View File

@ -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,
}
}