@rogieking/figui3
Version:
A lightweight, customizable web component library that uses Figmas UI3 style for modern web applications, but specifically for Figma plugins.
584 lines (508 loc) • 21.2 kB
HTML
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0">
<title>Figma UI3 Web Components</title>
<link rel="stylesheet"
type="text/css"
href="fig.css">
<script src="fig.js"></script>
<style>
body {
margin: 0 auto;
padding: 1rem;
display: flex;
gap: 1rem;
}
#svg {
width: 100%;
height: calc(100vh - 2rem);
border: 1px solid #000;
overflow: scroll;
svg {
width: 100%;
height: 100%;
object-fit: contain;
}
}
.controls {
width: 16rem;
flex-shrink: 0;
max-height: calc(100vh - 2rem);
overflow-y: auto;
padding: 1rem;
background: #f5f5f5;
border-radius: 8px;
}
.section {
margin-bottom: 1.5rem;
}
.section h3 {
margin: 0 0 0.5rem 0;
font-size: 0.9rem;
font-weight: bold;
color: #333;
border-bottom: 1px solid #ddd;
padding-bottom: 0.25rem;
}
.option {
margin-bottom: 0.75rem;
}
.option label {
display: block;
font-size: 0.8rem;
margin-bottom: 0.25rem;
color: #666;
}
.option fig-slider {
width: 100%;
margin-bottom: 0.25rem;
}
.option fig-checkbox {
margin-right: 0.5rem;
}
.option fig-dropdown {
width: 100%;
margin-top: 0.25rem;
}
.value-display {
font-size: 0.75rem;
color: #888;
}
.checkbox-option {
display: flex;
align-items: center;
}
fig-image {
--image-size: 14rem ;
width: 100%;
margin-bottom: 1rem;
position: sticky;
top: 0;
z-index: 100;
background: #f5f5f5;
}
</style>
</head>
<body>
<div class="controls">
<fig-image upload="true"
label="Upload image"
size="large"></fig-image>
<div class="section">
<h3>Presets</h3>
<div class="option">
<label for="preset">Select Preset:</label>
<fig-dropdown id="preset"
name="preset"
value="default">
<option value="default">Default</option>
<option value="posterized1">Posterized 1</option>
<option value="posterized2">Posterized 2</option>
<option value="posterized3">Posterized 3</option>
<option value="curvy">Curvy</option>
<option value="sharp">Sharp</option>
<option value="detailed">Detailed</option>
<option value="smoothed">Smoothed</option>
<option value="grayscale">Grayscale</option>
<option value="fixedpalette">Fixed Palette</option>
<option value="randomsampling1">Random Sampling 1</option>
<option value="randomsampling2">Random Sampling 2</option>
<option value="artistic1">Artistic 1</option>
<option value="artistic2">Artistic 2</option>
<option value="artistic3">Artistic 3</option>
<option value="artistic4">Artistic 4</option>
</fig-dropdown>
</div>
</div>
<div class="section">
<h3>Tracing</h3>
<div class="option checkbox-option">
<fig-checkbox id="corsenabled"
name="corsenabled"
label="CORS Enabled"></fig-checkbox>
</div>
<div class="option">
<label for="ltres">Line Threshold (ltres): <span class="value-display"
id="ltres-value">1</span></label>
<fig-slider min="0"
max="10"
value="1"
step="0.01"
id="ltres"
name="ltres"
variant="minimal"></fig-slider>
</div>
<div class="option">
<label for="qtres">Quadratic Threshold (qtres): <span class="value-display"
id="qtres-value">1</span></label>
<fig-slider min="0"
max="10"
value="1"
step="0.01"
id="qtres"
name="qtres"
variant="minimal"></fig-slider>
</div>
<div class="option">
<label for="pathomit">Path Omit (pathomit): <span class="value-display"
id="pathomit-value">8</span></label>
<fig-slider min="0"
max="100"
value="8"
step="1"
id="pathomit"
name="pathomit"
variant="minimal"></fig-slider>
</div>
<div class="option checkbox-option">
<fig-checkbox id="rightangleenhance"
name="rightangleenhance"
checked
label="Right Angle Enhance"></fig-checkbox>
</div>
</div>
<div class="section">
<h3>Color Quantization</h3>
<div class="option">
<label for="colorsampling">Color Sampling: <span class="value-display"
id="colorsampling-value">2</span></label>
<fig-slider min="0"
max="3"
value="2"
step="1"
id="colorsampling"
name="colorsampling"
variant="minimal"></fig-slider>
</div>
<div class="option">
<label for="numberofcolors">Number of Colors: <span class="value-display"
id="numberofcolors-value">16</span></label>
<fig-slider min="2"
max="256"
value="16"
step="1"
id="numberofcolors"
name="numberofcolors"
variant="minimal"></fig-slider>
</div>
<div class="option">
<label for="mincolorratio">Min Color Ratio: <span class="value-display"
id="mincolorratio-value">0</span></label>
<fig-slider min="0"
max="1"
value="0"
step="0.01"
id="mincolorratio"
name="mincolorratio"
variant="minimal"></fig-slider>
</div>
<div class="option">
<label for="colorquantcycles">Color Quant Cycles: <span class="value-display"
id="colorquantcycles-value">3</span></label>
<fig-slider min="1"
max="10"
value="3"
step="1"
id="colorquantcycles"
name="colorquantcycles"
variant="minimal"></fig-slider>
</div>
</div>
<div class="section">
<h3>SVG Rendering</h3>
<div class="option">
<label for="strokewidth">Stroke Width: <span class="value-display"
id="strokewidth-value">1</span></label>
<fig-slider min="0"
max="10"
value="1"
step="0.1"
id="strokewidth"
name="strokewidth"
variant="minimal"></fig-slider>
</div>
<div class="option checkbox-option">
<fig-checkbox id="linefilter"
name="linefilter"
label="Line Filter"></fig-checkbox>
</div>
<div class="option">
<label for="scale">Scale: <span class="value-display"
id="scale-value">1</span></label>
<fig-slider min="0.1"
max="5"
value="1"
step="0.1"
id="scale"
name="scale"
variant="minimal"></fig-slider>
</div>
<div class="option">
<label for="roundcoords">Round Coords: <span class="value-display"
id="roundcoords-value">1</span></label>
<fig-slider min="0"
max="3"
value="1"
step="1"
id="roundcoords"
name="roundcoords"
variant="minimal"></fig-slider>
</div>
<div class="option checkbox-option">
<fig-checkbox id="viewbox"
name="viewbox"
checked
label="Viewbox"></fig-checkbox>
</div>
<div class="option checkbox-option">
<fig-checkbox id="desc"
name="desc"
label="Description"></fig-checkbox>
</div>
<div class="option">
<label for="lcpr">LCPR: <span class="value-display"
id="lcpr-value">0</span></label>
<fig-slider min="0"
max="10"
value="0"
step="0.1"
id="lcpr"
name="lcpr"
variant="minimal"></fig-slider>
</div>
<div class="option">
<label for="qcpr">QCPR: <span class="value-display"
id="qcpr-value">0</span></label>
<fig-slider min="0"
max="10"
value="0"
step="0.1"
id="qcpr"
name="qcpr"
variant="minimal"></fig-slider>
</div>
</div>
<div class="section">
<h3>Blur</h3>
<div class="option">
<label for="blurradius">Blur Radius: <span class="value-display"
id="blurradius-value">0</span></label>
<fig-slider min="0"
max="20"
value="0"
step="0.1"
id="blurradius"
name="blurradius"
variant="minimal"></fig-slider>
</div>
<div class="option">
<label for="blurdelta">Blur Delta: <span class="value-display"
id="blurdelta-value">20</span></label>
<fig-slider min="1"
max="256"
value="20"
step="1"
id="blurdelta"
name="blurdelta"
variant="minimal"></fig-slider>
</div>
</div>
<div class="section">
<h3>Layering</h3>
<div class="option">
<label for="layering">Layering Method: <span class="value-display"
id="layering-value">0</span></label>
<fig-slider min="0"
max="2"
value="0"
step="1"
id="layering"
name="layering"
variant="minimal"></fig-slider>
</div>
</div>
</div>
<div id="svg">
<fig-spinner></fig-spinner>
</div>
<script type="module">
let options = {
'default': {
// Tracing
corsenabled: false,
ltres: 1,
qtres: 1,
pathomit: 8,
rightangleenhance: true,
// Color quantization
colorsampling: 2,
numberofcolors: 16,
mincolorratio: 0,
colorquantcycles: 3,
// Layering method
layering: 0,
// SVG rendering
strokewidth: 1,
linefilter: false,
scale: 1,
roundcoords: 1,
viewbox: true,
desc: false,
lcpr: 0,
qcpr: 0,
// Blur
blurradius: 0,
blurdelta: 20
},
'posterized1': { colorsampling: 0, numberofcolors: 2 },
'posterized2': { numberofcolors: 4, blurradius: 5 },
'curvy': { ltres: 0.01, linefilter: true, rightangleenhance: false },
'sharp': { qtres: 0.01, linefilter: false },
'detailed': { pathomit: 0, roundcoords: 2, ltres: 0.5, qtres: 0.5, numberofcolors: 64 },
'smoothed': { blurradius: 5, blurdelta: 64 },
'grayscale': { colorsampling: 0, colorquantcycles: 1, numberofcolors: 7 },
'fixedpalette': { colorsampling: 0, colorquantcycles: 1, numberofcolors: 27 },
'randomsampling1': { colorsampling: 1, numberofcolors: 8 },
'randomsampling2': { colorsampling: 1, numberofcolors: 64 },
'artistic1': { colorsampling: 0, colorquantcycles: 1, pathomit: 0, blurradius: 5, blurdelta: 64, ltres: 0.01, linefilter: true, numberofcolors: 16, strokewidth: 2 },
'artistic2': { qtres: 0.01, colorsampling: 0, colorquantcycles: 1, numberofcolors: 4, strokewidth: 0 },
'artistic3': { qtres: 10, ltres: 10, numberofcolors: 8 },
'artistic4': { qtres: 10, ltres: 10, numberofcolors: 64, blurradius: 5, blurdelta: 256, strokewidth: 2 },
'posterized3': {
ltres: 1, qtres: 1, pathomit: 20, rightangleenhance: true, colorsampling: 0, numberofcolors: 3,
mincolorratio: 0, colorquantcycles: 3, blurradius: 3, blurdelta: 20, strokewidth: 0, linefilter: false,
roundcoords: 1, pal: [{ r: 0, g: 0, b: 100, a: 255 }, { r: 255, g: 255, b: 255, a: 255 }]
}
}
// Import imagetracerjs
import ImageTracer from "https://esm.sh/imagetracerjs";
let figImage = document.querySelector("fig-image");
let currentImageBlob = null;
let currentOptions = { ...options.default };
// Function to update current options from form controls
function updateCurrentOptions() {
// Get all form controls - now they are fig.js components
const sliders = document.querySelectorAll('fig-slider');
const checkboxes = document.querySelectorAll('fig-checkbox');
// Handle sliders
sliders.forEach(slider => {
const name = slider.getAttribute('name');
if (name && currentOptions.hasOwnProperty(name)) {
currentOptions[name] = parseFloat(slider.value);
}
});
// Handle checkboxes
checkboxes.forEach(checkbox => {
const name = checkbox.getAttribute('name');
if (name && currentOptions.hasOwnProperty(name)) {
currentOptions[name] = checkbox.input.checked;
}
});
}
// Function to update form controls from current options
function updateFormControlsFromOptions() {
const sliders = document.querySelectorAll('fig-slider');
const checkboxes = document.querySelectorAll('fig-checkbox');
// Handle sliders
sliders.forEach(slider => {
const name = slider.getAttribute('name');
if (name && currentOptions.hasOwnProperty(name)) {
slider.setAttribute('value', currentOptions[name]);
}
});
// Handle checkboxes
checkboxes.forEach(checkbox => {
const name = checkbox.getAttribute('name');
if (name && currentOptions.hasOwnProperty(name)) {
if (currentOptions[name]) {
checkbox.setAttribute('checked', 'true');
} else {
checkbox.removeAttribute('checked');
}
}
});
updateValueDisplays();
}
// Function to apply a preset
function applyPreset(presetName) {
if (!options[presetName]) return;
// Reset to default first
currentOptions = { ...options.default };
// Apply preset values
const preset = options[presetName];
Object.assign(currentOptions, preset);
// Update form controls to reflect new values
updateFormControlsFromOptions();
// Update the preset dropdown
document.getElementById('preset').setAttribute('value', presetName);
// Perform tracing with new options
performTracing();
}
// Function to update value displays
function updateValueDisplays() {
const sliders = document.querySelectorAll('fig-slider');
sliders.forEach(slider => {
const valueDisplay = document.getElementById(slider.id + '-value');
if (valueDisplay) {
valueDisplay.textContent = slider.value;
}
});
}
// Function to perform tracing
function performTracing() {
if (!currentImageBlob) return;
// Clear existing SVG
document.getElementById('svg').innerHTML = '';
let appendImg = function (svgstr) {
console.log('SVG generated, updating display');
document.getElementById('svg').innerHTML = svgstr;
};
console.log('Tracing with options:', currentOptions);
ImageTracer.imageToSVG(currentImageBlob, appendImg, currentOptions);
}
// Set up event listeners for all controls
function setupControlListeners() {
const sliders = document.querySelectorAll('fig-slider');
const checkboxes = document.querySelectorAll('fig-checkbox');
// Handle slider events
sliders.forEach(slider => {
slider.addEventListener('input', (e) => {
updateValueDisplays();
updateCurrentOptions();
// Debounce the tracing to avoid too many calls
clearTimeout(slider.tracingTimeout);
slider.tracingTimeout = setTimeout(performTracing, 300);
});
});
// Handle checkbox events
checkboxes.forEach(checkbox => {
checkbox.addEventListener('change', (e) => {
updateCurrentOptions();
// Debounce the tracing to avoid too many calls
clearTimeout(checkbox.tracingTimeout);
checkbox.tracingTimeout = setTimeout(performTracing, 300);
});
});
// Set up preset dropdown listener
const presetSelect = document.getElementById('preset');
presetSelect.addEventListener('input', (e) => {
const selectedPreset = e.target.value;
applyPreset(selectedPreset);
});
}
// Initial setup
updateValueDisplays();
setupControlListeners();
// Handle image upload
figImage.addEventListener("change", e => {
console.log('Image uploaded:', figImage.blob);
currentImageBlob = figImage.blob;
updateCurrentOptions();
performTracing();
});
</script>
</body>
</html>