0
Fork 0
mirror of https://github.com/penpot/penpot-plugins.git synced 2025-01-06 14:50:21 -05:00

feat: move parser to plugins-data-parser library

This commit is contained in:
María Valderrama 2024-03-01 11:13:50 +01:00
parent afe00b1977
commit 6616fc45c8
18 changed files with 317 additions and 44 deletions

View file

@ -0,0 +1,25 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": "error"
}
}
]
}

View file

@ -0,0 +1,7 @@
# plugins-data-parser
This library was generated with [Nx](https://nx.dev).
## Building
Run `nx build plugins-data-parser` to build the library.

View file

@ -0,0 +1,10 @@
{
"name": "plugins-data-parser",
"version": "0.0.1",
"dependencies": {
"tslib": "^2.3.0"
},
"type": "commonjs",
"main": "./src/index.js",
"typings": "./src/index.d.ts"
}

View file

@ -0,0 +1,19 @@
{
"name": "plugins-data-parser",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/plugins-data-parser/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/libs/plugins-data-parser",
"main": "libs/plugins-data-parser/src/index.ts",
"tsConfig": "libs/plugins-data-parser/tsconfig.lib.json",
"assets": ["libs/plugins-data-parser/*.md"]
}
}
},
"tags": []
}

View file

@ -0,0 +1 @@
export * from './lib/plugins-data-parser';

View file

@ -0,0 +1 @@
export * from './parsed-file.model';

View file

@ -0,0 +1,127 @@
export interface ParsedFile {
id: string;
name: string;
data: FileData;
}
export interface FileData {
id: string;
version: number;
colors: IdData<Color>[];
typographies: IdData<Typhography>[];
pages: RootTail<unknown, string[]>; // Tail is an array of uuid (string)
pagesIndex?: IdData<PageIndex>[];
components: IdData<Components>[];
media?: IdData<Media>[];
}
export interface Color {
color: string;
opacity: number;
id: string;
name: string;
fileId: string;
path: string | null;
}
export interface Typhography {
lineHeight: string;
path: string | null;
fontStyle: string;
textTransform: string;
fontId: string;
fontSize: string;
fontWeight: string;
name: string;
fontVariantId: string;
id: string;
letterSpacing: string;
fontFamily: string;
modifiedAt?: Date;
}
export interface PageIndex {
options: IdData<Option>[];
name: string;
objects: IdData<ObjectI>[];
}
export interface Option {
position: number;
frameId: string;
id: string;
axis: null | unknown;
x: null | unknown;
}
export interface Components {
id: string;
name: string;
path: string;
objects: IdData<ObjectI>[];
}
export interface ObjectI {
id: string;
name: string;
type: string;
x: number;
y: number;
width: number;
height: number;
rotation: 0;
selrect: Selrect;
points: RootTail<unknown, Point[]>;
transform: Transform;
transformInverse: Transform;
parentId: null | string;
frameId: null | string;
}
export interface Selrect {
x: number;
y: number;
width: number;
height: number;
x1: number;
y1: number;
x2: number;
y2: number;
}
export interface Point {
x: number;
y: number;
}
export interface Transform {
a: number;
b: number;
c: number;
d: number;
e: number;
f: number;
}
export interface Media {
id: string;
name: string;
width: number;
height: number;
mtype: string;
path: string;
}
/*****************
* Generic types *
*****************/
export interface RootTail<R, T> {
root: R;
tail: T;
}
export interface IdData<T> {
id: string;
data: T;
}

View file

@ -0,0 +1,2 @@
export { parseFile } from './utils';
export * from './models';

View file

@ -1,3 +1,4 @@
export * from './object.util';
export * from './parse-arr.util';
export * from './parse.util';
export * from './parse-properties.util';

View file

@ -5,30 +5,6 @@ export function isObject(obj: unknown): boolean {
return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
}
/**
* Checks if an object have only one property, and if that
* property is the one passed as argument.
*
* examples checking property 'hello':
*
* { hello: 'world' } => true,
*
* { hello: 'world', foo: 'bar' } => false
*/
export function isSingleObjectWithProperty(
object: unknown,
property: string
): boolean {
if (isObject(object)) {
return (
Object.keys(object as Record<string, unknown>).length === 1 &&
!!(object as Record<string, unknown>)[property]
);
}
return false;
}
/**
* Converts a string to camelCase from kebab-case and snake_case
*/

View file

@ -1,8 +1,5 @@
import {
isObject,
isSingleObjectWithProperty,
toCamelCase,
} from './object.util';
import { isObject, toCamelCase } from './object.util';
import { isSingleObjectWithProperty } from './parse-properties.util';
interface Name {
name: string;
@ -143,13 +140,18 @@ export function parseObjArr(obj: unknown): unknown {
/**
* Checks if an array is a nested array of objects
*
* It also checks and filter empty nested arrays
*/
function isNestedArray(arr: unknown[]): boolean {
if (
Array.isArray(arr) &&
arr.every((a) => Array.isArray(a) && a.every((b) => isObject(b)))
) {
return true;
if (Array.isArray(arr) && arr.every((a) => Array.isArray(a))) {
// Filter empty nested arrays
const filtered = arr.filter((a) => (a as unknown[]).length > 0);
// Check if every nested array is an array of objects
return filtered.every(
(a) => Array.isArray(a) && a.every((b) => isObject(b))
);
}
return false;

View file

@ -0,0 +1,55 @@
import { isObject } from '.';
/**
* Checks if an object have only one property, and if that
* property is the one passed as argument.
*
* examples checking property 'hello':
*
* { hello: 'world' } => true,
*
* { hello: 'world', foo: 'bar' } => false
*/
export function isSingleObjectWithProperty(
object: unknown,
property: string
): boolean {
if (isObject(object)) {
return (
Object.keys(object as Record<string, unknown>).length === 1 &&
!!(object as Record<string, unknown>)[property]
);
}
return false;
}
export function isSingleObjectWithProperties(
object: unknown,
properties: string[]
): boolean {
if (isObject(object)) {
const keys = Object.keys(object as Record<string, unknown>);
if (keys.length === 1) {
return properties.includes(keys[0]);
}
}
return false;
}
export function parseSingleProperties(
obj: unknown,
properties: string[]
): unknown {
let result = obj;
properties.forEach((property) => {
if (isSingleObjectWithProperty(obj, property)) {
result = (obj as Record<string, unknown>)[property];
}
});
return result;
}

View file

@ -1,9 +1,11 @@
import {
isObject,
isSingleObjectWithProperty,
toCamelCase,
} from './object.util';
import { ParsedFile } from '../models/parsed-file.model';
import { isObject, toCamelCase } from './object.util';
import { flattenNestedArrays, parseObjArr } from './parse-arr.util';
import {
isSingleObjectWithProperties,
isSingleObjectWithProperty,
parseSingleProperties,
} from './parse-properties.util';
/**
* Recursively cleans an object from unnecesary properties
@ -42,6 +44,8 @@ export function cleanObject(
/**
* Recursively checks for "arr" properties and parses them
*
* It also checks for useless one-property objects like uuid or root
*/
export function parseObject(obj: unknown): unknown {
// If it's an array, parse each element
@ -58,6 +62,16 @@ export function parseObject(obj: unknown): unknown {
return parseObject(parsed);
}
// If it's an object with only properties singleProperties, parse them
const singleProperties = ['root', 'uuid', 'name', 'guides'];
if (isSingleObjectWithProperties(obj, singleProperties)) {
const parsed = parseSingleProperties(
obj as Record<string, unknown>,
singleProperties
);
return parseObject(parsed);
}
// If it's an object, parse each property
if (isObject(obj)) {
return Object.keys(obj as Record<string, unknown>).reduce(
@ -75,6 +89,6 @@ export function parseObject(obj: unknown): unknown {
/**
* Parse a file object into a more typescript friendly object
*/
export function parseFile(file: unknown): unknown {
return parseObject(cleanObject(file));
export function parseFile(file: unknown): ParsedFile {
return parseObject(cleanObject(file)) as ParsedFile;
}

View file

@ -0,0 +1,19 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

View file

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"]
}

View file

@ -3,7 +3,6 @@ import './lib/plugin-modal';
import { ɵloadPlugin } from './lib/load-plugin';
import { setFileState, setPageState, setSelection } from './lib/api';
import { parseFile } from './lib/utils';
repairIntrinsics({
evalTaming: 'unsafeEval',
@ -28,8 +27,6 @@ export function initialize(api: any) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
api.addListener('plugin-file', 'file', (file: any) => {
// console.log('File Changed (parsed):', parseFile(file));
console.log('File Changed:', file);
setFileState(file);

View file

@ -16,6 +16,11 @@
"cache": true,
"dependsOn": ["^build"],
"inputs": ["default", "^default"]
},
"@nx/js:tsc": {
"cache": true,
"dependsOn": ["^build"],
"inputs": ["default", "^default"]
}
},
"plugins": [

View file

@ -15,6 +15,8 @@
"skipDefaultLibCheck": true,
"baseUrl": ".",
"paths": {
"plugins-data-parser": ["libs/plugins-data-parser/src/index.ts"],
"plugins-parser": ["libs/plugins-parser/src/index.ts"],
"plugins-runtime": ["libs/plugins-runtime/src/index.ts"],
"plugins-styles/*": ["libs/plugins-styles/src/*"]
}