figma-gridgen
Version:
Utilizes built-in Figma rectangles, lines, and texts to generate tables with neatly organized layers
275 lines (266 loc) • 9.68 kB
text/typescript
import * as Constants from "../models/constants";
import * as Interfaces from "../models/interfaces";
/* Clone function taken from Figma Plugin API example */
export function clone(val) {
const type = typeof val;
if (val === null) {
return null;
} else if (type === "undefined" || type === "number" || type === "string" || type === "boolean") {
return val;
} else if (type === "object") {
if (val instanceof Array) {
return val.map(x => clone(x));
} else if (val instanceof Uint8Array) {
return new Uint8Array(val);
} else {
let o = {};
for (const key in val) {
o[key] = clone(val[key]);
}
return o;
}
}
throw "unknown";
}
/* HEX to RGB Conversion */
export function hexToRgb(hex: string): RGB {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result
? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
: null;
}
/* HTML Elements */
// Select generic HTML Element
export function getHTMLElementById(htmlElement: string): HTMLElement {
return document.getElementById(htmlElement) as HTMLElement;
}
// Select Input Element
export function getHTMLInputElementById(htmlElement: string): HTMLInputElement {
return document.getElementById(htmlElement) as HTMLInputElement;
}
// Get active mode
export function getActiveMode(): Constants.Modes {
return getValue(Constants.Modes.COUNT_AND_TABLE_SIZE, Constants.InputType.BOOLEAN)
? Constants.Modes.COUNT_AND_TABLE_SIZE
: getValue(Constants.Modes.COUNT_AND_CELL_SIZE, Constants.InputType.BOOLEAN)
? Constants.Modes.COUNT_AND_CELL_SIZE
: Constants.Modes.CELL_AND_TABLE_SIZE;
}
// Reset Invalid CSS
export function resetInvalidInput(): void {
const inputList: string[] = Object.keys(Constants.inputsAffectedByMode);
for (let input of inputList) {
getHTMLElementById(input).classList.remove("invalid");
}
}
// Set Invalid CSS by List
function setInvalidInputs(mode: string): void {
const inputList: string[] = Constants.defaultInputsForModes[mode];
for (let input of inputList) {
getHTMLElementById(input).classList.add("invalid");
}
}
/* Extract Inputs */
export function getValue(htmlTagId: string, inputType: Constants.InputType): number | boolean | string {
const input = getHTMLInputElementById(htmlTagId);
switch (inputType) {
case Constants.InputType.NUMBER:
return parseInt(input.value, 10) ? parseInt(input.value, 10) : 0;
case Constants.InputType.BOOLEAN:
return input.checked;
case Constants.InputType.STRING:
return input.value;
}
}
/* Validate User Input */
export function validateInput(
mode: string,
columns: number,
rows: number,
columnWidth: number,
rowHeight: number,
hasHeader: boolean,
hasFloatingFilter: boolean,
): boolean {
let validInput: boolean = true;
// reset invalid CSS and validators
resetInvalidInput();
getHTMLElementById("validValidator").classList.add("show");
getHTMLElementById("negativeValidator").classList.remove("show");
getHTMLElementById("minimumRowsValidator").classList.remove("show");
getHTMLElementById("constraintValidator").classList.remove("show");
// negative and invalid value check
let inputsToValidate: string[] = Constants.defaultInputsForModes[mode].concat(["tableFontSize"]);
if (hasHeader) {
inputsToValidate = inputsToValidate.concat(["headerHeight", "headerFontSize"]);
}
if (hasFloatingFilter) {
inputsToValidate = inputsToValidate.concat(["floatingFilterHeight"]);
}
for (let input of inputsToValidate) {
const inputValue: number = getValue(input, Constants.InputType.NUMBER) as number;
if (!inputValue || inputValue < 1) {
getHTMLInputElementById(input).classList.add("invalid");
getHTMLElementById("validValidator").classList.remove("show");
getHTMLElementById("negativeValidator").classList.add("show");
getHTMLElementById("minimumRowsValidator").classList.remove("show");
getHTMLElementById("constraintValidator").classList.remove("show");
validInput = false;
}
}
// limit check
if (validInput) {
if ((!hasHeader && rows < 1) || (hasHeader && rows < 2)) {
setInvalidInputs(mode);
getHTMLElementById("validValidator").classList.remove("show");
getHTMLElementById("negativeValidator").classList.remove("show");
getHTMLElementById("minimumRowsValidator").classList.add("show");
getHTMLElementById("constraintValidator").classList.remove("show");
validInput = false;
} else if (
columns * columnWidth > Constants.maxDimensionInPixels ||
rows * rowHeight > Constants.maxDimensionInPixels ||
columns > Constants.maxNumberOfRowsOrColumns ||
rows > Constants.maxNumberOfRowsOrColumns
) {
setInvalidInputs(mode);
getHTMLElementById("validValidator").classList.remove("show");
getHTMLElementById("negativeValidator").classList.remove("show");
getHTMLElementById("minimumRowsValidator").classList.remove("show");
getHTMLElementById("constraintValidator").classList.add("show");
validInput = false;
}
}
getHTMLInputElementById(Constants.defaultInputsForModes[mode][0]).select();
return validInput;
}
/* Process Input */
export function processInputToMessage(): void {
// Selected Mode
const mode: Constants.Modes = getActiveMode();
// PluginMessage Creation
const createMessage: Interfaces.PluginMessage = { type: Constants.MessageType.CREATE, mode: mode };
// Table Font Info
Object.keys(Constants.inputIds).forEach(id => {
createMessage[id] = getValue(id, Constants.inputIds[id]);
});
// Constraints Processing
createMessage.referenceCoordinates = { x: 0, y: 0 };
switch (mode) {
case Constants.Modes.COUNT_AND_TABLE_SIZE:
createMessage.columnWidth = createMessage.tableWidth / createMessage.columns;
createMessage.rowHeight = (createMessage.tableHeight - createMessage.headerHeight) / createMessage.rows;
break;
case Constants.Modes.CELL_AND_TABLE_SIZE:
createMessage.columns = Math.floor(createMessage.tableWidth / createMessage.columnWidth);
createMessage.rows =
createMessage.headerHeight > 0
? Math.floor((createMessage.tableHeight - createMessage.headerHeight) / createMessage.rowHeight + 1)
: Math.floor((createMessage.tableHeight - createMessage.headerHeight) / createMessage.rowHeight);
createMessage.referenceCoordinates.y = createMessage.tableHeight % createMessage.rowHeight;
break;
default:
break;
}
// Validation
const validWithinLimits: boolean = validateInput(
mode,
createMessage.columns,
createMessage.rows,
createMessage.columnWidth,
createMessage.rowHeight,
createMessage.header,
createMessage.floatingFilter,
);
if (validWithinLimits) {
parent.postMessage(
{
pluginMessage: createMessage,
},
"*",
);
} else {
// Enable create button and hide loader
getHTMLInputElementById("create").disabled = false;
getHTMLElementById("lds").classList.remove("is-visible");
}
}
/* Toggle HTML Input Editability */
// Generic function
export function toggleEditable(
htmlTagType: Constants.HtmlTagType,
htmlTagId: string,
isPrerequisiteSelected: boolean,
defaultValue: string,
): void {
let htmlTagById;
// Get HTML tag based on type
switch (htmlTagType) {
case Constants.HtmlTagType.INPUT:
htmlTagById = getHTMLInputElementById(htmlTagId) as HTMLInputElement;
break;
case Constants.HtmlTagType.SELECT:
htmlTagById = getHTMLElementById(htmlTagId) as HTMLSelectElement;
break;
default:
break;
}
// Unchecked any checkbox inputs
if (htmlTagById.checked) {
htmlTagById.checked = false;
}
// If input has to be enabled
if (isPrerequisiteSelected) {
htmlTagById.disabled = false;
switch (htmlTagType) {
// Populate default value for textboxes
case Constants.HtmlTagType.INPUT:
htmlTagById.value = defaultValue;
break;
// Populate options for select
case Constants.HtmlTagType.SELECT:
htmlTagById.innerHTML = defaultValue;
break;
default:
break;
}
} else {
htmlTagById.disabled = true;
htmlTagById.value = Constants.DefaultValuesForInputs.DISABLED;
}
// TODO put these somewhere else?
getHTMLInputElementById("stripedPickerButton").disabled = getHTMLInputElementById("stripedbackgroundColor").disabled;
getHTMLInputElementById("borderPickerButton").disabled = getHTMLInputElementById("borderColor").disabled;
return null;
}
/* Construct Font Options */
// Font family
export function constructFontFamilyOptions(pluginFontOptions: { [key: string]: string[] }): string {
let fontFamilyOptionsHTML: string = "";
Object.keys(pluginFontOptions).forEach(fontFamily => {
fontFamilyOptionsHTML += `<option value="${fontFamily}" />`;
});
return fontFamilyOptionsHTML;
}
// Font style
export function constructFontStyleOptions(
pluginFontOptions: { [key: string]: string[] },
selectedFontFamily: string,
selectedFontStyle?: string,
): string {
let fontStyleOptionsHTML: string = "";
pluginFontOptions[selectedFontFamily].forEach(fontStyle => {
const selected: string =
fontStyle === selectedFontStyle
? "selected"
: fontStyle === Constants.DefaultValuesForInputs.OVERALL_FONT_NAME_STYLE && !selectedFontStyle
? "selected"
: "";
fontStyleOptionsHTML += `<option value="${fontStyle}" ${selected}>${fontStyle}</option>`;
});
return fontStyleOptionsHTML;
}