Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Generate sharable link for code-explorer #24

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 59 additions & 28 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,81 @@
import React from 'react';
import { useLayoutEffect } from 'react';
import './App.css';
import { Navbar } from './components/navbar';
import { useExplorer } from './hooks/use-explorer';
import { tools } from './lib/tools';
import { Editor } from './components/editor';
import { ToolSelector } from './components/tool-selector';
import { cn } from './lib/utils';
import { ThemeProvider, useTheme } from './components/theme-provider';
import { ThemeProvider } from './components/theme-provider';
import { decodeFromBase64 } from './lib/utils';

function App() {
const { theme } = useTheme();
const { language, tool, code, setCode } = useExplorer();
const { setTool, language, tool, JSCode, setJSCode, JSONCode, setJSONCode, setLanguage, setParser,
setSourceType, setEsVersion, setIsJSX, setJsonMode, setWrap, setAstViewMode, setScopeViewMode,
setPathViewMode, setPathIndexes, setPathIndex } = useExplorer();
const activeTool = tools.find(({ value }) => value === tool) ?? tools[0];

useLayoutEffect(() => {
const getUrlState = () => {
try {
const urlState = JSON.parse(decodeFromBase64(window.location.hash.replace(/^#/u, "")));
if (urlState?.state) {
const { tool, JSCode, JSONCode, language, parser, sourceType, esVersion, isJSX,
jsonMode, wrap, astViewMode, scopeViewMode, pathViewMode, pathIndexes, pathIndex } = urlState.state;

setTool(tool);
setJSCode(JSCode);
setJSONCode(JSONCode);
setLanguage(language);
setParser(parser);
setSourceType(sourceType);
setEsVersion(esVersion);
setIsJSX(isJSX);
setJsonMode(jsonMode);
setWrap(wrap);
setAstViewMode(astViewMode);
setScopeViewMode(scopeViewMode);
setPathViewMode(pathViewMode);
setPathIndexes(pathIndexes);
setPathIndex(pathIndex);
}
} catch {
console.error('error while parsing');
}
};
getUrlState();
}, [])

return (
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
<div
className={"antialiased touch-manipulation font-sans"}
>
<div className="flex flex-col h-screen">
<Navbar />
<div className="h-full overflow-hidden">
<div className="grid sm:grid-cols-2 divide-x border-t h-full">
<Editor
className="h-[30dvh] sm:h-full"
language={language}
defaultValue={code}
onChange={(value) => setCode(value ?? '')}
/>
<div className="bg-foreground/5 pb-8 overflow-auto h-[70dvh] sm:h-full relative flex flex-col">
<div className="flex sm:items-center flex-col sm:flex-row justify-between p-4 gap-2 z-10">
<ToolSelector />
<div className="flex items-center gap-1">
{activeTool.options.map((Option, index) => (
<Option key={index} />
))}
<div className="antialiased touch-manipulation font-sans">
<div className="flex flex-col h-screen">
<Navbar />
<div className="h-full overflow-hidden">
<div className="grid sm:grid-cols-2 divide-x border-t h-full">
<Editor
className="h-[30dvh] sm:h-full"
language={language}
value={language === 'javascript' ? JSCode : JSONCode}
onChange={(value) => {
language === 'javascript' ? setJSCode(value ?? '') : setJSONCode(value ?? '')
}}
/>
<div className="bg-foreground/5 pb-8 overflow-auto h-[70dvh] sm:h-full relative flex flex-col">
<div className="flex sm:items-center flex-col sm:flex-row justify-between p-4 gap-2 z-10">
<ToolSelector />
<div className="flex items-center gap-1">
{activeTool.options.map((Option, index) => (
<Option key={index} />
))}
</div>
</div>
<activeTool.component />
</div>
<activeTool.component />
</div>
</div>
</div>
</div>
</div>
</ThemeProvider>

);
}

Expand Down
2 changes: 1 addition & 1 deletion src/components/ast/javascript-ast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const JavascriptAst: FC = () => {
let tree: ReturnType<typeof espree.parse> | null = null;

try {
tree = espree.parse(explorer.code, {
tree = espree.parse(explorer.JSCode, {
ecmaVersion: explorer.esVersion,
sourceType: explorer.sourceType,
});
Expand Down
2 changes: 1 addition & 1 deletion src/components/ast/json-ast.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const JsonAst: FC = () => {
let tree: ReturnType<typeof parse> | null = null;

try {
tree = parse(explorer.code, {
tree = parse(explorer.JSONCode, {
mode: explorer.jsonMode,
ranges: true,
tokens: true,
Expand Down
6 changes: 5 additions & 1 deletion src/components/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import { useTheme } from './theme-provider';

type EditorProperties = ComponentProps<typeof MonacoEditor> & {
readOnly?: boolean;
value?: string;
onChange?: (value: string) => void;
};

export const Editor: FC<EditorProperties> = ({ readOnly, ...properties }) => {
export const Editor: FC<EditorProperties> = ({ readOnly, value, onChange, ...properties }) => {
const { theme } = useTheme();
const explorer = useExplorer();

Expand Down Expand Up @@ -43,7 +45,9 @@ export const Editor: FC<EditorProperties> = ({ readOnly, ...properties }) => {
readOnly: readOnly ?? false,
}}
theme={theme === 'dark' ? 'eslint-dark' : 'eslint-light'}
value={value}
{...properties}
onChange={onChange}
/>
);
};
1 change: 1 addition & 0 deletions src/components/options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { Button } from './ui/button';
import { Label } from './ui/label';
import type { FC } from 'react';
import { Settings } from 'lucide-react';
import { defaultJSONCode, defaultJavascriptCode } from '../lib/const';

export const Options: FC = () => {
const explorer = useExplorer();
Expand Down
4 changes: 2 additions & 2 deletions src/components/path/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const CodePath: FC = () => {

useDebouncedEffect(
() => {
generateCodePath(explorer.code, explorer.esVersion, explorer.sourceType)
generateCodePath(explorer.JSCode, explorer.esVersion, explorer.sourceType)
.then((response) => {
if ('error' in response) {
throw new Error(response.error);
Expand All @@ -45,7 +45,7 @@ export const CodePath: FC = () => {
.catch((newError) => setError(parseError(newError)));
},
500,
[explorer, explorer.code, explorer.esVersion, explorer.sourceType, explorer.setPathIndexes, explorer.pathIndexes]
[explorer, explorer.JSCode, explorer.esVersion, explorer.sourceType, explorer.setPathIndexes, explorer.pathIndexes]
);

if (error) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/scope/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const Scope: FC = () => {
let ast = {};

try {
ast = espree.parse(explorer.code, {
ast = espree.parse(explorer.JSCode, {
range: true,
ecmaVersion: explorer.esVersion,
sourceType: explorer.sourceType,
Expand Down
70 changes: 40 additions & 30 deletions src/hooks/use-explorer.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import type { Options } from 'espree';

import { defaultJavascriptCode, defaultJSONCode } from '../lib/const'
import { storeState } from '../lib/utils';
export type SourceType = Exclude<Options['sourceType'], undefined>;
export type Version = Exclude<Options['ecmaVersion'], undefined>;

type ExplorerState = {
tool: 'ast' | 'scope' | 'path';
setTool: (tool: ExplorerState['tool']) => void;

code: string;
setCode: (code: string) => void;
JSCode: string;
setJSCode: (JSCode: string) => void;

JSONCode: string;
setJSONCode: (JSONCode: string) => void;

language: string;
setLanguage: (language: string) => void;
Expand All @@ -19,7 +23,7 @@ type ExplorerState = {
setParser: (parser: string) => void;

sourceType: SourceType;
setSourceType: (sourceType: string) => void;
setSourceType: (sourceType: SourceType) => void;

esVersion: Version;
setEsVersion: (esVersion: string) => void;
Expand Down Expand Up @@ -50,63 +54,69 @@ type ExplorerState = {

};

const createSetter = <T extends keyof ExplorerState>(
Copy link
Member

@harish-sethuraman harish-sethuraman Aug 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This way is in efficient. Zustand has a better way with persist middleware to store data in localstorage. https://docs.pmnd.rs/zustand/integrations/persisting-store-data We can use that instead of manually maintaining all of these operations? my bad its already there

key: T,
set: (state: Partial<ExplorerState>) => void
) => (value: ExplorerState[T]) => {
set({ [key]: value });
storeState();
};

export const useExplorer = create<ExplorerState>()(
devtools(
persist(
(set) => ({
tool: 'ast',
setTool: (tool) => set({ tool }),
setTool: createSetter('tool', set),

JSCode: defaultJavascriptCode,
setJSCode: createSetter('JSCode', set),

code: `const a = 'b';`,
setCode: (code) => set({ code }),
JSONCode: defaultJSONCode,
setJSONCode: createSetter('JSONCode', set),

language: 'javascript',
setLanguage: (language) => set({ language }),
setLanguage: createSetter('language', set),

parser: 'espree',
setParser: (parser) => set({ parser }),
setParser: createSetter('parser', set),

sourceType: 'commonjs',
setSourceType: (sourceType) =>
set({ sourceType: sourceType as SourceType }),
sourceType: 'module',
setSourceType: createSetter('sourceType', set),

esVersion: 'latest',
setEsVersion: (esVersion) =>
set({
esVersion:
esVersion === 'latest'
? 'latest'
: (Number(esVersion) as Options['ecmaVersion']),
}),
setEsVersion: (esVersion) => {
const version = esVersion === 'latest' ? 'latest' : Number(esVersion);
createSetter('esVersion', set)(version as Version);
},

isJSX: true,
setIsJSX: (isJSX) => set({ isJSX }),
setIsJSX: createSetter('isJSX', set),

jsonMode: 'json',
setJsonMode: (mode) => set({ jsonMode: mode }),
jsonMode: 'jsonc',
setJsonMode: createSetter('jsonMode', set),

wrap: true,
setWrap: (wrap) => set({ wrap }),
setWrap: createSetter('wrap', set),

astViewMode: 'json',
setAstViewMode: (mode) => set({ astViewMode: mode }),
setAstViewMode: createSetter('astViewMode', set),

scopeViewMode: 'flat',
setScopeViewMode: (mode) => set({ scopeViewMode: mode }),
setScopeViewMode: createSetter('scopeViewMode', set),

pathViewMode: 'code',
setPathViewMode: (mode) => set({ pathViewMode: mode }),
setPathViewMode: createSetter('pathViewMode', set),

pathIndexes: 1,
setPathIndexes: (indexes) => set({ pathIndexes: indexes }),
setPathIndexes: createSetter('pathIndexes', set),

pathIndex: 0,
setPathIndex: (index) => set({ pathIndex: index }),

setPathIndex: createSetter('pathIndex', set),
}),
{
name: 'eslint-explorer',
}
)
)
);
);
62 changes: 62 additions & 0 deletions src/lib/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,65 @@ export const pathViewOptions = [
icon: GitGraphIcon,
},
];

export const defaultJavascriptCode = `
/**
* Type or paste some JavaScript here to learn more about
* the static analysis that ESLint can do for you.
*
* The three tabs are:
*
* - AST - The Abstract Syntax Tree of the code, which can
* be useful to understand the structure of the code. You
* can view this structure as JSON or in a tree format.
* - Scope - The scope structure of the code, which can be
* useful to understand how variables are defined and
* where they are used.
* - Code Path - The code path structure of the code, which
* can be useful to understand how the code is executed.
*
* You can change the way that the JavaScript code is interpreted
* by clicking "JavaScript" in the header and selecting different
* options.
*/

import js from "@eslint/js";

function getConfig() {
return {
rules: {
"prefer-const": "warn"
}
};
}

export default [
...js.configs.recommended,
getConfig()
];`;

export const defaultJSONCode = `
/**
* Type or paste some JSON here to learn more about
* the static analysis that ESLint can do for you.
*
* The tabs are:
*
* - AST - The Abstract Syntax Tree of the code, which can
* be useful to understand the structure of the code. You
* can view this structure as JSON or in a tree format.
*
* You can change the way that the JSON code is interpreted
* by clicking "JSON" in the header and selecting different
* options.
*
* This example is in JSONC mode, which allows comments.
*/

{
"key1": [true, false, null],
"key2": {
"key3": [1, 2, "3", 1e10, 1e-3]
}
}
`
Loading