mirror of https://github.com/Budibase/budibase.git
152 changed files with 3521 additions and 3904 deletions
@ -0,0 +1,18 @@ |
|||
# Number of days of inactivity before an issue becomes stale |
|||
daysUntilStale: 60 |
|||
# Number of days of inactivity before a stale issue is closed |
|||
daysUntilClose: 7 |
|||
# Issues with these labels will never be considered stale |
|||
exemptLabels: |
|||
- pinned |
|||
- security |
|||
- roadmap |
|||
# Label to use when marking an issue as stale |
|||
staleLabel: stale |
|||
# Comment to post when marking an issue as stale. Set to `false` to disable |
|||
markComment: > |
|||
This issue has been automatically marked as stale because it has not had |
|||
recent activity. It will be closed if no further activity occurs. Thank you |
|||
for your contributions. |
|||
# Comment to post when closing a stale issue. Set to `false` to disable |
|||
closeComment: false |
|||
@ -0,0 +1,11 @@ |
|||
{ |
|||
"editor.formatOnSave": true, |
|||
"eslint.format.enable": true, |
|||
"editor.codeActionsOnSave": { |
|||
"source.fixAll": true |
|||
}, |
|||
"[svelte]": { |
|||
"editor.defaultFormatter": "JamesBirtles.svelte-vscode" |
|||
}, |
|||
"editor.defaultFormatter": "esbenp.prettier-vscode" |
|||
} |
|||
|
After Width: | Height: | Size: 132 KiB |
@ -0,0 +1,24 @@ |
|||
import * as Sentry from "@sentry/browser" |
|||
import posthog from "posthog-js" |
|||
|
|||
function activate() { |
|||
Sentry.init({ dsn: process.env.SENTRY_DSN }) |
|||
posthog.init(process.env.POSTHOG_TOKEN, { |
|||
api_host: process.env.POSTHOG_URL, |
|||
}) |
|||
} |
|||
|
|||
function captureException(err) { |
|||
Sentry.captureException(err) |
|||
} |
|||
|
|||
function captureEvent(event) { |
|||
if (process.env.NODE_ENV !== "production") return |
|||
posthog.capture(event) |
|||
} |
|||
|
|||
export default { |
|||
activate, |
|||
captureException, |
|||
captureEvent, |
|||
} |
|||
@ -1,116 +0,0 @@ |
|||
<script> |
|||
import { onMount } from "svelte" |
|||
// import { HsvPicker } from "svelte-color-picker" |
|||
|
|||
// export let initialValue = "#ffffff" |
|||
export let onChange = color => {} |
|||
export let open = false |
|||
let value = "#ffffff" |
|||
|
|||
let _justMounted = true //see onColorChange |
|||
let pickerHeight = 275 |
|||
let colorPreview |
|||
let pickerTopPosition = null |
|||
|
|||
function rbgaToHexa({ r, g, b, a }) { |
|||
r = r.toString(16) |
|||
g = g.toString(16) |
|||
b = b.toString(16) |
|||
a = Math.round(a * 255).toString(16) |
|||
|
|||
if (r.length == 1) r = "0" + r |
|||
if (g.length == 1) g = "0" + g |
|||
if (b.length == 1) b = "0" + b |
|||
if (a.length == 1) a = "0" + a |
|||
|
|||
return "#" + r + g + b + a |
|||
} |
|||
|
|||
function onColourChange(rgba) { |
|||
value = rbgaToHexa(rgba.detail) |
|||
|
|||
//Hack: so that color change doesn't fire onMount |
|||
if (!_justMounted) { |
|||
// onChange(value) |
|||
} |
|||
_justMounted = false |
|||
} |
|||
|
|||
function toggleColorpicker(isOpen) { |
|||
if (isOpen) { |
|||
const { |
|||
y: previewYPosition, |
|||
height: previewHeight, |
|||
} = colorPreview.getBoundingClientRect() |
|||
|
|||
let wiggleRoom = window.innerHeight - previewYPosition |
|||
let displayTop = wiggleRoom < pickerHeight |
|||
|
|||
if (displayTop) { |
|||
pickerTopPosition = previewYPosition - (pickerHeight - window.scrollY) |
|||
} else { |
|||
pickerTopPosition = null |
|||
} |
|||
} |
|||
open = isOpen |
|||
} |
|||
|
|||
$: style = open ? "display: block;" : "display: none;" |
|||
$: pickerStyle = pickerTopPosition ? `top: ${pickerTopPosition}px;` : "" |
|||
</script> |
|||
|
|||
<div |
|||
bind:this={colorPreview} |
|||
on:click={() => toggleColorpicker(true)} |
|||
class="color-preview" |
|||
style={`background: ${value}`} /> |
|||
|
|||
<div class="colorpicker" {style}> |
|||
<div class="overlay" on:click|self={() => toggleColorpicker(false)} /> |
|||
<div class="cp" style={pickerStyle}> |
|||
<!-- <HsvPicker on:colorChange={onColourChange} startColor={value} /> --> |
|||
</div> |
|||
</div> |
|||
<!-- |
|||
OLD LOCAL STORAGE OPTIONS. INCLUDING FOR ADDING LATER |
|||
function getRecentColors() { |
|||
let colorStore = localStorage.getItem("bb:recentColors") |
|||
if (!!colorStore) { |
|||
swatches = JSON.parse(colorStore) |
|||
} |
|||
} |
|||
|
|||
function setRecentColor(color) { |
|||
if (swatches.length >= 15) { |
|||
swatches.splice(0, 1) |
|||
picker.removeSwatch(0) |
|||
} |
|||
if (!swatches.includes(color)) { |
|||
swatches = [...swatches, color] |
|||
picker.addSwatch(color) |
|||
localStorage.setItem("bb:recentColors", JSON.stringify(swatches)) |
|||
} |
|||
} --> |
|||
|
|||
<style> |
|||
.overlay { |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
/* background: rgba(5, 5, 5, 0.25); */ |
|||
} |
|||
|
|||
.cp { |
|||
position: absolute; |
|||
right: 25px; |
|||
} |
|||
.color-preview { |
|||
height: 30px; |
|||
width: 100%; |
|||
margin: 5px; |
|||
cursor: pointer; |
|||
border: 1px solid gainsboro; |
|||
} |
|||
</style> |
|||
|
Before Width: | Height: | Size: 263 B After Width: | Height: | Size: 494 B |
@ -0,0 +1,54 @@ |
|||
<script> |
|||
import { Input, Button } from "@budibase/bbui" |
|||
import { store } from "builderStore" |
|||
import api from "builderStore/api" |
|||
import posthog from "posthog-js" |
|||
|
|||
let keys = { budibase: "", sendGrid: "" } |
|||
|
|||
async function updateKey([key, value]) { |
|||
const response = await api.put(`/api/keys/${key}`, { value }) |
|||
const res = await response.json() |
|||
if (key === "budibase") posthog.identify(value) |
|||
keys = { ...keys, ...res } |
|||
} |
|||
|
|||
// Get Keys |
|||
async function fetchKeys() { |
|||
const response = await api.get(`/api/keys/`) |
|||
const res = await response.json() |
|||
keys = res |
|||
} |
|||
|
|||
fetchKeys() |
|||
</script> |
|||
|
|||
<div class="container"> |
|||
<div class="background"> |
|||
<Input |
|||
on:save={e => updateKey(['budibase', e.detail])} |
|||
thin |
|||
edit |
|||
value={keys.budibase} |
|||
label="Budibase" /> |
|||
</div> |
|||
<div class="background"> |
|||
<Input |
|||
on:save={e => updateKey(['sendgrid', e.detail])} |
|||
thin |
|||
edit |
|||
value={keys.sendgrid} |
|||
label="Sendgrid" /> |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
.container { |
|||
display: grid; |
|||
grid-gap: var(--space); |
|||
} |
|||
.background { |
|||
border-radius: 5px; |
|||
padding: 12px 0px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,80 @@ |
|||
<script> |
|||
export let step, done, active |
|||
</script> |
|||
|
|||
<div class="container" class:active class:done> |
|||
<div class="circle" class:active class:done> |
|||
{#if done} |
|||
<svg |
|||
width="12" |
|||
height="10" |
|||
viewBox="0 0 12 10" |
|||
fill="none" |
|||
xmlns="http://www.w3.org/2000/svg"> |
|||
<path |
|||
fill-rule="evenodd" |
|||
clip-rule="evenodd" |
|||
d="M10.1212 0.319527C10.327 0.115582 10.6047 0.000803464 10.8944 |
|||
4.20219e-06C11.1841 -0.00079506 11.4624 0.11245 11.6693 |
|||
0.315256C11.8762 0.518062 11.9949 0.794134 11.9998 1.08379C12.0048 |
|||
1.37344 11.8955 1.65339 11.6957 1.86313L5.82705 9.19893C5.72619 |
|||
9.30757 5.60445 9.39475 5.46913 9.45527C5.3338 9.51578 5.18766 9.54839 |
|||
5.03944 9.55113C4.89123 9.55388 4.74398 9.52671 4.60651 |
|||
9.47124C4.46903 9.41578 4.34416 9.33316 4.23934 9.22833L0.350925 |
|||
5.33845C0.242598 5.23751 0.155712 5.11578 0.0954499 4.98054C0.0351876 |
|||
4.84529 0.00278364 4.69929 0.00017159 4.55124C-0.00244046 4.4032 |
|||
0.024793 4.25615 0.0802466 4.11886C0.1357 3.98157 0.218238 3.85685 |
|||
0.322937 3.75215C0.427636 3.64746 0.55235 3.56492 0.68964 |
|||
3.50946C0.82693 3.45401 0.973983 3.42678 1.12203 3.42939C1.27007 3.432 |
|||
1.41607 3.46441 1.55132 3.52467C1.68657 3.58493 1.80829 3.67182 |
|||
1.90923 3.78014L4.98762 6.85706L10.0933 0.35187C10.1024 0.340482 |
|||
10.1122 0.329679 10.1227 0.319527H10.1212Z" |
|||
fill="white" /> |
|||
</svg> |
|||
{:else}{step}{/if} |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
.container::before { |
|||
content: ""; |
|||
position: absolute; |
|||
top: -30px; |
|||
width: 1px; |
|||
height: 30px; |
|||
background: #bdbdbd; |
|||
} |
|||
.container:first-child::before { |
|||
display: none; |
|||
} |
|||
.container { |
|||
position: relative; |
|||
height: 45px; |
|||
display: grid; |
|||
place-items: center; |
|||
} |
|||
.container.active { |
|||
box-shadow: inset 3px 0 0 0 #4285f4; |
|||
} |
|||
.circle.active { |
|||
background: #4285f4; |
|||
color: white; |
|||
border: none; |
|||
} |
|||
.circle.done { |
|||
background: #bdbdbd; |
|||
color: white; |
|||
border: none; |
|||
} |
|||
.circle { |
|||
color: #bdbdbd; |
|||
font-size: 14px; |
|||
display: grid; |
|||
place-items: center; |
|||
width: 30px; |
|||
height: 30px; |
|||
border-radius: 50%; |
|||
border: 1px solid #bdbdbd; |
|||
box-sizing: border-box; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,25 @@ |
|||
<script> |
|||
import { Input } from "@budibase/bbui" |
|||
export let validationErrors |
|||
|
|||
let blurred = { api: false } |
|||
</script> |
|||
|
|||
<h2>Setup your API Key</h2> |
|||
<div class="container"> |
|||
<Input |
|||
on:input={() => (blurred.api = true)} |
|||
label="API Key" |
|||
name="apiKey" |
|||
placeholder="Enter your API Key" |
|||
type="password" |
|||
error={blurred.api && validationErrors.apiKey} /> |
|||
<a target="_blank" href="https://portal.budi.live/">Get API Key</a> |
|||
</div> |
|||
|
|||
<style> |
|||
.container { |
|||
display: grid; |
|||
grid-gap: 40px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,24 @@ |
|||
<script> |
|||
import { Input } from "@budibase/bbui" |
|||
export let validationErrors |
|||
|
|||
let blurred = { appName: false } |
|||
</script> |
|||
|
|||
<h2>Create your first web app</h2> |
|||
<div class="container"> |
|||
<Input |
|||
on:input={() => (blurred.appName = true)} |
|||
label="Web app name" |
|||
name="applicationName" |
|||
placeholder="Enter name of your web application" |
|||
type="name" |
|||
error={blurred.appName && validationErrors.applicationName} /> |
|||
</div> |
|||
|
|||
<style> |
|||
.container { |
|||
display: grid; |
|||
grid-gap: 40px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,35 @@ |
|||
<script> |
|||
import { Input, Select } from "@budibase/bbui" |
|||
export let validationErrors |
|||
|
|||
let blurred = { username: false, password: false } |
|||
</script> |
|||
|
|||
<h2>Create new user</h2> |
|||
<div class="container"> |
|||
<Input |
|||
on:input={() => (blurred.username = true)} |
|||
label="Username" |
|||
name="username" |
|||
placeholder="Username" |
|||
type="name" |
|||
error={blurred.username && validationErrors.username} /> |
|||
<Input |
|||
on:input={() => (blurred.password = true)} |
|||
label="Password" |
|||
name="password" |
|||
placeholder="Password" |
|||
type="pasword" |
|||
error={blurred.password && validationErrors.password} /> |
|||
<Select name="accessLevelId"> |
|||
<option value="ADMIN">Admin</option> |
|||
<option value="POWER_USER">Power User</option> |
|||
</Select> |
|||
</div> |
|||
|
|||
<style> |
|||
.container { |
|||
display: grid; |
|||
grid-gap: 40px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,3 @@ |
|||
export { default as API } from "./API.svelte" |
|||
export { default as Info } from "./Info.svelte" |
|||
export { default as User } from "./User.svelte" |
|||
@ -0,0 +1,157 @@ |
|||
<script> |
|||
import Colorpicker from "./Colorpicker.svelte" |
|||
import CheckedBackground from "./CheckedBackground.svelte" |
|||
import { createEventDispatcher, beforeUpdate } from "svelte" |
|||
|
|||
import { buildStyle } from "./helpers.js" |
|||
import { fade } from "svelte/transition" |
|||
import { getColorFormat } from "./utils.js" |
|||
|
|||
export let value = "#3ec1d3ff" |
|||
export let swatches = [] |
|||
export let disableSwatches = false |
|||
export let open = false |
|||
export let width = "25px" |
|||
export let height = "25px" |
|||
|
|||
let format = "hexa" |
|||
let dimensions = { top: 0, bottom: 0, right: 0, left: 0 } |
|||
let positionSide = "top" |
|||
let colorPreview = null |
|||
|
|||
let previewHeight = null |
|||
let previewWidth = null |
|||
let pickerWidth = 0 |
|||
let pickerHeight = 0 |
|||
|
|||
let errorMsg = null |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
|
|||
beforeUpdate(() => { |
|||
format = getColorFormat(value) |
|||
if (!format) { |
|||
errorMsg = `Colorpicker - ${value} is an unknown color format. Please use a hex, rgb or hsl value` |
|||
console.error(errorMsg) |
|||
} else { |
|||
errorMsg = null |
|||
} |
|||
}) |
|||
|
|||
function openColorpicker(event) { |
|||
if (colorPreview) { |
|||
open = true |
|||
} |
|||
} |
|||
|
|||
function onColorChange(color) { |
|||
value = color.detail |
|||
dispatch("change", color.detail) |
|||
} |
|||
|
|||
$: if (open && colorPreview) { |
|||
const { |
|||
top: spaceAbove, |
|||
width, |
|||
bottom, |
|||
right, |
|||
left, |
|||
} = colorPreview.getBoundingClientRect() |
|||
|
|||
const spaceBelow = window.innerHeight - bottom |
|||
const previewCenter = previewWidth / 2 |
|||
|
|||
let y, x |
|||
|
|||
if (spaceAbove > spaceBelow) { |
|||
positionSide = "bottom" |
|||
y = window.innerHeight - spaceAbove |
|||
} else { |
|||
positionSide = "top" |
|||
y = bottom |
|||
} |
|||
|
|||
x = left + previewCenter - pickerWidth / 2 |
|||
|
|||
dimensions = { [positionSide]: y.toFixed(1), left: x.toFixed(1) } |
|||
} |
|||
|
|||
$: previewStyle = buildStyle({ width, height, background: value }) |
|||
$: errorPreviewStyle = buildStyle({ width, height }) |
|||
$: pickerStyle = buildStyle({ |
|||
[positionSide]: `${dimensions[positionSide]}px`, |
|||
left: `${dimensions.left}px`, |
|||
}) |
|||
</script> |
|||
|
|||
<div class="color-preview-container"> |
|||
{#if !errorMsg} |
|||
<CheckedBackground borderRadius="3px" backgroundSize="8px"> |
|||
<div |
|||
bind:this={colorPreview} |
|||
bind:clientHeight={previewHeight} |
|||
bind:clientWidth={previewWidth} |
|||
class="color-preview" |
|||
style={previewStyle} |
|||
on:click={openColorpicker} /> |
|||
</CheckedBackground> |
|||
|
|||
{#if open} |
|||
<Colorpicker |
|||
style={pickerStyle} |
|||
on:change={onColorChange} |
|||
on:addswatch |
|||
on:removeswatch |
|||
bind:format |
|||
bind:value |
|||
bind:pickerHeight |
|||
bind:pickerWidth |
|||
bind:open |
|||
{swatches} |
|||
{disableSwatches} /> |
|||
<div on:click|self={() => (open = false)} class="overlay" /> |
|||
{/if} |
|||
{:else} |
|||
<div class="color-preview preview-error" style={errorPreviewStyle}> |
|||
<span>×</span> |
|||
</div> |
|||
{/if} |
|||
</div> |
|||
|
|||
<style> |
|||
.color-preview-container { |
|||
display: flex; |
|||
flex-flow: row nowrap; |
|||
height: fit-content; |
|||
} |
|||
|
|||
.color-preview { |
|||
cursor: pointer; |
|||
border-radius: 3px; |
|||
border: 1px solid #dedada; |
|||
} |
|||
|
|||
.preview-error { |
|||
background: #cccccc; |
|||
color: #808080; |
|||
text-align: center; |
|||
font-size: 18px; |
|||
cursor: not-allowed; |
|||
} |
|||
|
|||
/* .picker-container { |
|||
position: absolute; |
|||
z-index: 3; |
|||
width: fit-content; |
|||
height: fit-content; |
|||
} */ |
|||
|
|||
.overlay { |
|||
position: fixed; |
|||
top: 0; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
z-index: 2; |
|||
} |
|||
</style> |
|||
@ -1,23 +0,0 @@ |
|||
export default function(node) { |
|||
function handleMouseDown() { |
|||
window.addEventListener("mousemove", handleMouseMove) |
|||
window.addEventListener("mouseup", handleMouseUp) |
|||
} |
|||
|
|||
function handleMouseMove(event) { |
|||
let mouseX = event.clientX |
|||
node.dispatchEvent( |
|||
new CustomEvent("drag", { |
|||
detail: mouseX, |
|||
}) |
|||
) |
|||
} |
|||
|
|||
function handleMouseUp() { |
|||
window.removeEventListener("mousedown", handleMouseDown) |
|||
window.removeEventListener("mousemove", handleMouseMove) |
|||
node.dispatchEvent(new CustomEvent("dragend")) |
|||
} |
|||
|
|||
node.addEventListener("mousedown", handleMouseDown) |
|||
} |
|||
@ -1,2 +0,0 @@ |
|||
export { default as drag } from "./drag.js" |
|||
export { default as keyevents } from "./key-events.js" |
|||
@ -1,41 +0,0 @@ |
|||
//events: Array<{trigger: fn}>
|
|||
export default function(node, events = []) { |
|||
const ev = Object.entries(events) |
|||
let fns = [] |
|||
|
|||
for (let [trigger, fn] of ev) { |
|||
let f = addEvent(trigger, fn) |
|||
fns = [...fns, f] |
|||
} |
|||
|
|||
function _scaffold(trigger, fn) { |
|||
return () => { |
|||
let trig = parseInt(trigger) |
|||
if (trig) { |
|||
if (event.keyCode === trig) { |
|||
fn(event) |
|||
} |
|||
} else { |
|||
if (event.key === trigger) { |
|||
fn(event) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
function addEvent(trigger, fn) { |
|||
let f = _scaffold(trigger, fn) |
|||
node.addEventListener("keydown", f) |
|||
return f |
|||
} |
|||
|
|||
function removeEvents() { |
|||
fns.forEach(f => node.removeEventListener("keypress", f)) |
|||
} |
|||
|
|||
return { |
|||
destroy() { |
|||
removeEvents() |
|||
}, |
|||
} |
|||
} |
|||
@ -1,29 +0,0 @@ |
|||
<script> |
|||
import FlatButton from "./FlatButton.svelte" |
|||
|
|||
export let format = "hex" |
|||
export let onclick = format => {} |
|||
|
|||
let colorFormats = ["hex", "rgb", "hsl"] |
|||
</script> |
|||
|
|||
<div class="flatbutton-group"> |
|||
{#each colorFormats as text} |
|||
<FlatButton |
|||
selected={format === text} |
|||
{text} |
|||
on:click={() => onclick(text)} /> |
|||
{/each} |
|||
</div> |
|||
|
|||
<style> |
|||
.flatbutton-group { |
|||
font-weight: 500; |
|||
display: flex; |
|||
flex-flow: row nowrap; |
|||
justify-content: center; |
|||
width: 170px; |
|||
height: 30px; |
|||
align-self: center; |
|||
} |
|||
</style> |
|||
@ -1,26 +0,0 @@ |
|||
<script> |
|||
import { buildStyle } from "../helpers.js" |
|||
import { fade } from "svelte/transition" |
|||
|
|||
export let backgroundSize = "10px" |
|||
export let borderRadius = "" |
|||
export let height = "" |
|||
export let width = "" |
|||
export let margin = "" |
|||
|
|||
$: style = buildStyle({ backgroundSize, borderRadius, height, width, margin }) |
|||
</script> |
|||
|
|||
<div in:fade {style}> |
|||
<slot /> |
|||
</div> |
|||
|
|||
<style> |
|||
div { |
|||
background-image: url('data:image/svg+xml;utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2 2"><path fill="white" d="M1,0H2V1H1V0ZM0,1H1V2H0V1Z"/><path fill="gray" d="M0,0H1V1H0V0ZM1,1H2V2H1V1Z"/></svg>'); |
|||
height: fit-content; |
|||
width: fit-content; |
|||
width: -moz-fit-content; |
|||
height: -moz-fit-content; |
|||
} |
|||
</style> |
|||
@ -1,175 +0,0 @@ |
|||
<script> |
|||
import Colorpicker from "./Colorpicker.svelte"; |
|||
import CheckedBackground from "./CheckedBackground.svelte"; |
|||
import { createEventDispatcher, beforeUpdate, onMount } from "svelte"; |
|||
|
|||
import { buildStyle, debounce } from "../helpers.js"; |
|||
import { fade } from "svelte/transition"; |
|||
import { getColorFormat } from "../utils.js"; |
|||
|
|||
export let value = "#3ec1d3ff"; |
|||
export let swatches = []; |
|||
export let disableSwatches = false; |
|||
export let open = false; |
|||
export let width = "25px"; |
|||
export let height = "25px"; |
|||
|
|||
let format = "hexa"; |
|||
let dimensions = { top: 0, bottom: 0, right: 0, left: 0 }; |
|||
let positionY = "top"; |
|||
let positionX = "left"; |
|||
let colorPreview = null; |
|||
|
|||
let previewHeight = null; |
|||
let previewWidth = null; |
|||
let pickerWidth = 0; |
|||
let pickerHeight = 0; |
|||
|
|||
let errorMsg = null; |
|||
|
|||
const dispatch = createEventDispatcher(); |
|||
|
|||
beforeUpdate(() => { |
|||
format = getColorFormat(value); |
|||
if (!format) { |
|||
errorMsg = `Colorpicker - ${value} is an unknown color format. Please use a hex, rgb or hsl value`; |
|||
console.error(errorMsg); |
|||
} else { |
|||
errorMsg = null; |
|||
} |
|||
}); |
|||
|
|||
function openColorpicker(event) { |
|||
if (colorPreview) { |
|||
open = true; |
|||
} |
|||
} |
|||
|
|||
function onColorChange(color) { |
|||
value = color.detail; |
|||
dispatch("change", color.detail); |
|||
} |
|||
|
|||
function calculateDimensions() { |
|||
if (open) { |
|||
const { |
|||
top: spaceAbove, |
|||
width, |
|||
bottom, |
|||
right, |
|||
left |
|||
} = colorPreview.getBoundingClientRect(); |
|||
|
|||
const spaceBelow = window.innerHeight - bottom; |
|||
const previewCenter = previewWidth / 2; |
|||
|
|||
let y, x; |
|||
|
|||
if (spaceAbove > spaceBelow) { |
|||
positionY = "bottom"; |
|||
y = window.innerHeight - spaceAbove; |
|||
} else { |
|||
positionY = "top"; |
|||
y = bottom; |
|||
} |
|||
|
|||
// Centre picker by default |
|||
x = left + previewCenter - 220 / 2; |
|||
|
|||
const spaceRight = window.innerWidth - right; |
|||
|
|||
//Position picker left or right if space not available for centering |
|||
if (left < 110 && spaceRight > 220) { |
|||
positionX = "left"; |
|||
x = right; |
|||
} else if (spaceRight < 100 && left > 220) { |
|||
positionX = "right"; |
|||
x = document.body.clientWidth - left; |
|||
} |
|||
|
|||
dimensions = { [positionY]: y.toFixed(1), [positionX]: x.toFixed(1) }; |
|||
} |
|||
} |
|||
|
|||
$: if (open && colorPreview) { |
|||
calculateDimensions(); |
|||
} |
|||
|
|||
$: previewStyle = buildStyle({ width, height, background: value }); |
|||
$: errorPreviewStyle = buildStyle({ width, height }); |
|||
$: pickerStyle = buildStyle({ |
|||
[positionY]: `${dimensions[positionY]}px`, |
|||
[positionX]: `${dimensions[positionX]}px` |
|||
}); |
|||
</script> |
|||
|
|||
<style> |
|||
.color-preview-container { |
|||
display: flex; |
|||
flex-flow: row nowrap; |
|||
height: fit-content; |
|||
} |
|||
|
|||
.color-preview { |
|||
cursor: pointer; |
|||
border-radius: 3px; |
|||
border: 1px solid #dedada; |
|||
} |
|||
|
|||
.preview-error { |
|||
background: #cccccc; |
|||
color: #808080; |
|||
text-align: center; |
|||
font-size: 18px; |
|||
cursor: not-allowed; |
|||
} |
|||
|
|||
.overlay { |
|||
position: fixed; |
|||
top: 0; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
z-index: 2; |
|||
} |
|||
</style> |
|||
|
|||
<svelte:window on:resize={debounce(calculateDimensions, 200)} /> |
|||
|
|||
<div class="color-preview-container"> |
|||
{#if !errorMsg} |
|||
<CheckedBackground borderRadius="3px" backgroundSize="8px"> |
|||
<div |
|||
bind:this={colorPreview} |
|||
bind:clientHeight={previewHeight} |
|||
bind:clientWidth={previewWidth} |
|||
title={value} |
|||
class="color-preview" |
|||
style={previewStyle} |
|||
on:click={openColorpicker} /> |
|||
</CheckedBackground> |
|||
|
|||
{#if open} |
|||
<Colorpicker |
|||
style={pickerStyle} |
|||
on:change={onColorChange} |
|||
on:addswatch |
|||
on:removeswatch |
|||
bind:format |
|||
bind:value |
|||
bind:pickerHeight |
|||
bind:pickerWidth |
|||
bind:open |
|||
{swatches} |
|||
{disableSwatches} /> |
|||
<div on:click|self={() => (open = false)} class="overlay" /> |
|||
{/if} |
|||
{:else} |
|||
<div |
|||
class="color-preview preview-error" |
|||
title="Invalid Color" |
|||
style={errorPreviewStyle}> |
|||
<span>×</span> |
|||
</div> |
|||
{/if} |
|||
</div> |
|||
@ -1,37 +0,0 @@ |
|||
<script> |
|||
import {createEventDispatcher} from "svelte" |
|||
import {keyevents} from "../actions" |
|||
|
|||
export let text = "" |
|||
export let selected = false |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
</script> |
|||
|
|||
<div class="flatbutton" tabindex="0" use:keyevents={{"Enter": () => dispatch("click")}} class:selected on:click>{text}</div> |
|||
|
|||
<style> |
|||
.flatbutton { |
|||
cursor: pointer; |
|||
border: 1px solid #d4d4d4; |
|||
border-radius: 8px; |
|||
text-transform: uppercase; |
|||
margin: 5px; |
|||
transition: all 0.3s; |
|||
font-size: 10px; |
|||
flex: 1; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
background: #f1f3f4; |
|||
outline-color: #003cb0; |
|||
outline-width: thin; |
|||
} |
|||
|
|||
.selected { |
|||
color: #ffffff; |
|||
background-color: #003cb0; |
|||
border: none; |
|||
outline: none; |
|||
} |
|||
</style> |
|||
@ -1,28 +0,0 @@ |
|||
<script> |
|||
export let value = "" |
|||
</script> |
|||
|
|||
<div> |
|||
<input on:input on:change type="text" {value} maxlength="25" /> |
|||
</div> |
|||
|
|||
<style> |
|||
div { |
|||
display: flex; |
|||
justify-content: center; |
|||
margin: 5px 0px; |
|||
} |
|||
|
|||
input { |
|||
width: 175px; |
|||
font-size: 13px; |
|||
background: #f1f3f4; |
|||
border-radius: 8px; |
|||
height: 20px; |
|||
outline-color: #003cb0; |
|||
color: inherit; |
|||
text-align: center; |
|||
border: 1px solid #dadada; |
|||
font-weight: 550; |
|||
} |
|||
</style> |
|||
@ -1,73 +0,0 @@ |
|||
<script> |
|||
import { onMount, createEventDispatcher } from "svelte" |
|||
import CheckedBackground from "./CheckedBackground.svelte" |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
|
|||
export let h = 0 |
|||
export let s = 0 |
|||
export let v = 0 |
|||
export let a = 1 |
|||
|
|||
let palette |
|||
|
|||
let paletteHeight, |
|||
paletteWidth = 0 |
|||
|
|||
function handleClick(event) { |
|||
const { left, top } = palette.getBoundingClientRect() |
|||
let clickX = event.clientX - left |
|||
let clickY = event.clientY - top |
|||
if ( |
|||
clickX > 0 && |
|||
clickY > 0 && |
|||
clickX < paletteWidth && |
|||
clickY < paletteHeight |
|||
) { |
|||
let s = (clickX / paletteWidth) * 100 |
|||
let v = 100 - (clickY / paletteHeight) * 100 |
|||
dispatch("change", { s, v }) |
|||
} |
|||
} |
|||
|
|||
$: pickerX = (s * paletteWidth) / 100 |
|||
$: pickerY = paletteHeight * ((100 - v) / 100) |
|||
|
|||
$: paletteGradient = `linear-gradient(to top, rgba(0, 0, 0, 1), transparent), |
|||
linear-gradient(to left, hsla(${h}, 100%, 50%, ${a}), rgba(255, 255, 255, ${a})) |
|||
` |
|||
$: style = `background: ${paletteGradient};` |
|||
|
|||
$: pickerStyle = `transform: translate(${pickerX - 8}px, ${pickerY - 8}px);` |
|||
</script> |
|||
|
|||
<CheckedBackground width="100%"> |
|||
<div |
|||
bind:this={palette} |
|||
bind:clientHeight={paletteHeight} |
|||
bind:clientWidth={paletteWidth} |
|||
on:click={handleClick} |
|||
class="palette" |
|||
{style}> |
|||
<div class="picker" style={pickerStyle} /> |
|||
</div> |
|||
</CheckedBackground> |
|||
|
|||
<style> |
|||
.palette { |
|||
position: relative; |
|||
width: 100%; |
|||
height: 140px; |
|||
cursor: crosshair; |
|||
overflow: hidden; |
|||
} |
|||
|
|||
.picker { |
|||
position: absolute; |
|||
width: 10px; |
|||
height: 10px; |
|||
background: transparent; |
|||
border: 2px solid white; |
|||
border-radius: 50%; |
|||
} |
|||
</style> |
|||
@ -1,111 +0,0 @@ |
|||
<script> |
|||
import { onMount, createEventDispatcher } from "svelte" |
|||
import {drag, keyevents} from "../actions" |
|||
|
|||
export let value = 1 |
|||
export let type = "hue" |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
|
|||
let slider |
|||
let sliderWidth = 0 |
|||
let upperLimit = type === "hue" ? 360 : 1 |
|||
let incrementFactor = type === "hue" ? 1 : 0.01 |
|||
|
|||
const isWithinLimit = value => value >= 0 && value <= upperLimit |
|||
|
|||
function onSliderChange(mouseX, isDrag = false) { |
|||
const { left, width } = slider.getBoundingClientRect() |
|||
let clickPosition = mouseX - left |
|||
|
|||
let percentageClick = (clickPosition / sliderWidth).toFixed(2) |
|||
|
|||
if (percentageClick >= 0 && percentageClick <= 1) { |
|||
|
|||
let value = type === "hue" ? 360 * percentageClick : percentageClick |
|||
dispatch("change", { color: value, isDrag }) |
|||
} |
|||
} |
|||
|
|||
function handleLeftKey() { |
|||
let v = value - incrementFactor |
|||
if(isWithinLimit(v)) { |
|||
value = v |
|||
dispatch("change", { color: value }) |
|||
} |
|||
} |
|||
|
|||
function handleRightKey() { |
|||
let v = value + incrementFactor |
|||
if(isWithinLimit(v)) { |
|||
value = v |
|||
dispatch("change", { color: value }) |
|||
|
|||
} |
|||
} |
|||
|
|||
$: thumbPosition = |
|||
type === "hue" ? sliderWidth * (value / 360) : sliderWidth * value |
|||
|
|||
$: style = `transform: translateX(${thumbPosition - 6}px);` |
|||
</script> |
|||
|
|||
<div |
|||
tabindex="0" |
|||
bind:this={slider} |
|||
use:keyevents={{37: handleLeftKey, 39: handleRightKey}} |
|||
bind:clientWidth={sliderWidth} |
|||
on:click={event => onSliderChange(event.clientX)} |
|||
class="color-format-slider" |
|||
class:hue={type === 'hue'} |
|||
class:alpha={type === 'alpha'}> |
|||
<div |
|||
use:drag |
|||
on:drag={e => onSliderChange(e.detail, true)} |
|||
on:dragend |
|||
class="slider-thumb" |
|||
{style} /> |
|||
</div> |
|||
|
|||
<style> |
|||
.color-format-slider { |
|||
position: relative; |
|||
align-self: center; |
|||
height: 8px; |
|||
width: 150px; |
|||
border-radius: 10px; |
|||
margin: 10px 0px; |
|||
border: 1px solid #e8e8ef; |
|||
cursor: pointer; |
|||
outline-color: #003cb0; |
|||
outline-width: thin; |
|||
} |
|||
|
|||
.hue { |
|||
background: linear-gradient( |
|||
to right, |
|||
hsl(0, 100%, 50%), |
|||
hsl(60, 100%, 50%), |
|||
hsl(120, 100%, 50%), |
|||
hsl(180, 100%, 50%), |
|||
hsl(240, 100%, 50%), |
|||
hsl(300, 100%, 50%), |
|||
hsl(360, 100%, 50%) |
|||
); |
|||
} |
|||
|
|||
.alpha { |
|||
background: linear-gradient(to right, transparent, rgb(0 0 0)); |
|||
} |
|||
|
|||
.slider-thumb { |
|||
position: absolute; |
|||
bottom: -3px; |
|||
height: 12px; |
|||
width: 12px; |
|||
border: 1px solid #777676; |
|||
border-radius: 50%; |
|||
background-color: #ffffff; |
|||
cursor: grab; |
|||
} |
|||
</style> |
|||
@ -1,68 +0,0 @@ |
|||
<script> |
|||
import { createEventDispatcher } from "svelte"; |
|||
import { fade } from "svelte/transition"; |
|||
import CheckedBackground from "./CheckedBackground.svelte"; |
|||
import { keyevents } from "../actions"; |
|||
|
|||
export let hovered = false; |
|||
export let color = "#fff"; |
|||
|
|||
const dispatch = createEventDispatcher(); |
|||
</script> |
|||
|
|||
<style> |
|||
.swatch { |
|||
position: relative; |
|||
cursor: pointer; |
|||
border-radius: 6px; |
|||
border: 1px solid #dedada; |
|||
height: 20px; |
|||
width: 20px; |
|||
outline-color: #003cb0; |
|||
outline-width: thin; |
|||
} |
|||
|
|||
.space { |
|||
padding: 3px 5px; |
|||
} |
|||
|
|||
span { |
|||
cursor: pointer; |
|||
position: absolute; |
|||
top: -5px; |
|||
right: -4px; |
|||
background: #800000; |
|||
color: white; |
|||
font-size: 12px; |
|||
border-radius: 50%; |
|||
text-align: center; |
|||
width: 13px; |
|||
height: 13px; |
|||
} |
|||
|
|||
span:after { |
|||
content: "\00d7"; |
|||
position: relative; |
|||
left: 0; |
|||
bottom: 3px; |
|||
} |
|||
</style> |
|||
|
|||
<div class="space"> |
|||
<CheckedBackground borderRadius="6px"> |
|||
<div |
|||
tabindex="0" |
|||
use:keyevents={{ Enter: () => dispatch('click') }} |
|||
in:fade |
|||
title={color} |
|||
class="swatch" |
|||
style={`background: ${color};`} |
|||
on:mouseover={() => (hovered = true)} |
|||
on:mouseleave={() => (hovered = false)} |
|||
on:click|self> |
|||
{#if hovered} |
|||
<span in:fade on:click={() => dispatch('removeswatch')} /> |
|||
{/if} |
|||
</div> |
|||
</CheckedBackground> |
|||
</div> |
|||
@ -1,20 +0,0 @@ |
|||
export function buildStyle(styles) { |
|||
let str = "" |
|||
for (let s in styles) { |
|||
if (styles[s]) { |
|||
let key = convertCamel(s) |
|||
str += `${key}: ${styles[s]}; ` |
|||
} |
|||
} |
|||
return str |
|||
} |
|||
|
|||
export const convertCamel = str => { |
|||
return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`) |
|||
} |
|||
|
|||
export const debounce = (fn, milliseconds) => { |
|||
return () => { |
|||
setTimeout(fn, milliseconds) |
|||
} |
|||
} |
|||
@ -1,2 +0,0 @@ |
|||
import Colorpreview from "./components/Colorpreview.svelte" |
|||
export default Colorpreview |
|||
@ -1,281 +0,0 @@ |
|||
export const isValidHex = str => |
|||
/^#(?:[A-F0-9]{3}$|[A-F0-9]{4}$|[A-F0-9]{6}$|[A-F0-9]{8})$/gi.test(str) |
|||
|
|||
const getHexaValues = hexString => { |
|||
if (hexString.length <= 5) { |
|||
let hexArr = hexString.match(/[A-F0-9]/gi) |
|||
let t = hexArr.map(c => (c += c)) |
|||
return t |
|||
} else { |
|||
return hexString.match(/[A-F0-9]{2}/gi) |
|||
} |
|||
} |
|||
|
|||
export const isValidRgb = str => { |
|||
const hasValidStructure = /^(?:rgba\(|rgb\()(?:[0-9,\s]|\.(?=\d))*\)$/gi.test( |
|||
str |
|||
) |
|||
if (hasValidStructure) { |
|||
return testRgbaValues(str.toLowerCase()) |
|||
} |
|||
} |
|||
|
|||
const findNonNumericChars = /[a-z()\s]/gi |
|||
|
|||
export const getNumericValues = str => |
|||
str |
|||
.replace(findNonNumericChars, "") |
|||
.split(",") |
|||
.map(v => (v !== "" ? v : undefined)) |
|||
|
|||
export const testRgbaValues = str => { |
|||
const rgba = getNumericValues(str) |
|||
const [r, g, b, a] = rgba |
|||
|
|||
let isValidLengthRange = |
|||
(str.startsWith("rgb(") && rgba.length === 3) || |
|||
(str.startsWith("rgba(") && rgba.length === 4) |
|||
let isValidColorRange = [r, g, b].every(v => v >= 0 && v <= 255) |
|||
let isValidAlphaRange = str.startsWith("rgba(") |
|||
? `${a}`.length <= 4 && a >= 0 && a <= 1 |
|||
: true |
|||
|
|||
return isValidLengthRange && isValidColorRange && isValidAlphaRange |
|||
} |
|||
|
|||
export const isValidHsl = str => { |
|||
const hasValidStructure = /^(?:hsl\(|hsla\()(?:[0-9,%\s]|\.(?=\d))*\)$/gi.test( |
|||
str |
|||
) |
|||
if (hasValidStructure) { |
|||
return testHslaValues(str.toLowerCase()) |
|||
} |
|||
} |
|||
|
|||
export const testHslaValues = str => { |
|||
const hsla = getNumericValues(str) |
|||
const [h, s, l, a] = hsla |
|||
const isUndefined = [h, s, l].some(v => v === undefined) |
|||
|
|||
if (isUndefined) return false |
|||
|
|||
let isValidLengthRange = |
|||
(str.startsWith("hsl(") && hsla.length === 3) || |
|||
(str.startsWith("hsla(") && hsla.length === 4) |
|||
let isValidHue = h >= 0 && h <= 360 |
|||
let isValidSatLum = [s, l].every( |
|||
v => v.endsWith("%") && parseInt(v) >= 0 && parseInt(v) <= 100 |
|||
) |
|||
let isValidAlphaRange = str.startsWith("hsla(") |
|||
? `${a}`.length <= 4 && a >= 0 && a <= 1 |
|||
: true |
|||
|
|||
return isValidLengthRange && isValidHue && isValidSatLum && isValidAlphaRange |
|||
} |
|||
|
|||
export const getColorFormat = color => { |
|||
if (typeof color === "string") { |
|||
if (isValidHex(color)) { |
|||
return "hex" |
|||
} else if (isValidRgb(color)) { |
|||
return "rgb" |
|||
} else if (isValidHsl(color)) { |
|||
return "hsl" |
|||
} |
|||
} |
|||
} |
|||
|
|||
export const convertToHSVA = (value, format) => { |
|||
switch (format) { |
|||
case "hex": |
|||
return getAndConvertHexa(value) |
|||
case "rgb": |
|||
return getAndConvertRgba(value) |
|||
case "hsl": |
|||
return getAndConvertHsla(value) |
|||
} |
|||
} |
|||
|
|||
export const convertHsvaToFormat = (hsva, format) => { |
|||
switch (format) { |
|||
case "hex": |
|||
return hsvaToHexa(hsva, true) |
|||
case "rgb": |
|||
return hsvaToRgba(hsva, true) |
|||
case "hsl": |
|||
return hsvaToHsla(hsva) |
|||
} |
|||
} |
|||
|
|||
export const getAndConvertHexa = color => { |
|||
let [rHex, gHex, bHex, aHex] = getHexaValues(color) |
|||
return hexaToHSVA([rHex, gHex, bHex], aHex) |
|||
} |
|||
|
|||
export const getAndConvertRgba = color => { |
|||
let rgba = getNumericValues(color) |
|||
return rgbaToHSVA(rgba) |
|||
} |
|||
|
|||
export const getAndConvertHsla = color => { |
|||
let hsla = getNumericValues(color) |
|||
return hslaToHSVA(hsla) |
|||
} |
|||
|
|||
export const hexaToHSVA = (hex, alpha = "FF") => { |
|||
const rgba = hex |
|||
.map(v => parseInt(v, 16)) |
|||
.concat(Number((parseInt(alpha, 16) / 255).toFixed(2))) |
|||
return rgbaToHSVA(rgba) |
|||
} |
|||
|
|||
export const rgbaToHSVA = rgba => { |
|||
const [r, g, b, a = 1] = rgba |
|||
let hsv = _rgbToHSV([r, g, b]) |
|||
return [...hsv, a].map(x => parseFloat(x)) |
|||
} |
|||
|
|||
export const hslaToHSVA = ([h, s, l, a = 1]) => { |
|||
let sat = s.replace(/%/, "") |
|||
let lum = l.replace(/%/, "") |
|||
let hsv = _hslToHSV([h, sat, lum]) |
|||
return [...hsv, a].map(x => parseFloat(x)) |
|||
} |
|||
|
|||
export const hsvaToHexa = (hsva, asString = false) => { |
|||
const [r, g, b, a] = hsvaToRgba(hsva) |
|||
const padSingle = hex => (hex.length === 1 ? `0${hex}` : hex) |
|||
|
|||
let hexa = [r, g, b].map(v => { |
|||
let hex = Math.round(v).toString(16) |
|||
return padSingle(hex) |
|||
}) |
|||
|
|||
let alpha = padSingle(Math.round(a * 255).toString(16)) |
|||
hexa = [...hexa, alpha] |
|||
return asString ? `#${hexa.join("")}` : hexa |
|||
} |
|||
|
|||
export const hsvaToRgba = ([h, s, v, a = 1], asString = false) => { |
|||
let rgb = _hsvToRgb([h, s, v]).map(x => Math.round(x)) |
|||
let rgba = [...rgb, a < 1 ? _fixNum(a, 2) : a] |
|||
return asString ? `rgba(${rgba.join(",")})` : rgba |
|||
} |
|||
|
|||
export const hsvaToHsla = ([h, s, v, a = 1]) => { |
|||
let [hue, sat, lum] = _hsvToHSL([h, s, v]) |
|||
let hsla = [hue, sat + "%", lum + "%", a < 1 ? _fixNum(a, 2) : a] |
|||
return `hsla(${hsla.join(",")})` |
|||
} |
|||
|
|||
export const _hslToHSV = hsl => { |
|||
const h = hsl[0] |
|||
let s = hsl[1] / 100 |
|||
let l = hsl[2] / 100 |
|||
let smin = s |
|||
const lmin = Math.max(l, 0.01) |
|||
|
|||
l *= 2 |
|||
s *= l <= 1 ? l : 2 - l |
|||
smin *= lmin <= 1 ? lmin : 2 - lmin |
|||
const v = (l + s) / 2 |
|||
const sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s) |
|||
|
|||
return [h, sv * 100, v * 100] |
|||
} |
|||
|
|||
//Credit : https://github.com/Qix-/color-convert
|
|||
export const _rgbToHSV = rgb => { |
|||
let rdif |
|||
let gdif |
|||
let bdif |
|||
let h |
|||
let s |
|||
|
|||
const r = rgb[0] / 255 |
|||
const g = rgb[1] / 255 |
|||
const b = rgb[2] / 255 |
|||
const v = Math.max(r, g, b) |
|||
const diff = v - Math.min(r, g, b) |
|||
const diffc = function(c) { |
|||
return (v - c) / 6 / diff + 1 / 2 |
|||
} |
|||
|
|||
if (diff === 0) { |
|||
h = 0 |
|||
s = 0 |
|||
} else { |
|||
s = diff / v |
|||
rdif = diffc(r) |
|||
gdif = diffc(g) |
|||
bdif = diffc(b) |
|||
|
|||
if (r === v) { |
|||
h = bdif - gdif |
|||
} else if (g === v) { |
|||
h = 1 / 3 + rdif - bdif |
|||
} else if (b === v) { |
|||
h = 2 / 3 + gdif - rdif |
|||
} |
|||
|
|||
if (h < 0) { |
|||
h += 1 |
|||
} else if (h > 1) { |
|||
h -= 1 |
|||
} |
|||
} |
|||
|
|||
const hsvResult = [h * 360, s * 100, v * 100].map(v => Math.round(v)) |
|||
return hsvResult |
|||
} |
|||
|
|||
//Credit : https://github.com/Qix-/color-convert
|
|||
export const _hsvToRgb = hsv => { |
|||
const h = hsv[0] / 60 |
|||
const s = hsv[1] / 100 |
|||
let v = hsv[2] / 100 |
|||
const hi = Math.floor(h) % 6 |
|||
|
|||
const f = h - Math.floor(h) |
|||
const p = 255 * v * (1 - s) |
|||
const q = 255 * v * (1 - s * f) |
|||
const t = 255 * v * (1 - s * (1 - f)) |
|||
v *= 255 |
|||
|
|||
switch (hi) { |
|||
case 0: |
|||
return [v, t, p] |
|||
case 1: |
|||
return [q, v, p] |
|||
case 2: |
|||
return [p, v, t] |
|||
case 3: |
|||
return [p, q, v] |
|||
case 4: |
|||
return [t, p, v] |
|||
case 5: |
|||
return [v, p, q] |
|||
} |
|||
} |
|||
|
|||
//Credit : https://github.com/Qix-/color-convert
|
|||
export const _hsvToHSL = hsv => { |
|||
const h = hsv[0] |
|||
const s = hsv[1] / 100 |
|||
const v = hsv[2] / 100 |
|||
const vmin = Math.max(v, 0.01) |
|||
let sl |
|||
let l |
|||
|
|||
l = (2 - s) * v |
|||
const lmin = (2 - s) * vmin |
|||
sl = s * vmin |
|||
sl /= lmin <= 1 ? lmin : 2 - lmin |
|||
sl = sl || 0 |
|||
l /= 2 |
|||
|
|||
return [_fixNum(h, 0), _fixNum(sl * 100, 0), _fixNum(l * 100, 0)] |
|||
} |
|||
|
|||
export const _fixNum = (value, decimalPlaces) => |
|||
Number(parseFloat(value).toFixed(decimalPlaces)) |
|||
@ -1,106 +0,0 @@ |
|||
import { getColorFormat, convertToHSVA, convertHsvaToFormat } from "./utils" |
|||
|
|||
describe("convertToHSVA - convert to hsva from format", () => { |
|||
test("convert from hexa", () => { |
|||
expect(convertToHSVA("#f222d382", "hex")).toEqual([309, 86, 95, 0.51]) |
|||
}) |
|||
|
|||
test("convert from hex", () => { |
|||
expect(convertToHSVA("#f222d3", "hex")).toEqual([309, 86, 95, 1]) |
|||
}) |
|||
|
|||
test("convert from rgba", () => { |
|||
expect(convertToHSVA("rgba(242, 34, 211, 1)", "rgb")).toEqual([ |
|||
309, |
|||
86, |
|||
95, |
|||
1, |
|||
]) |
|||
}) |
|||
|
|||
test("convert from rgb", () => { |
|||
expect(convertToHSVA("rgb(150, 80, 255)", "rgb")).toEqual([264, 69, 100, 1]) |
|||
}) |
|||
|
|||
test("convert from from hsl", () => { |
|||
expect(convertToHSVA("hsl(264, 100%, 65.7%)", "hsl")).toEqual([ |
|||
264, |
|||
68.6, |
|||
100, |
|||
1, |
|||
]) |
|||
}) |
|||
|
|||
test("convert from from hsla", () => { |
|||
expect(convertToHSVA("hsla(264, 100%, 65.7%, 0.51)", "hsl")).toEqual([ |
|||
264, |
|||
68.6, |
|||
100, |
|||
0.51, |
|||
]) |
|||
}) |
|||
}) |
|||
|
|||
describe("convertHsvaToFormat - convert from hsva to format", () => { |
|||
test("Convert to hexa", () => { |
|||
expect(convertHsvaToFormat([264, 68.63, 100, 0.5], "hex")).toBe("#9650ff80") |
|||
}) |
|||
|
|||
test("Convert to rgba", () => { |
|||
expect(convertHsvaToFormat([264, 68.63, 100, 0.75], "rgb")).toBe( |
|||
"rgba(150,80,255,0.75)" |
|||
) |
|||
}) |
|||
|
|||
test("Convert to hsla", () => { |
|||
expect(convertHsvaToFormat([264, 68.63, 100, 1], "hsl")).toBe( |
|||
"hsla(264,100%,66%,1)" |
|||
) |
|||
}) |
|||
}) |
|||
|
|||
describe("Get Color Format", () => { |
|||
test("Testing valid hex string", () => { |
|||
expect(getColorFormat("#FFF")).toBe("hex") |
|||
}) |
|||
|
|||
test("Testing invalid hex string", () => { |
|||
expect(getColorFormat("#FFZ")).toBeUndefined() |
|||
}) |
|||
|
|||
test("Testing valid hex with alpha", () => { |
|||
expect(getColorFormat("#FF00BB80")).toBe("hex") |
|||
}) |
|||
|
|||
test("Test valid rgb value", () => { |
|||
expect(getColorFormat("RGB(255, 20, 50)")).toBe("rgb") |
|||
}) |
|||
|
|||
test("Testing invalid rgb value", () => { |
|||
expect(getColorFormat("rgb(255, 0)")).toBeUndefined() |
|||
}) |
|||
|
|||
test("Testing rgb value with alpha", () => { |
|||
expect(getColorFormat("rgba(255, 0, 50, 0.5)")).toBe("rgb") |
|||
}) |
|||
|
|||
test("Testing rgb value with incorrectly provided alpha", () => { |
|||
expect(getColorFormat("rgb(255, 0, 50, 0.5)")).toBeUndefined() |
|||
}) |
|||
|
|||
test("Testing invalid hsl value", () => { |
|||
expect(getColorFormat("hsla(255, 0)")).toBeUndefined() |
|||
}) |
|||
|
|||
test("Testing hsla value with alpha", () => { |
|||
expect(getColorFormat("hsla(150, 60%, 50%, 0.5)")).toBe("hsl") |
|||
}) |
|||
|
|||
test("Testing hsl value with incorrectly provided alpha", () => { |
|||
expect(getColorFormat("hsl(150, 0, 50, 0.5)")).toBeUndefined() |
|||
}) |
|||
|
|||
test("Testing out of bounds hsl", () => { |
|||
expect(getColorFormat("hsl(375, 0, 50)")).toBeUndefined() |
|||
}) |
|||
}) |
|||
@ -0,0 +1,112 @@ |
|||
<script> |
|||
import { MoreIcon } from "components/common/Icons" |
|||
import { store } from "builderStore" |
|||
import ConfirmDialog from "components/common/ConfirmDialog.svelte" |
|||
import UIkit from "uikit" |
|||
import api from "builderStore/api" |
|||
import Portal from "svelte-portal" |
|||
import { DropdownMenu } from "@budibase/bbui" |
|||
|
|||
export let screen |
|||
|
|||
let confirmDeleteDialog |
|||
let dropdown |
|||
let buttonForDropdown |
|||
|
|||
const hideDropdown = () => { |
|||
dropdown.hide() |
|||
} |
|||
|
|||
const deleteScreen = () => { |
|||
store.update(s => { |
|||
const screens = s.screens.filter(c => c.name !== screen.name) |
|||
s.screens = screens |
|||
if (s.currentPreviewItem.name === screen.name) { |
|||
s.currentPreviewItem = s.pages[s.currentPageName] |
|||
s.currentFrontEndType = "page" |
|||
} |
|||
|
|||
api.delete( |
|||
`/_builder/api/pages/${s.currentPageName}/screens/${screen.name}` |
|||
) |
|||
|
|||
return s |
|||
}) |
|||
} |
|||
</script> |
|||
|
|||
<div class="root boundary" on:click|stopPropagation={() => {}}> |
|||
<button on:click={() => dropdown.show()} bind:this={buttonForDropdown}> |
|||
<MoreIcon /> |
|||
</button> |
|||
<DropdownMenu bind:this={dropdown} anchor={buttonForDropdown}> |
|||
<ul class="menu" on:click={hideDropdown}> |
|||
<li class="item" on:click={() => confirmDeleteDialog.show()}> |
|||
<i class="icon ri-delete-bin-2-line" /> |
|||
Delete |
|||
</li> |
|||
</ul> |
|||
</DropdownMenu> |
|||
</div> |
|||
|
|||
<ConfirmDialog |
|||
bind:this={confirmDeleteDialog} |
|||
title="Confirm Delete" |
|||
body={`Are you sure you wish to delete the screen '${screen.props._instanceName}' ?`} |
|||
okText="Delete Screen" |
|||
onOk={deleteScreen} /> |
|||
|
|||
<style> |
|||
.root { |
|||
overflow: hidden; |
|||
z-index: 9; |
|||
} |
|||
|
|||
.root button { |
|||
border-style: none; |
|||
border-radius: 2px; |
|||
padding: 5px; |
|||
background: transparent; |
|||
cursor: pointer; |
|||
color: var(--ink); |
|||
outline: none; |
|||
} |
|||
|
|||
.menu { |
|||
z-index: 100000; |
|||
overflow: visible; |
|||
padding: 12px 0px; |
|||
border-radius: 5px; |
|||
margin: 0; |
|||
} |
|||
|
|||
.menu li { |
|||
border-style: none; |
|||
background-color: transparent; |
|||
list-style-type: none; |
|||
padding: 4px 16px; |
|||
margin: 0; |
|||
width: 100%; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.item { |
|||
display: flex; |
|||
align-items: center; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.icon { |
|||
margin-right: 8px; |
|||
} |
|||
|
|||
.menu li:not(.disabled) { |
|||
cursor: pointer; |
|||
color: var(--grey-7); |
|||
} |
|||
|
|||
.menu li:not(.disabled):hover { |
|||
color: var(--ink); |
|||
background-color: var(--grey-1); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,2 @@ |
|||
<!-- routify:options index=4 --> |
|||
<slot /> |
|||
@ -0,0 +1,90 @@ |
|||
<script> |
|||
import { Button } from "@budibase/bbui" |
|||
import { store } from "builderStore" |
|||
import { notifier } from "builderStore/store/notifications" |
|||
import api from "builderStore/api" |
|||
import Spinner from "components/common/Spinner.svelte" |
|||
import analytics from "../../../analytics" |
|||
|
|||
let deployed = false |
|||
let loading = false |
|||
|
|||
$: appId = $store.appId |
|||
|
|||
async function deployApp() { |
|||
loading = true |
|||
const DEPLOY_URL = `/deploy` |
|||
|
|||
try { |
|||
notifier.info("Starting Deployment..") |
|||
const response = await api.post(DEPLOY_URL) |
|||
const json = await response.json() |
|||
if (response.status !== 200) { |
|||
throw new Error() |
|||
} |
|||
|
|||
notifier.success(`Your Deployment is Complete.`) |
|||
deployed = true |
|||
loading = false |
|||
analytics.captureEvent("web_app_deployment", { |
|||
appId, |
|||
}) |
|||
} catch (err) { |
|||
analytics.captureException(err) |
|||
notifier.danger("Deployment unsuccessful. Please try again later.") |
|||
loading = false |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<section> |
|||
<div> |
|||
<h4>It's time to shine!</h4> |
|||
{#if deployed} |
|||
<a target="_blank" href={`https://${appId}.app.budi.live/${appId}`}> |
|||
View App |
|||
</a> |
|||
{:else} |
|||
<Button secondary medium on:click={deployApp}> |
|||
Deploy App |
|||
{#if loading} |
|||
<Spinner ratio={'0.5'} /> |
|||
{/if} |
|||
</Button> |
|||
{/if} |
|||
</div> |
|||
<img src="/_builder/assets/deploy-rocket.jpg" /> |
|||
</section> |
|||
|
|||
<style> |
|||
img { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
|
|||
h4 { |
|||
color: var(--white); |
|||
font-size: 18px; |
|||
font-weight: bold; |
|||
margin-bottom: 30px; |
|||
} |
|||
|
|||
section { |
|||
position: relative; |
|||
} |
|||
|
|||
div { |
|||
position: absolute; |
|||
display: flex; |
|||
text-align: center; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
left: 0; |
|||
right: 0; |
|||
top: 20%; |
|||
margin-left: auto; |
|||
margin-right: auto; |
|||
width: 50%; |
|||
} |
|||
</style> |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue