diff --git a/package.json b/package.json
index 8ca2574..d140b82 100644
--- a/package.json
+++ b/package.json
@@ -6,9 +6,13 @@
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
- "fictoan-react": "^0.33.10",
+ "ace-builds": "^1.4.13",
+ "acorn": "^8.6.0",
+ "deep-diff": "^1.0.2",
+ "fictoan-react": "^0.35.3",
"polished": "^4.0.4",
"react": "^16.13.1",
+ "react-ace": "^9.5.0",
"react-dom": "^16.13.1",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.1",
@@ -34,5 +38,13 @@
"last 1 firefox version",
"last 1 safari version"
]
+ },
+ "devDependencies": {
+ "@types/acorn": "^4.0.6",
+ "@types/node": "^16.11.11",
+ "@types/react": "^17.0.37",
+ "@types/react-dom": "^17.0.11",
+ "@types/styled-components": "^5.1.16",
+ "typescript": "^4.5.2"
}
}
diff --git a/src/App.jsx b/src/App.jsx
index 1f8ef5e..9dc0ed7 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -32,6 +32,7 @@ import { TableDocs } from "./pages/Components/Table/Table";
// Local assets
import { Sidebar } from "./components/Sidebar/Sidebar";
import { ThemeDocs } from "./pages/03Theme/Theme";
+import { ThemeEditorDocs } from "./pages/03ThemeBuilder/ThemeEditor";
import { CodeBlockDocs } from "./pages/Components/CodeBlock/CodeBlock";
import { BaseElementDocs } from "./pages/BaseElement/BaseElement";
import { InfoPanelDocs } from "./pages/Components/InfoPanel/InfoPanel";
@@ -91,6 +92,12 @@ export const App = () => {
component={ ThemeDocs }
/>
+
+
{
{/* COMPONENTS ============================================= */}
-
+ <>
@@ -286,41 +284,32 @@ export const Sidebar = ({toggleTheme}) => {
{/* SIDEBAR ================================================ */}
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{/* TABS =================================================== */}
@@ -339,7 +328,7 @@ export const Sidebar = ({toggleTheme}) => {
-
+ >
{/* FOOTER ================================================= */}
diff --git a/src/pages/03ThemeBuilder/CodeManipulation.ts b/src/pages/03ThemeBuilder/CodeManipulation.ts
new file mode 100644
index 0000000..1d5e69b
--- /dev/null
+++ b/src/pages/03ThemeBuilder/CodeManipulation.ts
@@ -0,0 +1,73 @@
+import { DeepDiff } from "deep-diff";
+import dep2 from "polished/lib/color/lighten";
+import dep3 from "polished/lib/color/darken";
+import { defaultColours as dep1 } from "fictoan-react";
+import { ThemeVisitor, JSVisitor } from "./Visitor";
+
+interface DiffType {
+ kind: "E" | "N" | "D" | "A";
+ path: string[];
+ lhs: any;
+ rhs: any;
+ index?: number;
+ item?: any;
+}
+
+function templatize(val: string): string {
+ return `{{${val}}}`;
+}
+
+function unTemplatize(val: string): string {
+ return val.replaceAll(/\'\{\{([^\{\}]+)\}\}\'/g, "$1");
+}
+
+function setDeepValue(obj: any, path: string[], value: string) {
+ const key = path.shift();
+ if (!key) {
+ return value;
+ }
+ obj[key] = setDeepValue(obj[key] ? obj[key] : {}, path, value);
+ return obj;
+}
+
+export function evaluateJS(javascriptCode: string) {
+ const defaultColours = dep1;
+ const lighten = dep2;
+ const darken = dep3;
+ return eval(`(
+ function () {
+ ${javascriptCode}
+ return Theme;
+ }
+ )();`);
+}
+
+export function performDiff(newJavascriptCode: string, oldJavascriptCode: string) {
+ const newCode = evaluateJS(newJavascriptCode);
+ const newCodeVisitor = new ThemeVisitor(newJavascriptCode);
+ const oldCode = evaluateJS(oldJavascriptCode);
+ const oldCodeVisitor = new ThemeVisitor(oldJavascriptCode);
+ const deepDiffs = DeepDiff(oldCode, newCode);
+
+ const diffs = deepDiffs
+ ? DeepDiff(oldCode, newCode)
+ .filter((diff: DiffType) => diff.path[0] != "customColours")
+ .map((diff: DiffType) => {
+ const _newVal = newCodeVisitor.getValueAtPath([ "Theme", ...diff.path ]);
+ const _oldVal = oldCodeVisitor.getValueAtPath([ "Theme", ...diff.path ]);
+ if (_newVal.trim() !== _oldVal.trim())
+ return {path : diff.path, val : _newVal}
+ return undefined;
+ }).filter(Boolean)
+ : [];
+
+ const minifiedTheme = diffs.reduce((acc: object, curr: { path: string[], val: string }) => {
+ return setDeepValue(acc, curr.path.map(templatize), templatize(curr.val))
+ }, {[templatize("customColours")] : templatize("customColours")});
+
+ const v = new JSVisitor(newJavascriptCode);
+ return (
+ `${v.getAllVariableDeclarations()}
+ \nconst Theme = ${unTemplatize(JSON.stringify(minifiedTheme, null, "\t"))}`
+ )
+}
diff --git a/src/pages/03ThemeBuilder/Editor.tsx b/src/pages/03ThemeBuilder/Editor.tsx
new file mode 100644
index 0000000..48e4439
--- /dev/null
+++ b/src/pages/03ThemeBuilder/Editor.tsx
@@ -0,0 +1,52 @@
+import React, { FunctionComponent, useRef } from "react";
+import AceEditor from "react-ace";
+import "ace-builds/src-noconflict/mode-javascript";
+import "ace-builds/src-noconflict/theme-tomorrow";
+import { Button } from "fictoan-react";
+import { STRINGIFIED_FICTOAN_THEME } from "./Theme";
+import { evaluateJS, performDiff } from "./CodeManipulation";
+
+interface EditorProps {
+ setTheme: Function;
+ setMinified: Function;
+}
+
+export const Editor: FunctionComponent = ({setTheme, setMinified}) => {
+
+ const editorRef = useRef();
+
+ function handleClick() {
+ if (editorRef.current) {
+ const code = editorRef.current.editor.getValue();
+ const theme = evaluateJS(code);
+ setTheme(theme);
+ }
+ }
+
+ function handleMinify() {
+ if (editorRef.current) {
+ const codeString = editorRef.current.editor.getValue();
+ const minified = performDiff(codeString, STRINGIFIED_FICTOAN_THEME);
+ setMinified(minified);
+ }
+ }
+
+ return (
+ <>
+
+
+
+ >
+ )
+}
+
diff --git a/src/pages/03ThemeBuilder/Result.tsx b/src/pages/03ThemeBuilder/Result.tsx
new file mode 100644
index 0000000..67fbe2e
--- /dev/null
+++ b/src/pages/03ThemeBuilder/Result.tsx
@@ -0,0 +1,59 @@
+import React, { ReactElement, useState } from "react";
+import {
+ Element,
+ Button,
+ Card,
+ FormWrapper,
+ Heading,
+ InputField,
+ ProgressBar,
+ Text,
+ ThemeProvider,
+} from "fictoan-react";
+import { evaluateJS } from "./CodeManipulation";
+import { STRINGIFIED_FICTOAN_THEME } from "./Theme";
+
+export const FictoanThemeOutputSample = () => {
+ const [json, setJson] = useState(() => evaluateJS(STRINGIFIED_FICTOAN_THEME));
+
+ return (
+
+
+ ACME CORP
+
+
+
+
+ This is a heading
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
+ et dolore magna aliqua.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/pages/03ThemeBuilder/Theme.ts b/src/pages/03ThemeBuilder/Theme.ts
new file mode 100644
index 0000000..54071d8
--- /dev/null
+++ b/src/pages/03ThemeBuilder/Theme.ts
@@ -0,0 +1,561 @@
+export const STRINGIFIED_FICTOAN_THEME= `
+const customColours = {
+ hue: defaultColours.blue90,
+ tint: defaultColours.amber,
+ shade: defaultColours.grey,
+ analogue: defaultColours.indigo50,
+ accent: defaultColours.green80
+};
+
+const Theme= {
+ customColours: customColours,
+ // GLOBALS //////////////////////////////////////////////////////////////
+ globals : {
+ borderWidth : "1px"
+ },
+
+ // BASICS ///////////////////////////////////////////////////////////////
+ body : {
+ bg : String(defaultColours.white)
+ },
+
+
+ // BREADCRUMBS ///////////////////////////////////////////////////////////
+ breadcrumbs : {
+ wrapper : {
+ bg : String(defaultColours.white)
+ },
+ item : {
+ text : String(customColours.shade),
+ active : String(customColours.shade),
+ inactive : String(customColours.shade)
+ },
+ separator : {
+ text : String(defaultColours.slate80),
+ content : "\"/\""
+ }
+ },
+
+
+ // BUTTON ///////////////////////////////////////////////////////////////
+ button : {
+ font : "sans-serif",
+ isLoading : {
+ spinnerBorder : String(customColours.hue)
+ },
+ primary : {
+ default : {
+ bg : String(customColours.hue),
+ border : String(customColours.hue),
+ text : String(defaultColours.white),
+ borderRadius : "4px"
+ },
+ onHover : {
+ bg : String(customColours.hue),
+ border : String(customColours.hue),
+ text : String(defaultColours.white)
+ },
+ isActive : {
+ bg : String(customColours.hue),
+ border : String(customColours.hue),
+ text : String(defaultColours.white)
+ },
+ isLoading : {
+ spinnerBorder : String(defaultColours.white)
+ }
+ },
+ secondary : {
+ default : {
+ bg : lighten(0.4, String(customColours.hue)),
+ border : String(customColours.hue),
+ text : String(customColours.hue),
+ borderRadius : "4px"
+ },
+ onHover : {
+ bg : lighten(0.4, String(customColours.hue)),
+ border : String(customColours.hue),
+ text : String(customColours.hue)
+ },
+ isActive : {
+ bg : lighten(0.2, String(customColours.hue)),
+ border : String(customColours.hue),
+ text : String(customColours.hue)
+ },
+ isLoading : {
+ spinnerBorder : String(customColours.hue)
+ }
+ },
+ tertiary : {
+ default : {
+ bg : String(defaultColours.transparent),
+ border : String(customColours.hue),
+ text : String(customColours.hue),
+ borderRadius : "4px"
+ },
+ onHover : {
+ bg : lighten(0.40, String(customColours.hue)),
+ border : String(defaultColours.transparent),
+ text : String(customColours.hue)
+ },
+ isActive : {
+ bg : lighten(0.32, String(customColours.hue)),
+ border : String(defaultColours.transparent),
+ text : String(customColours.hue)
+ },
+ isLoading : {
+ spinnerBorder : String(customColours.hue)
+ }
+ }
+ },
+
+
+ // CARD /////////////////////////////////////////////////////////////////
+ card : {
+ bg : String(defaultColours.white),
+ border : lighten(0.96, String(defaultColours.black)),
+ borderRadius : "4px"
+ },
+
+
+ // RULE /////////////////////////////////////////////////////////////////
+ hr : {
+ default : {
+ bg : String(defaultColours.blue80),
+ height : "1px"
+ },
+ primary : {
+ bg : String(defaultColours.blue80),
+ height : "1px"
+ },
+ secondary : {
+ bg : String(defaultColours.slate40),
+ height : "1px"
+ },
+ tertiary : {
+ bg : String(defaultColours.slate20),
+ height : "1px"
+ }
+ },
+
+
+ // INPUT ////////////////////////////////////////////////////////////////
+ inputField : {
+ default : {
+ bg : String(defaultColours.white),
+ border : String(defaultColours.slate40),
+ label : String(customColours.shade),
+ text : String(customColours.shade),
+ borderRadius : "4px",
+ helpText : String(defaultColours.slate60)
+ },
+ onFocus : {
+ bg : String(defaultColours.white),
+ border : String(customColours.hue),
+ text : String(customColours.shade),
+ },
+ isValid : {
+ bg : String(defaultColours.white),
+ border : String(defaultColours.green80),
+ label : String(customColours.shade)
+ },
+ isInvalid : {
+ bg : String(defaultColours.red10),
+ border : String(defaultColours.red80),
+ label : String(defaultColours.red),
+ errorText : String(defaultColours.red)
+ },
+ isReadOnly : {
+ bg : String(defaultColours.slate10),
+ border : String(defaultColours.slate20),
+ text : String(defaultColours.slate60),
+ label : String(customColours.shade)
+ },
+ required : {
+ text : String(defaultColours.red)
+ },
+ icons : {
+ default : {
+ fill : String(defaultColours.slate30)
+ },
+ onFocus : {
+ fill : String(customColours.hue)
+ },
+ isValid : {
+ bg : String(defaultColours.grey50),
+ border : String(defaultColours.red30)
+ }
+ }
+ },
+
+ select : {
+ chevron : String(customColours.hue)
+ },
+
+ radioButton : {
+ inset : {
+ default : {
+ bg : String(defaultColours.slate20)
+ },
+ onHover : {
+ bg : String(defaultColours.slate40)
+ },
+ isSelected : {
+ bg : String(customColours.hue)
+ },
+ isDisabled : {
+ bg : String(defaultColours.slate10)
+ }
+ },
+ circle : {
+ default : {
+ bg : String(defaultColours.white)
+ }
+ }
+ },
+
+ checkBox : {
+ square : {
+ default : {
+ bg : String(defaultColours.slate20)
+ },
+ onHover : {
+ bg : String(defaultColours.slate40)
+ },
+ isChecked : {
+ bg : String(customColours.hue)
+ },
+ isDisabled : {
+ bg : String(defaultColours.slate10)
+ }
+ },
+ check : {
+ default : {
+ border : String(defaultColours.white)
+ }
+ }
+ },
+
+ toggleSwitch : {
+ switch : {
+ default : {
+ bg : String(defaultColours.white)
+ },
+ isChecked : {
+ bg : String(defaultColours.white)
+ }
+ }
+ },
+
+
+ // INFO PANEL ///////////////////////////////////////////////////////////
+ infoPanel : {
+ bg : String(defaultColours.white),
+ border : String(defaultColours.slate20),
+ dismissButton : {
+ color : String(defaultColours.slate90)
+ }
+ },
+
+
+ // NOTIFICATION /////////////////////////////////////////////////////////
+ notification : {
+ default : {
+ bg : String(defaultColours.white),
+ text : String(customColours.shade)
+ },
+ kinds : {
+ info : {
+ border : String(defaultColours.blue60)
+ },
+ warning : {
+ border : String(defaultColours.amber)
+ },
+ error : {
+ border : String(defaultColours.red90)
+ },
+ success : {
+ border : String(defaultColours.green90)
+ }
+ }
+ },
+
+
+ // PROGRESS BAR /////////////////////////////////////////////////////////
+ progressBar : {
+ bg : String(defaultColours.slate20),
+ fill : String(customColours.hue),
+ label : String(customColours.shade),
+ value : String(customColours.shade),
+ borderRadius : "4px"
+ },
+
+
+ // SIDEBAR //////////////////////////////////////////////////////////////
+ sidebar : {
+ width : "240px",
+ bg : String(defaultColours.white),
+
+ isCollapsed : {
+ width : "48px",
+ label : {
+ text : String(defaultColours.white),
+ bg : String(customColours.hue)
+ }
+ },
+
+ header : {
+ bg : String(defaultColours.white),
+ borderBottom : String(defaultColours.slate10),
+ logoWidth : "50%"
+ },
+
+ linksWrapper : {
+ icons : {
+ size : "24px",
+ stroked : {
+ thickness : 2,
+ default : {
+ line : String(defaultColours.slate40)
+ },
+ onHover : {
+ line : String(defaultColours.slate80)
+ },
+ isActive : {
+ line : String(defaultColours.slate)
+ }
+ },
+ filled : {
+ default : {
+ bg : String(defaultColours.slate40)
+ },
+ onHover : {
+ bg : String(defaultColours.slate80)
+ },
+ isActive : {
+ bg : String(defaultColours.slate)
+ }
+ }
+ },
+
+ links : {
+ default : {
+ bg : String(defaultColours.white),
+ text : String(customColours.shade),
+ scale : 100,
+ weight : 600
+ },
+ onHover : {
+ bg : String(defaultColours.slate10),
+ text : String(customColours.hue)
+ },
+ isSelected : {
+ bg : String(defaultColours.white),
+ border : String(customColours.hue),
+ text : String(customColours.hue)
+ },
+ hasAlert : {
+ bg : String(defaultColours.red70)
+ }
+ },
+
+ subLinks : {
+ header : {
+ weight : 600
+ },
+ default : {
+ bg : String(defaultColours.white),
+ text : lighten(0.24, String(customColours.shade)),
+ weight : 400,
+ scale : 92
+ },
+ onHover : {
+ bg : String(defaultColours.slate10),
+ text : String(customColours.hue)
+ },
+ chevron : {
+ border : String(defaultColours.slate40)
+ }
+ },
+ },
+
+ footer : {
+ height : "32px",
+ bg : String(defaultColours.white),
+ borderTop : String(defaultColours.slate10)
+ }
+ },
+
+
+ // TABLE ////////////////////////////////////////////////////////////////
+ table : {
+ bg : String(defaultColours.white),
+ text : String(customColours.shade),
+ border : String(defaultColours.slate40),
+ striped : {
+ header : {
+ bg : String(defaultColours.blue40)
+ },
+ cell : {
+ bg : String(defaultColours.slate20)
+ }
+ },
+ onHover : {
+ bg : String(defaultColours.amber20),
+ text : String(customColours.shade)
+ }
+ },
+
+ tablePagination : {
+ bg : String(defaultColours.white),
+ text : String(defaultColours.grey),
+ svg : {
+ onHover : {
+ stroke : String(defaultColours.slate60)
+ }
+ }
+ },
+
+
+ // TABS ////////////////////////////////////////////////////////////////
+ tabs : {
+ label : {
+ default : {
+ text : lighten(0.16, String(defaultColours.grey))
+ },
+ onHover : {
+ text : lighten(0.16, String(customColours.hue))
+ },
+ isActive : {
+ border : String(customColours.hue),
+ text : String(customColours.hue)
+ },
+ isDisabled : {
+ text : darken(0.24, String(defaultColours.slate))
+ },
+ hasAlert : {
+ circle : {
+ bg : String(defaultColours.red90),
+ border : String(defaultColours.slate10)
+ }
+ }
+ }
+ },
+
+
+ // TEXT /////////////////////////////////////////////////////////////////
+ text : {
+ font : {
+ sans : "sans-serif",
+ serif : "serif",
+ mono : "monospace"
+ },
+
+ paras : {
+ font : "sans-serif",
+ size : 1,
+ color : String(defaultColours.grey),
+ weight : 400,
+ lineHeight : 1.64
+ },
+
+ headings : {
+ font : "sans-serif",
+ color : String(customColours.shade),
+ weight : 600,
+ multiplier : 1.24,
+ lineHeight : 1.24
+ },
+
+ links : {
+ font : "sans-serif",
+ default : {
+ color : String(defaultColours.blue90)
+ },
+ onHover : {
+ color : String(defaultColours.blue60)
+ }
+ },
+
+ selection : {
+ bg : String(customColours.hue),
+ text : String(defaultColours.white)
+ },
+
+ code : {
+ inline : {
+ bg : String(defaultColours.blue10),
+ text : String(defaultColours.blue90),
+ scale : 80
+ },
+ block : {
+ bg : lighten(0.02, String(defaultColours.slate10)),
+ text : String(defaultColours.blue70),
+ scale : 80,
+ lineHeight : 1.8
+ },
+ prism : {
+ tokens : {
+ tag : String(defaultColours.violet),
+ atrule : String(defaultColours.teal90),
+ attrName : String(defaultColours.orange),
+ attrValue : String(defaultColours.green80),
+ boolean : String(defaultColours.green80),
+ cdata : String(defaultColours.grey70),
+ className : String(defaultColours.red),
+ comment : String(defaultColours.grey70),
+ constant : String(defaultColours.green80),
+ deleted : String(defaultColours.slate80),
+ delimiter : String(defaultColours.grey90),
+ doctype : String(defaultColours.grey90),
+ entity : String(defaultColours.green80),
+ function : String(defaultColours.orange),
+ hexcode : String(defaultColours.green),
+ inserted : String(defaultColours.green80),
+ italic : String(defaultColours.green80),
+ keyword : String(defaultColours.orange90),
+ namespace : String(defaultColours.green80),
+ number : String(defaultColours.green80),
+ operator : String(defaultColours.pistachio),
+ plain : String(defaultColours.grey),
+ prolog : String(defaultColours.grey90),
+ property : String(defaultColours.red90),
+ punctuation : String(defaultColours.grey60),
+ regex : String(defaultColours.green80),
+ selector : String(defaultColours.violet),
+ string : String(defaultColours.crimson60),
+ symbol : String(defaultColours.green80),
+ url : String(defaultColours.green80),
+ variable : String(defaultColours.orange80)
+ },
+
+ languages : {
+ css : {
+ fallback : String(defaultColours.orange90)
+ },
+ html : {
+ fallback : String(defaultColours.grey)
+ },
+ js : {
+ fallback : String(defaultColours.violet90)
+ },
+ json : {
+ fallback : String(defaultColours.teal),
+ tokens : {
+ string : String(defaultColours.teal)
+ }
+ }
+ }
+ }
+ },
+
+ kbd : {
+ text : String(defaultColours.grey),
+ bg : String(defaultColours.grey10)
+ }
+ },
+
+ spinner: {
+ color: String(defaultColours.teal),
+ }
+}
+`;
diff --git a/src/pages/03ThemeBuilder/ThemeEditor.styled.tsx b/src/pages/03ThemeBuilder/ThemeEditor.styled.tsx
new file mode 100644
index 0000000..7c40140
--- /dev/null
+++ b/src/pages/03ThemeBuilder/ThemeEditor.styled.tsx
@@ -0,0 +1,5 @@
+import styled from "styled-components";
+
+
+export const ThemeEditorStyled = styled.article`
+`
diff --git a/src/pages/03ThemeBuilder/ThemeEditor.tsx b/src/pages/03ThemeBuilder/ThemeEditor.tsx
new file mode 100644
index 0000000..3c7e2e4
--- /dev/null
+++ b/src/pages/03ThemeBuilder/ThemeEditor.tsx
@@ -0,0 +1,60 @@
+import React, { useEffect, useRef, useState } from "react";
+import {
+ Element,
+ Portion,
+ Row,
+ CodeBlock,
+ Heading,
+ Text, Card
+} from "fictoan-react";
+
+import { ThemeEditorStyled } from "./ThemeEditor.styled";
+import { FictoanThemeOutputSample } from "./Result";
+import { Editor } from "./Editor";
+import {STRINGIFIED_FICTOAN_THEME} from "./Theme";
+import { evaluateJS, performDiff } from "./CodeManipulation";
+
+
+export const ThemeEditorDocs = () => {
+ useEffect(() => {
+ document.title = "Theme editor — Fictoan";
+
+ try {
+ window.scroll({
+ top : 0,
+ left : 0
+ });
+ } catch (error) {
+ window.scrollTo(0, 0);
+ }
+ }, []);
+
+ const [json, setJson] = useState(() => evaluateJS(STRINGIFIED_FICTOAN_THEME));
+ const [min, setMin] = useState("");
+
+ return (
+
+
+
+ Theme editor
+
+ Use this page to edit the default theme that comes with Fictoan to suit your needs.
+
+
+
+
+
+
+
+
+
+ {/**/}
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/pages/03ThemeBuilder/Visitor.ts b/src/pages/03ThemeBuilder/Visitor.ts
new file mode 100644
index 0000000..8bcb18f
--- /dev/null
+++ b/src/pages/03ThemeBuilder/Visitor.ts
@@ -0,0 +1,179 @@
+/**
+ * Acorn AST node visitor
+ */
+
+import acorn, { Parser, defaultOptions } from "acorn";
+
+interface ProgramNode extends acorn.Node {
+ body: acorn.Node[]
+}
+interface VariableDeclarationNode extends acorn.Node {
+ declarations: VariableDeclaratorNode[]
+}
+interface VariableDeclaratorNode extends acorn.Node {
+ id: IdentifierNode,
+ init: any
+}
+interface IdentifierNode extends acorn.Node {
+ name: string;
+}
+interface LiteralNode extends acorn.Node {
+ value: String;
+}
+interface ObjectExpressionNode extends acorn.Node {
+ properties: ObjectPropertyNode[]
+}
+interface ObjectPropertyNode extends acorn.Node {
+ key: IdentifierNode;
+ value: ObjectExpressionNode | IdentifierNode;
+}
+interface MemberExpressionNode extends acorn.Node {
+ object: MemberExpressionNode | IdentifierNode;
+ property: IdentifierNode;
+}
+interface CallExpressionNode extends acorn.Node {
+ arguments: acorn.Node[];
+ callee: IdentifierNode;
+}
+
+
+
+export class Visitor {
+ constructor() {
+ this.visitNode = this.visitNode.bind(this)
+ }
+ visitVariableDeclaration(node: VariableDeclarationNode) {
+ return this.visitNodes(node.declarations);
+ }
+ visitVariableDeclarator(node: VariableDeclaratorNode) {
+ this.visitNode(node.id);
+ this.visitNode(node.init);
+ return node.init;
+ }
+ visitIdentifier(node: IdentifierNode) {
+ return node.name
+ }
+ visitLiteral(node: LiteralNode) {
+ return node.value;
+ }
+ visitMemberExpression(node: MemberExpressionNode): any {
+ return [this.visitNode(node.object), this.visitNode(node.property)];
+ }
+ visitObjectExpression(node: ObjectExpressionNode): any {
+ return node.properties.map(this.visitNode);
+ }
+ visitObjectProperty(node: ObjectPropertyNode) {
+ this.visitNode(node.key);
+ this.visitNode(node.value);
+ }
+ visitCallExpression(node: CallExpressionNode) {
+ this.visitNode(node.callee);
+ this.visitNodes(node.arguments);
+ }
+ visitProgram(node: ProgramNode): any {
+ return node.body.map(this.visitNode)
+ }
+ visitNodes(nodes: acorn.Node[]): any {
+ return nodes.map(this.visitNode);
+ }
+ visitNode(node: acorn.Node) {
+ if (node.type == 'Program')
+ return this.visitProgram(node as ProgramNode);
+ if (node.type == 'VariableDeclaration')
+ return this.visitVariableDeclaration(node as VariableDeclarationNode);
+ if (node.type == 'VariableDeclarator')
+ return this.visitVariableDeclarator(node as VariableDeclaratorNode);
+ if (node.type == 'MemberExpression')
+ return this.visitMemberExpression(node as MemberExpressionNode)
+ if (node.type == 'ObjectExpression')
+ return this.visitObjectExpression(node as ObjectExpressionNode);
+ if (node.type == 'Property')
+ return this.visitObjectProperty(node as ObjectPropertyNode);
+ if (node.type == 'Identifier')
+ return this.visitIdentifier(node as IdentifierNode);
+ if (node.type == 'Literal')
+ return this.visitLiteral(node as LiteralNode);
+ }
+}
+
+
+
+export class ThemeVisitor extends Visitor {
+ key: string = '';
+ sourceCodeString: string = '';
+ astRoot: acorn.Node;
+ constructor(sourceCodeString: string) {
+ super();
+ this.sourceCodeString = sourceCodeString;
+ this.astRoot = Parser.parse(sourceCodeString, defaultOptions);
+ }
+ __toString(node: acorn.Node) {
+ return this.sourceCodeString.substring(node.start, node.end);
+ }
+ visitProgram(node: ProgramNode) {
+ return super.visitProgram(node).filter(filterOut)[0]
+ }
+ visitObjectExpression(node: ObjectExpressionNode): any {
+ return super.visitObjectExpression(node).filter(filterOut)[0]
+ }
+ visitNodes(nodes: acorn.Node[]) {
+ return super.visitNodes(nodes).filter(filterOut)[0]
+ }
+ visitVariableDeclarator(node: VariableDeclaratorNode) {
+ if (node.id.name == this.key)
+ return node.init;
+ }
+ visitObjectProperty(node: ObjectPropertyNode) {
+ if (node.key.name == this.key)
+ return node.value
+ }
+
+ visitCallExpression(node: CallExpressionNode) {
+ return this.__toString(node);
+ }
+ visitMemberExpression(node: MemberExpressionNode) {
+ return this.__toString(node);
+ }
+
+ visitPath(node: acorn.Node, path: string[]): any {
+ if (path.length == 0)
+ return node;
+ this.key = path.shift()!;
+ const nextNode = this.visitNode(node);
+ return this.visitPath(nextNode, path);
+ }
+ getValueAtPath(path: string[]): string {
+ const node = this.visitPath(this.astRoot, path);
+ return this.__toString(node);
+ }
+}
+
+export class JSVisitor extends Visitor {
+ sourceCodeString: string = '';
+ astRoot: acorn.Node;
+ constructor(sourceCodeString: string) {
+ super();
+ this.sourceCodeString = sourceCodeString;
+ this.astRoot = Parser.parse(sourceCodeString, defaultOptions);
+ }
+ __toString(node: acorn.Node) {
+ return this.sourceCodeString.substring(node.start, node.end);
+ }
+ visitVariableDeclaration(node: VariableDeclarationNode) {
+ return this.__toString(node);
+ }
+ getAllVariableDeclarations(){
+ return this.visitNode(this.astRoot).filter((declaration:string) => !declaration.startsWith("const Theme"))
+ }
+}
+
+function filterOut(val: Array | any) {
+ if (val == undefined)
+ return false;
+ if (val == [])
+ return false;
+ if (val.map) {
+ return val.filter(filterOut).length == 0 ? false : true
+ }
+ return true;
+}
\ No newline at end of file
diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts
new file mode 100644
index 0000000..6431bc5
--- /dev/null
+++ b/src/react-app-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..a273b0c
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noFallthroughCasesInSwitch": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx"
+ },
+ "include": [
+ "src"
+ ]
+}