2024-02-21 09:26:01 +03:00
|
|
|
import { existsSync, mkdirSync, readFileSync, statSync } from 'node:fs';
|
|
|
|
import fs from 'node:fs/promises';
|
|
|
|
import path, { join } from 'node:path';
|
|
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
|
|
|
|
import JSZip from 'jszip';
|
|
|
|
|
|
|
|
const __dirname = join(fileURLToPath(import.meta.url), '..');
|
|
|
|
const ZIP_PATH = join(__dirname, './edgeless-snapshot');
|
|
|
|
const ASSETS_PREFIX = `/static/templates`;
|
|
|
|
const ASSETS_PATH = join(__dirname, '../core/public/', ASSETS_PREFIX);
|
|
|
|
const TEMPLATE_PATH = join(__dirname, './edgeless');
|
|
|
|
|
|
|
|
const getZipFilesInCategroies = () => {
|
|
|
|
return fs.readdir(ZIP_PATH).then(folders => {
|
|
|
|
return Promise.all(
|
|
|
|
folders
|
|
|
|
.filter(folder => {
|
|
|
|
return statSync(join(ZIP_PATH, folder)).isDirectory();
|
|
|
|
})
|
|
|
|
.map(async folder => {
|
|
|
|
const files = await fs.readdir(join(ZIP_PATH, folder));
|
|
|
|
return {
|
|
|
|
category: folder,
|
|
|
|
files: files.filter(file => path.extname(file) === '.zip'),
|
|
|
|
};
|
|
|
|
})
|
|
|
|
);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const setupFolder = async () => {
|
|
|
|
if (!existsSync(ASSETS_PATH)) {
|
|
|
|
mkdirSync(ASSETS_PATH);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!existsSync(TEMPLATE_PATH)) {
|
|
|
|
mkdirSync(TEMPLATE_PATH);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @typedef Block
|
|
|
|
* @type {object}
|
|
|
|
* @property {string} flavour
|
|
|
|
* @property {Array<Block> | undefined} children
|
|
|
|
* @property {object} props
|
|
|
|
* @property {string} props.sourceId
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Block} block
|
|
|
|
*/
|
|
|
|
const convertSourceId = (block, assetsExtMap) => {
|
|
|
|
if (block.props?.sourceId) {
|
|
|
|
const extname = assetsExtMap[block.props.sourceId];
|
|
|
|
if (!extname) {
|
|
|
|
console.warn(`No extname found for ${block.props.sourceId}`);
|
|
|
|
}
|
|
|
|
block.props.sourceId = `${ASSETS_PREFIX}/${block.props.sourceId}${
|
|
|
|
extname ?? ''
|
|
|
|
}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (block.children && Array.isArray(block.children)) {
|
|
|
|
block.children.forEach(block => convertSourceId(block, assetsExtMap));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const parseSnapshot = async () => {
|
|
|
|
const filesInCategroies = await getZipFilesInCategroies();
|
|
|
|
await setupFolder();
|
|
|
|
/**
|
|
|
|
* @type {Array<{ category: string, templates: string[] }}>}
|
|
|
|
*/
|
|
|
|
const templatesInCategory = [];
|
|
|
|
|
|
|
|
for (let cate of filesInCategroies) {
|
|
|
|
const templates = [];
|
|
|
|
const assetsExtentionMap = {};
|
|
|
|
|
|
|
|
for (let file of cate.files) {
|
|
|
|
const templateName = path.basename(file, '.zip');
|
|
|
|
const zip = new JSZip();
|
|
|
|
const { files: unarchivedFiles } = await zip.loadAsync(
|
|
|
|
readFileSync(join(ZIP_PATH, cate.category, file))
|
|
|
|
);
|
|
|
|
/**
|
|
|
|
* @type {Array<JSZip.JSZipObject>}
|
|
|
|
*/
|
|
|
|
const assetsFiles = [];
|
|
|
|
/**
|
|
|
|
* @type {Array<JSZip.JSZipObject>}
|
|
|
|
*/
|
|
|
|
const snapshotFiles = [];
|
|
|
|
|
|
|
|
Object.entries(unarchivedFiles).forEach(([name, fileObj]) => {
|
|
|
|
if (name.includes('MACOSX') || name.includes('__MACOSX')) return;
|
|
|
|
|
|
|
|
if (name.startsWith('assets/') && !fileObj.dir) {
|
|
|
|
assetsFiles.push(fileObj);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (name.endsWith('.snapshot.json')) {
|
|
|
|
snapshotFiles.push(fileObj);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
await Promise.all(
|
|
|
|
assetsFiles.map(async file => {
|
|
|
|
const blob = await file.async('blob');
|
|
|
|
const arrayBuffer = await blob.arrayBuffer();
|
|
|
|
const buffer = Buffer.from(arrayBuffer, 'binary');
|
|
|
|
const extname = path.extname(file.name);
|
|
|
|
|
|
|
|
assetsExtentionMap[
|
|
|
|
file.name.replace('assets/', '').replace(extname, '')
|
|
|
|
] = extname;
|
|
|
|
|
|
|
|
await fs.writeFile(
|
|
|
|
join(ASSETS_PATH, file.name.replace('assets/', '')),
|
|
|
|
buffer
|
|
|
|
);
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
await Promise.all(
|
|
|
|
snapshotFiles.map(async snapshot => {
|
|
|
|
const json = await snapshot.async('text');
|
|
|
|
const snapshotContent = JSON.parse(json);
|
|
|
|
const previewPath = join(
|
|
|
|
ZIP_PATH,
|
|
|
|
cate.category,
|
|
|
|
`${templateName}.svg`
|
|
|
|
);
|
|
|
|
let previewContent = '';
|
|
|
|
|
|
|
|
if (existsSync(previewPath)) {
|
|
|
|
const previewFile = readFileSync(previewPath, 'utf-8');
|
|
|
|
previewContent = previewFile
|
|
|
|
.replace(/\n/g, '')
|
|
|
|
.replace(/\s+/g, ' ')
|
|
|
|
.replace('fill="white"', 'fill="currentColor"');
|
|
|
|
} else {
|
|
|
|
console.warn(`No preview found for ${templateName}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
convertSourceId(snapshotContent.blocks, assetsExtentionMap);
|
|
|
|
|
|
|
|
const template = {
|
|
|
|
name: templateName,
|
|
|
|
type: 'template',
|
|
|
|
preview: previewContent,
|
|
|
|
content: snapshotContent,
|
|
|
|
};
|
|
|
|
|
|
|
|
await fs.writeFile(
|
|
|
|
join(join(TEMPLATE_PATH, `${templateName}.json`)),
|
|
|
|
JSON.stringify(template, undefined, 2)
|
|
|
|
);
|
|
|
|
|
|
|
|
templates.push(templateName);
|
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
templatesInCategory.push({
|
|
|
|
category: cate.category,
|
|
|
|
templates,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return templatesInCategory;
|
|
|
|
};
|
|
|
|
|
|
|
|
function numberToWords(n) {
|
|
|
|
const ones = [
|
|
|
|
'Zero',
|
|
|
|
'One',
|
|
|
|
'Two',
|
|
|
|
'Three',
|
|
|
|
'Four',
|
|
|
|
'Five',
|
|
|
|
'Six',
|
|
|
|
'Seven',
|
|
|
|
'Eight',
|
|
|
|
'Nine',
|
|
|
|
];
|
|
|
|
|
|
|
|
if (n < 10) {
|
|
|
|
return ones[n];
|
|
|
|
} else {
|
|
|
|
throw new Error(`Not implemented: ${n}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const camelCaseNumber = variable => {
|
|
|
|
const words = variable.split(' ');
|
|
|
|
return words
|
|
|
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
|
|
.join('');
|
|
|
|
};
|
|
|
|
|
|
|
|
const toVariableName = name => {
|
|
|
|
const converted = Array.from(name).reduce((pre, char) => {
|
|
|
|
if (char >= '0' && char <= '9') {
|
|
|
|
return pre + numberToWords(char - '0');
|
|
|
|
}
|
|
|
|
|
|
|
|
return pre + char;
|
|
|
|
}, '');
|
|
|
|
|
|
|
|
return camelCaseNumber(converted);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {Array<{category: string, templates: string[]}} templatesInGroup
|
|
|
|
*/
|
|
|
|
const buildScript = async templatesInGroup => {
|
|
|
|
const templates = [];
|
|
|
|
const templateVariableMap = {};
|
|
|
|
|
|
|
|
templatesInGroup.forEach(group => {
|
|
|
|
group.templates.forEach(template => {
|
|
|
|
templates.push(template);
|
|
|
|
templateVariableMap[template] = toVariableName(template);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
const importStatements = templates
|
|
|
|
.map(template => {
|
|
|
|
return `import ${toVariableName(
|
|
|
|
template
|
|
|
|
)} from './edgeless/${template}.json';`;
|
|
|
|
})
|
|
|
|
.join('\n');
|
|
|
|
const templatesDeclaration = templatesInGroup.map(group => {
|
|
|
|
return `'${group.category}': [
|
|
|
|
${group.templates
|
|
|
|
.map(template => templateVariableMap[template])
|
|
|
|
.join(',\n ')}
|
|
|
|
]`;
|
|
|
|
});
|
|
|
|
|
|
|
|
const code = `${importStatements}
|
|
|
|
|
|
|
|
const templates = {
|
|
|
|
${templatesDeclaration.join(',\n ')}
|
|
|
|
}
|
|
|
|
|
|
|
|
function lcs(text1: string, text2: string) {
|
|
|
|
const dp: number[][] = Array.from({ length: text1.length + 1 })
|
|
|
|
.fill(null)
|
|
|
|
.map(() => Array.from<number>({length: text2.length + 1}).fill(0));
|
|
|
|
|
|
|
|
for (let i = 1; i <= text1.length; i++) {
|
|
|
|
for (let j = 1; j <= text2.length; j++) {
|
|
|
|
if (text1[i - 1] === text2[j - 1]) {
|
|
|
|
dp[i][j] = dp[i - 1][j - 1] + 1;
|
|
|
|
} else {
|
|
|
|
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return dp[text1.length][text2.length];
|
|
|
|
}
|
|
|
|
|
|
|
|
export const builtInTemplates = {
|
|
|
|
list: async (category: string) => {
|
|
|
|
// @ts-expect-error type should be asserted when using
|
|
|
|
return templates[category] ?? []
|
|
|
|
},
|
|
|
|
|
|
|
|
categories: async () => {
|
|
|
|
return Object.keys(templates)
|
|
|
|
},
|
|
|
|
|
|
|
|
search: async(query: string) => {
|
|
|
|
const candidates: unknown[] = [];
|
|
|
|
const cates = Object.keys(templates);
|
|
|
|
|
|
|
|
query = query.toLowerCase();
|
|
|
|
|
|
|
|
for(let cate of cates) {
|
|
|
|
// @ts-expect-error type should be asserted when using
|
|
|
|
const templatesOfCate = templates[cate];
|
|
|
|
|
|
|
|
for(let temp of templatesOfCate) {
|
|
|
|
if(lcs(query, temp.name.toLowerCase()) === query.length) {
|
|
|
|
candidates.push(temp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return candidates;
|
|
|
|
},
|
|
|
|
}
|
|
|
|
`;
|
|
|
|
|
|
|
|
await fs.writeFile(join(__dirname, './edgeless-templates.gen.ts'), code, {
|
|
|
|
encoding: 'utf-8',
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2024-03-19 10:48:56 +03:00
|
|
|
const templatesInGroup = await parseSnapshot();
|
|
|
|
await buildScript(templatesInGroup);
|