Page Menu
Home
Sealhub
Search
Configure Global Search
Log In
Files
F4637557
colors-builder.ts
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
11 KB
Referenced Files
None
Subscribers
None
colors-builder.ts
View Options
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import
{
build
}
from
"esbuild"
;
import
tempfile
from
"tempfile"
;
import
{
promises
as
fs
}
from
"fs"
;
import
{
resolve
}
from
"path"
;
import
{
colord
}
from
"colord"
;
import
{
formatWithPrettier
}
from
"../utils/prettier.js"
;
import
{
Builder
}
from
"./builder.js"
;
import
{
exec
}
from
"../utils/exec.js"
;
import
{
APP_BACK_ALIVE_SIGNAL
}
from
"../notifier.js"
;
export
const
COLORS_TS_PATH
=
"src/back/colors.ts"
;
// keeping it in src/back instead of just src to make typescript hints work there
export
const
COLORS_CSS_PATH
=
"src/colors.css"
;
export
const
COLORS_HTML_PATH
=
"public/dist/colors.html"
;
export
class
ColorsBuilder
extends
Builder
{
getName
()
:
string
{
return
"colors"
;
}
ownsFile
(
file_path
:
string
)
{
return
file_path
==
COLORS_TS_PATH
;
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
async
dispose
()
:
Promise
<
void
>
{}
async
_build
()
{
const
outfile
=
tempfile
({
extension
:
"mjs"
});
await
build
({
entryPoints
:
[
COLORS_TS_PATH
],
bundle
:
true
,
format
:
"esm"
,
outfile
,
});
await
fs
.
appendFile
(
outfile
,
/* HTML */
`<script>
console.log(
JSON.stringify(
Object.fromEntries(
Object.entries(colors).map(
([group_name, group]) => [
group_name,
Object.fromEntries(
Object.entries(group).map(
([color_name, color]) => {
const shades = [];
for (let i = 0; i <= 9; i++) {
shades.push(
shade(color, i)
);
}
return [
color_name,
{ color, shades },
];
}
)
),
]
)
)
)
);
</script>`
.
replaceAll
(
/<\/?script>/g
,
""
)
// <script> helps Prettier format this snippet
);
const
output
=
await
exec
(
"node"
,
[
outfile
]);
const
color_groups
=
JSON
.
parse
(
output
.
stdout
)
as
Record
<
string
,
Record
<
string
,
{
color
:
string
;
shades
:
string
[]
}
>
>
;
let
css_colors
=
""
;
let
html_colors
=
""
;
function
html_color_box
({
color
,
shade_no
,
name
,
group_name
,
is_main
=
false
,
}
:
{
color
:
string
;
shade_no
:
string
;
name
:
string
;
group_name
:
string
;
is_main
?:
boolean
;
})
{
const
c
=
colord
(
color
);
return
/* HTML */
`<div
class="color-box
${
c
.
isDark
()
?
"is-dark"
:
""
}
${
is_main
?
"is-main"
:
""
}
${
c
.
toHsl
().
l
>
90
?
"is-almost-white"
:
""
}
"
style="background-color:
${
color
}
; order:
${
parseInt
(
shade_no
)
}
"
data-color-name="
${
name
}
"
data-color-group-name="
${
group_name
}
"
data-color-shade-number="
${
shade_no
}
"
>
<span class="hex">
${
c
.
toHex
()
}
</span>
</div>`
;
}
for
(
const
group_name
of
Object
.
keys
(
color_groups
).
sort
((
key
)
=>
key
==
"brand"
?
-
1
:
11
))
{
html_colors
+=
`<h2>
${
group_name
}
</h2><div class="group">`
;
const
colors
=
color_groups
[
group_name
];
for
(
const
[
color_name
,
color_info
]
of
Object
.
entries
(
colors
))
{
html_colors
+=
`<h3>
${
color_name
}
</h3><div class="shades">`
;
const
main_color
=
color_info
.
color
;
css_colors
+=
`--color-
${
group_name
}
-
${
color_name
}
:
${
main_color
}
;`
;
const
main_l
=
colord
(
main_color
).
toHsl
().
l
;
html_colors
+=
html_color_box
({
color
:
main_color
,
name
:
color_name
,
shade_no
:
Math
.
round
((
main_l
-
(
main_l
%
10
))
/
10
)
.
toString
()
.
padStart
(
2
,
"0"
),
is_main
:
true
,
group_name
,
});
color_info
.
shades
.
forEach
((
shaded_color
,
index
)
=>
{
const
shade_no
=
index
.
toString
().
padStart
(
2
,
"0"
);
css_colors
+=
`--color-
${
group_name
}
-
${
color_name
}
-
${
shade_no
}
:
${
shaded_color
}
;`
;
if
(
Math
.
abs
(
colord
(
shaded_color
).
toHsl
().
l
-
colord
(
main_color
).
toHsl
().
l
)
<
10
)
{
// this color is already in HTML as the main color, skipping adding it to html to avoid duplication
return
;
}
html_colors
+=
html_color_box
({
color
:
shaded_color
,
shade_no
,
name
:
color_name
,
group_name
,
});
});
html_colors
+=
`</div>`
;
}
html_colors
+=
"</div>"
;
}
const
css
=
await
formatWithPrettier
(
`/* DO NOT EDIT! This file is automatically generated by sealgen */
:root {
${
css_colors
}
}`
,
"css"
);
function
makeDemoPair
(
fg
:
string
,
bg
:
string
,
text
:
string
,
classname
=
""
)
{
return
/* HTML */
` <div
class="pair
${
classname
}
"
data-fg="
${
fg
}
"
data-bg="
${
bg
}
"
style="background-color:
${
bg
}
; color:
${
fg
}
"
>
<span>
${
text
}
</span>
</div>`
;
}
const
html
=
await
formatWithPrettier
(
/* HTML */
`<!DOCTYPE html>
<html>
<head>
<title>Color palettes</title>
<style>
${
css
}
</style>
<style>
* {
font-family: sans-serif;
}
.shades {
display: flex;
}
.color-box {
width: 100px;
height: 100px;
transition: transform 50ms;
transition-timing-function: ease-in-out;
transform: scale(1);
cursor: pointer;
&:hover {
transform: scale(1.1);
z-index: 2;
.hex {
visibility: visible;
}
}
&.is-main {
height: 116px;
width: 116px;
margin-top: -8px;
box-shadow: 0px 0px 6px 2px white;
z-index: 1;
&:before {
font-weight: bold;
}
}
&.is-almost-white.is-main {
box-shadow: 0px 0px 6px 1px #00000078;
}
.hex {
padding: 8px;
font-family: Menlo, Consolas, Monaco,
Liberation Mono, Lucida Console,
monospace;
opacity: 0.5;
visibility: hidden;
}
&.is-dark .hex {
color: white;
}
}
.color-box:before {
content: attr(data-color-shade-number);
box-sizing: border-box;
display: block;
color: black;
font-size: 14px;
width: 100%;
padding: 10px;
opacity: 0.3;
}
.color-box.is-dark:before {
color: white;
}
@keyframes float-up {
from {
transform: translateY(0);
opacity: 1;
}
to {
transform: translateY(-20px);
opacity: 0;
}
}
.toast {
background-color: black;
position: absolute;
color: white;
border-radius: 8px;
padding: 8px;
animation: float-up 800ms;
animation-timing-function: ease-out;
z-index: 3;
}
.container {
width: 100vw;
display: flex;
flex-flow: row wrap;
gap: 16px;
@media (max-width: 1400px) {
.demo {
order: 1;
}
.pallete {
order: 2;
}
}
.canvas {
background-color: var(--color-brand-canvas);
display: flex;
flex-flow: column;
border: 1px dashed gray;
color: gray;
gap: 8px;
padding: 8px;
& > * {
padding: 8px;
color: black;
}
.pair.single span {
opacity: 0.5;
}
.pair.link span {
text-decoration: underline;
}
}
}
</style>
</head>
<body>
<div class="container">
<div class="pallete">
${
html_colors
}
</div>
<div class="demo">
<div class="canvas">
<span>canvas</span>
${
makeDemoPair
(
"var(--color-brand-text-fg)"
,
"var(--color-brand-canvas)"
,
"text-fg on canvas"
)
}
${
makeDemoPair
(
"var(--color-brand-text-fg)"
,
"var(--color-brand-text-bg)"
,
"text-fg on text-bg"
)
}
${
makeDemoPair
(
"var(--color-brand-text-accent)"
,
"var(--color-brand-text-bg)"
,
"text-accent on text-bg"
)
}
${
makeDemoPair
(
"var(--color-brand-text-accent2)"
,
"var(--color-brand-text-bg)"
,
"text-accent2 on text-bg"
)
}
${
makeDemoPair
(
"var(--color-brand-link-fg)"
,
"var(--color-brand-text-bg)"
,
"link-fg on text-bg"
,
"link"
)
}
${
makeDemoPair
(
""
,
"var(--color-brand-accent)"
,
"accent"
,
"single"
)
}
${
makeDemoPair
(
""
,
"var(--color-brand-accent2)"
,
"accent2"
,
"single"
)
}
${
makeDemoPair
(
"var(--color-brand-text-on-accent)"
,
"var(--color-brand-accent)"
,
"text-on-accent on accent"
)
}
${
makeDemoPair
(
"var(--color-brand-text-on-accent2)"
,
"var(--color-brand-accent2)"
,
"text-on-accent2 on accent2"
)
}
${
makeDemoPair
(
"var(--color-brand-link-on-accent)"
,
"var(--color-brand-accent)"
,
"link-on-accent on accent"
,
"link"
)
}
${
makeDemoPair
(
"var(--color-brand-link-on-accent2)"
,
"var(--color-brand-accent2)"
,
"link-on-accent2 on accent2"
,
"link"
)
}
</div>
</div>
</div>
</body>
<script>
function pop_toast() {
const toast = document.createElement("div");
document.body.appendChild(toast);
toast.classList.add("toast");
toast.textContent = "Copied!";
toast.style.setProperty(
"top",
event.clientY +
document.scrollingElement.scrollTop -
40 +
"px"
);
toast.style.setProperty(
"left",
event.clientX + "px"
);
toast.addEventListener("animationend", () => {
toast.remove();
});
}
document.addEventListener("click", (event) => {
const box = event.target.closest(".color-box");
if (box) {
const css_var = box.classList.contains(
"is-main"
)
? \`var(--color-\${box.getAttribute(
"data-color-group-name"
)}-\${box.getAttribute(
"data-color-name"
)})\`
: \`var(--color-\${box.getAttribute(
"data-color-group-name"
)}-\${box.getAttribute(
"data-color-name"
)}-\${box.getAttribute(
"data-color-shade-number"
)})\`;
navigator.clipboard.writeText(css_var);
pop_toast();
}
const pair = event.target.closest(".pair");
if (pair) {
let to_copy = "";
if (pair.classList.contains("single")) {
to_copy = pair.getAttribute("data-bg");
} else {
to_copy = \`background-color: \${pair.getAttribute(
"data-bg"
)};\\ncolor: \${pair.getAttribute(
"data-fg"
)};\`;
}
navigator.clipboard.writeText(to_copy);
pop_toast();
}
});
</script>
<script>
(async function () {
const response = await (
await fetch("/dist/notifier.json")
).json();
const ws = new WebSocket(
\`http://localhost:\${response.port}\`
);
ws.addEventListener("message", (event) => {
console.log(event);
if (event.data == "
${
APP_BACK_ALIVE_SIGNAL
}
") {
document.location = document.location; // refresh
}
});
})();
</script>
</html>`
,
"html"
);
await
Promise
.
all
([
fs
.
writeFile
(
resolve
(
this
.
project_dir
,
COLORS_CSS_PATH
),
css
),
fs
.
writeFile
(
resolve
(
this
.
project_dir
,
COLORS_HTML_PATH
),
html
),
]);
}
}
File Metadata
Details
Attached
Mime Type
text/html
Expires
Tue, May 27, 23:48 (1 d, 20 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
663461
Default Alt Text
colors-builder.ts (11 KB)
Attached To
Mode
rSGEN sealgen
Attached
Detach File
Event Timeline
Log In to Comment