Skip to content

Commit

Permalink
chore: add build for MAUI ResourceDictionary swatches
Browse files Browse the repository at this point in the history
  • Loading branch information
panayot-cankov committed Jan 29, 2024
1 parent d4d237e commit 176291f
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 33 deletions.
25 changes: 25 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,31 @@
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-non-null-assertion": "off"
}
},
{
"files": [
"scripts/build-maui.ts"
],
"extends": [
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"rules": {
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"no-cond-assign": "off",
"no-console": "off",
"no-constant-condition": "off",
"indent": "off",
"space-before-function-paren": "off",
"no-case-declarations": "off",
"eqeqeq": "off"
}
}
]
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"create-component": "gulp create-component",
"test-contrast": "node ./scripts/test-contrast.mjs",
"test:integrations": "npm run build --prefix integrations",
"build:maui-scss": "sass packages/default/scss/core/color-system/_swatch.scss dist/maui/default-main.css",
"build:maui": "node --no-warnings=ExperimentalWarning --loader ts-node/esm ./scripts/build-maui.ts"
},
"engines": {
Expand Down
230 changes: 197 additions & 33 deletions scripts/build-maui.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import fs from "fs/promises";
import { resolve, join, extname, parse, } from 'path';
import * as ts from "typescript";
import { resolve, join } from 'path';

interface Theme {
theme: string;
Expand All @@ -14,8 +13,7 @@ interface Swatch {
}

(async () => {

let themesCatalog: Theme[] = [];
const themesCatalog: Theme[] = [];

const root = resolve(__dirname, "../");

Expand All @@ -24,60 +22,226 @@ interface Swatch {
await fs.rm(mauiDist, { recursive: true, force: true });
await fs.mkdir(mauiDist, { recursive: true });

const themes = ["Default", "Bootstrap"].map(name => ({
const themes = [ "Default", "Bootstrap", "Fluent", "Material" ].map(name => ({
name,
path: join(root, "packages", name.toLowerCase()),
swatchesDir: join(root, "packages", name, "lib", "swatches")
path: join(root, "packages", name.toLowerCase())
}));
for (const { name, swatchesDir } of themes) {

for (const { name, path } of themes) {
console.log(name.toUpperCase());
console.log(underline(name, "="));
console.log();

await fs.mkdir(join(mauiDist, name, "Swatches"), { recursive: true });

const themeCatalog: Theme = {
theme: name,
swatches: []
};
themesCatalog.push(themeCatalog);

const swatches =
(await fs.readdir(swatchesDir, { withFileTypes: true }))
.filter(f => f.isFile() && extname(f.name) == ".json")
.map(f => ({ swatch: parse(f.name).name, path: join(swatchesDir, f.name) }));
for (const { swatch, path } of swatches) {
console.log(`## ${swatch}\n`);
const _swatchesPath = join(path, "scss", "core", "color-system", "_swatch.scss");
const _palettesPath = join(path, "scss", "core", "color-system", "_palettes.scss");

const content = await fs.readFile(path, "utf-8");
const json = JSON.parse(content);
console.log(json);
const paletteMap = await parseKendoPalettesFile(_palettesPath);

const fullName: string = json.name;
const resourceDictionary = await parseSwatchesAndCreateResourceDictionary(_swatchesPath, name, "Main", paletteMap);

const separator = fullName.indexOf(" ");
const theme = fullName.substring(0, separator);
const swatchName = fullName.substring(separator + 1);
const previewColors = json.previewColors;
await fs.writeFile(join(mauiDist, name, "Swatches", "Main.xaml"), resourceDictionary);

themeCatalog.swatches.push({ theme, swatch: swatchName, previewColors });
// fs.writeFile(join(mauiDist, ))

console.log();
}
// console.log(resourceDictionary);
}

// let telerikThemingCatalog: string = "";
// telerikThemingCatalog += "namespace QSF;\n";
// fs.writeFile(join(mauiDist, "TelerikThemingCatalog.cs"), telerikThemingCatalog);

fs.writeFile(
await fs.writeFile(
join(mauiDist, "catalog.json"),
JSON.stringify(themesCatalog, null, " "));


})().catch(console.error);

async function parseKendoPalettesFile(_palettesPath: string): Promise<{ [paletteName: string]: { [colorName: string]: string; }; }> {
const paletteMap: { [paletteName: string]: { [colorName: string]: string; }; } = {};

const palettes = await fs.readFile(_palettesPath, "utf-8");
let lastIndex = 0;

const paletteStartRegEx = /^\$_default-([a-zA-Z-]*)\s*:\s*\(/gm;

let paletteStart: RegExpExecArray | null;
while (paletteStart = paletteStartRegEx.exec(palettes)) {
const paletteName = "kendo-" + paletteStart[1];

paletteMap[paletteName] = {};

console.log(`// Palette ${paletteName}`);
console.log(`const ${paletteName} = {`);

lastIndex = paletteStartRegEx.lastIndex;

// Read colors for the palette....
const colorVariableRegEx = /\s*([a-zA-Z0-9-]*)\s*:\s*(#[0-9a-fA-F]*)\s*,?/gym;
const paletteEndRegEx = /\s*\)\s*;/gym;

while (true) {
colorVariableRegEx.lastIndex = lastIndex;
const colorVariable = colorVariableRegEx.exec(palettes);
if (colorVariable) {
lastIndex = colorVariableRegEx.lastIndex;
const colorKey = colorVariable[1];
const colorValue = colorVariable[2];

paletteMap[paletteName][colorKey] = colorValue;

console.log(` ${colorKey}: ${colorValue},`);
continue;
}

paletteEndRegEx.lastIndex = lastIndex;
if (paletteEndRegEx.exec(palettes)) {
console.log(`}\n`);
break;
}

console.log(`Unexpected character at ${lastIndex}`);
break;
}
}
return paletteMap;
}

async function parseSwatchesAndCreateResourceDictionary(
_swatchesPath: string,
themeName: string,
swatchName: string,
paletteMap: { [paletteName: string]: { [colorName: string]: string; }; }) {
const swatches = await fs.readFile(_swatchesPath, "utf-8");

const defaultColorsStartRegEx = /^\$_default-colors\s*:\s*\(\s*$/mg;

let rd = "";

const defaultColorsStart = defaultColorsStartRegEx.exec(swatches);
if (defaultColorsStart) {
const line = `<?xml version="1.0" encoding="utf-8" ?>
<?xaml-comp compile="true" ?>
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:telerik="http://schemas.telerik.com/2022/xaml/maui"
x:Class="Telerik.MAUI.Theming.${themeName}.Swatches.${swatchName}">`;

console.log(line);
rd += line + "\n";

let lastIndex = defaultColorsStartRegEx.lastIndex;

const commentRegEx = /\s*\/\/\s*(.*)\s*$/gym;
const kendoPaletteLookupRegEx = /\s*([a-zA-Z-]*)\s*:\s*(k-map-get|map\.get)\s*\(\s*\$([a-zA-Z-]*)\s*,\s*([a-zA-Z\-0-9]*)\s*\)\s*,/gym;
const filteredPaletteLookupRegEx = /\s*([a-zA-Z-]*)\s*:\s*([a-zA-Z-]*)\s*\(\s*(k-map-get|map\.get)\s*\(\s*\$([a-zA-Z-]*)\s*,\s*([a-zA-Z\-0-9]*)\s*\)\s*(,\s*([.a-zA-Z\-0-9]*))?\)\s*,/gym;

const endOfMapRegEx = /\s*\)\s*(!default)?\s*;/gym;

while (true) {
let result: RegExpExecArray | null;

commentRegEx.lastIndex = lastIndex;
result = commentRegEx.exec(swatches);
if (result) {
lastIndex = commentRegEx.lastIndex;
const comment = result[1].trim();

const line = `\n <!-- ${comment} -->`;
console.log(line);
rd += line + "\n";

continue;
}

kendoPaletteLookupRegEx.lastIndex = lastIndex;
result = kendoPaletteLookupRegEx.exec(swatches);
if (result) {
lastIndex = kendoPaletteLookupRegEx.lastIndex;
const colorKey = result[1].trim();
const paletteName = result[3].trim();
const peletteColorKey = result[4].trim();

const colorValue = paletteMap[paletteName][peletteColorKey].toUpperCase();

const line = ` <Color x:Key="${kendoToRadColorNaming(colorKey)}" d:ProgressDesignSystem="${colorKey}">${colorValue}</Color>`;
console.log(line);
rd += line + "\n";

continue;
}

filteredPaletteLookupRegEx.lastIndex = lastIndex;
result = filteredPaletteLookupRegEx.exec(swatches);
if (result) {
lastIndex = filteredPaletteLookupRegEx.lastIndex;

const colorKey = result[1].trim();

const paletteName = result[4].trim();
const peletteColorKey = result[5].trim();

const filterName = result[2].trim();
const filterArg = result[7]?.trim();

let colorValue = paletteMap[paletteName][peletteColorKey].toUpperCase();

switch (filterName) {
case "rgba":
if (!filterArg) {
console.log("Expected opacity for rgba filter.");
}
const opacity = Number.parseFloat(filterArg);
let alpha = Math.round(opacity * 255).toString(16);
if (alpha.length == 1) {
alpha = "0" + alpha;
}
colorValue = (colorValue + alpha).toUpperCase();
break;
default:
console.log(`Unknown filter function: ${filterName}!`);
continue;
}

const line = ` <Color x:Key="${kendoToRadColorNaming(colorKey)}" d:ProgressDesignSystem="${colorKey}">${colorValue}</Color>`;
console.log(line);
rd += line + "\n";

continue;
}

endOfMapRegEx.lastIndex = lastIndex;
result = endOfMapRegEx.exec(swatches);
if (result) {
lastIndex = endOfMapRegEx.lastIndex;

const line = "\n</ResourceDictionary>";
console.log(line);
rd += line + "\n";

break;
}

console.log("Unexpected character at " + lastIndex);
break;
}
}
return rd;
}

function kendoToRadColorNaming(name: string): string {
return "Rad" + (name as any).replaceAll(/(^|-)([a-z])/g, (m: any) => m?.[1]?.toUpperCase() ?? m?.[0]?.toUpperCase() ?? "") + "Color";
}

function underline(title: string, char: string = "="): string {
let underline = "";
for (let i = 0; i < title.length; i++) underline += char;
for (let i = 0; i < title.length; i++) {
underline += char;
}
return underline;
}

0 comments on commit 176291f

Please sign in to comment.