palette viewer #10
@ -1,5 +1,7 @@
|
|||||||
import React, { FormEventHandler } from "react";
|
import React, { FormEventHandler } from "react";
|
||||||
import Palette, { palettes } from "./Palette";
|
import Palette, { palettes } from "./Palette";
|
||||||
|
import PalettePreview from "./PalettePreview";
|
||||||
|
import PaletteSelect from "./PaletteSelect";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onImageSubmit: (image: Blob, palette: Palette) => void;
|
onImageSubmit: (image: Blob, palette: Palette) => void;
|
||||||
@ -7,7 +9,7 @@ interface Props {
|
|||||||
|
|
||||||
function ImageInput({ onImageSubmit }: Props) {
|
function ImageInput({ onImageSubmit }: Props) {
|
||||||
const fileInputRef = React.useRef<HTMLInputElement>(null);
|
const fileInputRef = React.useRef<HTMLInputElement>(null);
|
||||||
const [paletteIndex, setPaletteIndex] = React.useState(0);
|
const [palette, setPalette] = React.useState<Palette>(palettes[0]);
|
||||||
|
|
||||||
const handleSubmit: FormEventHandler = (e) => {
|
const handleSubmit: FormEventHandler = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -19,11 +21,11 @@ function ImageInput({ onImageSubmit }: Props) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
onImageSubmit(fileInputRef.current.files[0], palettes[paletteIndex]);
|
onImageSubmit(fileInputRef.current.files[0], palette);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePaletteChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
const handlePaletteChange = (palette: Palette) => {
|
||||||
setPaletteIndex(parseInt(event.target.value));
|
setPalette(palette);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -42,18 +44,9 @@ function ImageInput({ onImageSubmit }: Props) {
|
|||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span className="block">Select a color palette:</span>
|
<span className="block">Select a color palette:</span>
|
||||||
<select
|
<PaletteSelect value={palette} onChange={handlePaletteChange} />
|
||||||
value={paletteIndex}
|
|
||||||
onChange={handlePaletteChange}
|
|
||||||
className="form-select block w-full mt-1"
|
|
||||||
>
|
|
||||||
{palettes.map((palette, i) => (
|
|
||||||
<option key={i} value={i}>
|
|
||||||
{palette.label}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</label>
|
</label>
|
||||||
|
<PalettePreview palette={palette}></PalettePreview>
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Go"
|
value="Go"
|
||||||
|
@ -58,11 +58,7 @@ for (let i = 0; i < 256; i++) {
|
|||||||
}
|
}
|
||||||
const grayScale8bits = new Palette("Gray Scale 8 bits", grayColors);
|
const grayScale8bits = new Palette("Gray Scale 8 bits", grayColors);
|
||||||
|
|
||||||
export const palettes = [
|
const palettes = [nord, monokai, grayScale1bit, grayScale2bits, grayScale8bits];
|
||||||
nord,
|
|
||||||
monokai,
|
export { palettes };
|
||||||
grayScale1bit,
|
|
||||||
grayScale2bits,
|
|
||||||
grayScale8bits,
|
|
||||||
];
|
|
||||||
export default Palette;
|
export default Palette;
|
||||||
|
35
client/src/PalettePreview.tsx
Normal file
35
client/src/PalettePreview.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import Palette from "./Palette";
|
||||||
|
|
||||||
|
interface PalettePreviewProps {
|
||||||
|
palette: Palette;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PalettePreview({ palette }: PalettePreviewProps) {
|
||||||
|
// Build a linear-gradient css string from the palette colors, evenly spaced with hard stops
|
||||||
|
const paletteLength = palette.colors.length;
|
||||||
|
const bandSize = 100 / paletteLength;
|
||||||
|
let gradientPercent = 0;
|
||||||
|
let gradientString = `linear-gradient(90deg, `;
|
||||||
|
for (let i = 0; i < paletteLength; i++) {
|
||||||
|
const color = palette.colors[i];
|
||||||
|
gradientString += `#${color} ${gradientPercent}%, #${color} ${
|
||||||
|
gradientPercent + bandSize
|
||||||
|
}%`;
|
||||||
|
gradientPercent += bandSize;
|
||||||
|
|
||||||
|
if (i !== paletteLength - 1) {
|
||||||
|
gradientString += `, `;
|
||||||
|
} else {
|
||||||
|
gradientString += `)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="h-5 my-2 border-solid border-2 border-nord-0"
|
||||||
|
style={{ background: gradientString }}
|
||||||
|
></div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PalettePreview;
|
34
client/src/PaletteSelect.tsx
Normal file
34
client/src/PaletteSelect.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Palette, { palettes } from "./Palette";
|
||||||
|
|
||||||
|
interface PaletteSelectProps {
|
||||||
|
value: Palette;
|
||||||
|
onChange: (palette: Palette) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PaletteSelect({ value, onChange }: PaletteSelectProps) {
|
||||||
|
const handlePaletteChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
onChange(palettes[parseInt(event.target.value)]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Index of the palette in the palettes array, used as the value of the select
|
||||||
|
const valueIndex = palettes.findIndex((p) => p === value);
|
||||||
|
|
||||||
|
const paletteOptions = palettes.map((palette, index) => (
|
||||||
|
<option key={index} value={index}>
|
||||||
|
{palette.label}
|
||||||
|
</option>
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<select
|
||||||
|
value={valueIndex}
|
||||||
|
onChange={handlePaletteChange}
|
||||||
|
className="form-select block w-full mt-1"
|
||||||
|
>
|
||||||
|
{paletteOptions}
|
||||||
|
</select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PaletteSelect;
|
Loading…
Reference in New Issue
Block a user