Skip to content

Commit

Permalink
feat: implement color picker
Browse files Browse the repository at this point in the history
  • Loading branch information
josdejong committed Aug 14, 2021
1 parent 7d73846 commit 652e3ac
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 5 deletions.
29 changes: 28 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
"sass": "^1.37.5",
"svelte-awesome": "^2.3.2",
"svelte-select": "^4.3.1",
"svelte-simple-modal": "^1.0.0"
"svelte-simple-modal": "^1.0.0",
"vanilla-picker": "^2.11.2"
},
"devDependencies": {
"@commitlint/cli": "13.1.0",
Expand Down
1 change: 1 addition & 0 deletions src/lib/components/controls/ColorPickerPopup.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@import '../../styles';
38 changes: 38 additions & 0 deletions src/lib/components/controls/ColorPickerPopup.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script>
import { onDestroy, onMount } from 'svelte'
export let color
export let onChange
export let showOnTop
let ref
let colorPicker
onMount(async () => {
const VanillaPicker = (await import('vanilla-picker')).default
colorPicker = new VanillaPicker({
parent: ref,
color: color,
popup: showOnTop ? 'top' : 'bottom',
onDone: function (color) {
const alpha = color.rgba[3]
const hex =
alpha === 1
? color.hex.substr(0, 7) // return #RRGGBB
: color.hex // return #RRGGBBAA
onChange(hex)
}
})
colorPicker.show()
})
onDestroy(() => {
colorPicker.destroy()
})
</script>

<div class="jse-color-picker-popup" bind:this={ref} />

<style src="./ColorPickerPopup.scss"></style>
7 changes: 6 additions & 1 deletion src/lib/components/modes/treemode/JSONValue.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@
import { keyComboFromEvent } from '../../../utils/keyBindings.js'
import {
isBoolean,
isColor,
isObjectOrArray,
isTimestamp,
isUrl,
stringConvert,
valueType
} from '../../../utils/typeUtils.js'
import BooleanToggle from './value/BooleanToggle.svelte'
import Timestamp from '$lib/components/modes/treemode/value/Timestamp.svelte'
import Timestamp from '../../../components/modes/treemode/value/Timestamp.svelte'
import Color from '../../../components/modes/treemode/value/Color.svelte'
export let path
export let value
Expand Down Expand Up @@ -176,6 +178,9 @@
{#if isBoolean(value)}
<BooleanToggle {path} {value} {onPatch} />
{/if}
{#if isColor(value)}
<Color {path} {value} {onPatch} />
{/if}
{/if}

<div
Expand Down
18 changes: 18 additions & 0 deletions src/lib/components/modes/treemode/value/Color.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@import '../../../../styles';

.jse-color {
$margin: 2px;
$size: $line-height - 2 * $margin;

width: $size;
height: $size;
box-sizing: border-box;
padding: 0;
margin: $margin;

border: 1px solid $black;
border-radius: 2px;
background: $white;
outline: none;
cursor: pointer;
}
62 changes: 62 additions & 0 deletions src/lib/components/modes/treemode/value/Color.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<script>
import { getColorCSS } from '../../../../utils/typeUtils'
import { getWindow } from '../../../../utils/domUtils'
import { compileJSONPointer } from 'immutable-json-patch'
import { getContext } from 'svelte'
import ColorPickerPopup from '../../../../components/controls/ColorPickerPopup.svelte'
const { openAbsolutePopup } = getContext('absolute-popup')
export let onPatch
export let path
export let value
$: color = getColorCSS(value)
function onChange(color) {
onPatch(
[
{
op: 'replace',
path: compileJSONPointer(path),
value: color
}
],
null
)
}
function openColorPicker(event) {
// estimate of the color picker height
// we'll render the color picker on top
// when there is not enough space below, and there is enough space above
const height = 300
const top = event.target.getBoundingClientRect().top
const windowHeight = getWindow(event.target).innerHeight
const showOnTop = windowHeight - top < height && top > height
const props = {
color: value,
onChange,
showOnTop
}
openAbsolutePopup(ColorPickerPopup, props, {
anchor: event.target,
closeOnOuterClick: true,
offsetTop: 18,
offsetLeft: -8,
height
})
}
</script>

<button
class="jse-color"
style="background: {color}"
title="Click to open a color picker"
on:click={openColorPicker}
/>

<style src="./Color.scss"></style>
2 changes: 1 addition & 1 deletion src/lib/components/modes/treemode/value/Timestamp.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
export let value
$: text = new Date(value).toString()
$: text = `Time: ${new Date(value).toString()}`
</script>

<div class="jse-timestamp" use:tooltip={{ text, ...absolutePopupContext }}>
Expand Down
27 changes: 26 additions & 1 deletion src/lib/utils/typeUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export function isBoolean(value) {
* @return {boolean}
*/
export function isTimestamp(value) {
const YEAR_2000 = 946684800000

return (
typeof value === 'number' &&
value > YEAR_2000 &&
Expand All @@ -60,7 +62,30 @@ export function isTimestamp(value) {
)
}

const YEAR_2000 = 946684800000
/**
* Get the applied color given a color name or code
* Source: https://stackoverflow.com/questions/6386090/validating-css-color-names/33184805
* @param {string} color
* @returns {string | null} returns the color if the input is a valid
* color, and returns null otherwise. Example output:
* 'rgba(255,0,0,0.7)' or 'rgb(255,0,0)'
*/
export function getColorCSS(color) {
// TODO: test performance impact of this function
const colorStyleElement = document.createElement('div')

colorStyleElement.style.color = color
return colorStyleElement.style.color.split(/\s+/).join('').toLowerCase() || null
}

/**
* Test if a string contains a valid color name or code.
* @param {string} value
* @returns {boolean} returns true if a valid color, false otherwise
*/
export function isColor(value) {
return !!getColorCSS(value)
}

/**
* Get the type of a value
Expand Down

0 comments on commit 652e3ac

Please sign in to comment.