pull/5370/head
Solid Studio 4 years ago committed by Joseph Izang
parent eb7991a36d
commit c1dd4d748e
  1. 124
      src/App.tsx
  2. 20
      src/AppContext.tsx
  3. 24
      src/hooks/useLocalStorage.tsx
  4. 12
      src/index.tsx
  5. 49
      src/routes.tsx
  6. 42
      src/utils/faker.ts
  7. 4
      src/utils/index.ts
  8. 36
      src/utils/publisher.test.ts
  9. 17
      src/utils/publisher.ts
  10. 103
      src/utils/template.ts
  11. 43
      src/utils/types.ts
  12. 192
      src/utils/utils.test.ts
  13. 214
      src/utils/utils.ts
  14. 13
      src/views/ErrorView.tsx
  15. 170
      src/views/HomeView.tsx
  16. 4
      src/views/index.ts

@ -1,71 +1,88 @@
import React, { useState, useEffect, useRef } from "react" import React, { useState, useEffect, useRef } from "react";
import { import {
createIframeClient, createIframeClient,
CompilationFileSources, CompilationFileSources,
CompilationResult, CompilationResult,
Status Status,
} from "@remixproject/plugin" } from "@remixproject/plugin";
import { AppContext } from "./AppContext" import { AppContext } from "./AppContext";
import { Routes } from "./routes" import { Routes } from "./routes";
import { useLocalStorage } from "./hooks/useLocalStorage" import { useLocalStorage } from "./hooks/useLocalStorage";
import { createDocumentation } from "./utils/utils" import { createDocumentation } from "./utils/utils";
import "./App.css" import "./App.css";
import { ContractName, Documentation } from "./types" import { ContractName, Documentation } from "./types";
const devMode = { port: 8080 } const devMode = { port: 8080 };
export const getNewContractNames = (compilationResult: CompilationResult) => { export const getNewContractNames = (compilationResult: CompilationResult) => {
const compiledContracts = compilationResult.contracts const compiledContracts = compilationResult.contracts;
let result: string[] = [] let result: string[] = [];
for (const file of Object.keys(compiledContracts)) { for (const file of Object.keys(compiledContracts)) {
const newContractNames = Object.keys(compiledContracts[file]) const newContractNames = Object.keys(compiledContracts[file]);
result = [...result, ...newContractNames] result = [...result, ...newContractNames];
} }
return result return result;
} };
const sampleMap = new Map<ContractName, Documentation>() const sampleMap = new Map<ContractName, Documentation>();
const App = () => { const App = () => {
const [clientInstance, setClientInstance] = useState(undefined as any) const [clientInstance, setClientInstance] = useState(undefined as any);
const [contracts, setContracts] = useState(sampleMap) const [contracts, setContracts] = useState(sampleMap);
const [sites, setSites] = useLocalStorage('sites', []) const [sites, setSites] = useLocalStorage("sites", []);
const clientInstanceRef = useRef(clientInstance) const clientInstanceRef = useRef(clientInstance);
clientInstanceRef.current = clientInstance clientInstanceRef.current = clientInstance;
const contractsRef = useRef(contracts) const contractsRef = useRef(contracts);
contractsRef.current = contracts contractsRef.current = contracts;
useEffect(() => { useEffect(() => {
console.log("Remix EthDoc loading...") console.log("Remix EthDoc loading...");
const client = createIframeClient({ devMode }) const client = createIframeClient({ devMode });
const loadClient = async () => { const loadClient = async () => {
await client.onload() await client.onload();
setClientInstance(client) setClientInstance(client);
console.log("Remix EthDoc Plugin has been loaded") console.log("Remix EthDoc Plugin has been loaded");
client.solidity.on("compilationFinished", (fileName: string, source: CompilationFileSources, languageVersion: string, data: CompilationResult) => { client.solidity.on(
console.log("New compilation received") "compilationFinished",
(
const existingMap = contractsRef.current; fileName: string,
const newContractsMapWithDocumentation = createDocumentation(fileName, data) source: CompilationFileSources,
const newMap = new Map([...existingMap, ...newContractsMapWithDocumentation]) languageVersion: string,
data: CompilationResult
console.log("New Map", newMap) ) => {
console.log("New compilation received");
const status: Status = { key: 'succeed', type: 'success', title: 'New documentation ready' }
clientInstanceRef.current.emit('statusChanged', status) const existingMap = contractsRef.current;
setContracts(newMap) const newContractsMapWithDocumentation = createDocumentation(
}) fileName,
} data
);
loadClient() const newMap = new Map([
}, []) ...existingMap,
...newContractsMapWithDocumentation,
]);
console.log("New Map", newMap);
const status: Status = {
key: "succeed",
type: "success",
title: "New documentation ready",
};
clientInstanceRef.current.emit("statusChanged", status);
setContracts(newMap);
}
);
};
loadClient();
}, []);
return ( return (
<AppContext.Provider <AppContext.Provider
@ -74,11 +91,12 @@ const App = () => {
contracts, contracts,
setContracts, setContracts,
sites, sites,
setSites setSites,
}}> }}
>
<Routes /> <Routes />
</AppContext.Provider> </AppContext.Provider>
) );
} };
export default App export default App;

@ -1,17 +1,23 @@
import React from "react" import React from "react";
import { PluginApi, IRemixApi, Api, PluginClient, CompilationResult } from "@remixproject/plugin" import {
PluginApi,
IRemixApi,
Api,
PluginClient,
CompilationResult,
} from "@remixproject/plugin";
import { ContractName, Documentation, PublishedSite } from "./types" import { ContractName, Documentation, PublishedSite } from "./types";
export const AppContext = React.createContext({ export const AppContext = React.createContext({
clientInstance: {} as PluginApi<Readonly<IRemixApi>> & clientInstance: {} as PluginApi<Readonly<IRemixApi>> &
PluginClient<Api, Readonly<IRemixApi>>, PluginClient<Api, Readonly<IRemixApi>>,
contracts: new Map<ContractName, Documentation>(), contracts: new Map<ContractName, Documentation>(),
setContracts: (contracts: Map<ContractName, Documentation>) => { setContracts: (contracts: Map<ContractName, Documentation>) => {
console.log("Calling Set Contract Names") console.log("Calling Set Contract Names");
}, },
sites: [], sites: [],
setSites: (sites: PublishedSite[]) => { setSites: (sites: PublishedSite[]) => {
console.log("Calling Set Sites") console.log("Calling Set Sites");
} },
}) });

@ -1,4 +1,4 @@
import { useState } from "react" import { useState } from "react";
export function useLocalStorage(key: string, initialValue: any) { export function useLocalStorage(key: string, initialValue: any) {
// State to store our value // State to store our value
@ -6,15 +6,15 @@ export function useLocalStorage(key: string, initialValue: any) {
const [storedValue, setStoredValue] = useState(() => { const [storedValue, setStoredValue] = useState(() => {
try { try {
// Get from local storage by key // Get from local storage by key
const item = window.localStorage.getItem(key) const item = window.localStorage.getItem(key);
// Parse stored json or if none return initialValue // Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue return item ? JSON.parse(item) : initialValue;
} catch (error) { } catch (error) {
// If error also return initialValue // If error also return initialValue
console.log(error) console.log(error);
return initialValue return initialValue;
} }
}) });
// Return a wrapped version of useState's setter function that ... // Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage. // ... persists the new value to localStorage.
@ -22,16 +22,16 @@ export function useLocalStorage(key: string, initialValue: any) {
try { try {
// Allow value to be a function so we have same API as useState // Allow value to be a function so we have same API as useState
const valueToStore = const valueToStore =
value instanceof Function ? value(storedValue) : value value instanceof Function ? value(storedValue) : value;
// Save state // Save state
setStoredValue(valueToStore) setStoredValue(valueToStore);
// Save to local storage // Save to local storage
window.localStorage.setItem(key, JSON.stringify(valueToStore)) window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) { } catch (error) {
// A more advanced implementation would handle the error case // A more advanced implementation would handle the error case
console.log(error) console.log(error);
} }
} };
return [storedValue, setValue] return [storedValue, setValue];
} }

@ -1,11 +1,11 @@
import React from 'react'; import React from "react";
import ReactDOM from 'react-dom'; import ReactDOM from "react-dom";
import App from './App'; import App from "./App";
import { Routes } from './routes' import { Routes } from "./routes";
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode>, </React.StrictMode>,
document.getElementById('root') document.getElementById("root")
); );

@ -1,36 +1,31 @@
import React from "react" import React from "react";
import { import {
BrowserRouter as Router, BrowserRouter as Router,
Switch, Switch,
Route, Route,
RouteProps, RouteProps,
} from "react-router-dom" } from "react-router-dom";
import { ErrorView, HomeView } from "./views" import { ErrorView, HomeView } from "./views";
interface Props extends RouteProps { interface Props extends RouteProps {
component: any // TODO: new (props: any) => React.Component component: any; // TODO: new (props: any) => React.Component
from: string from: string;
} }
const CustomRoute = ({ component: Component, ...rest }: Props) => { const CustomRoute = ({ component: Component, ...rest }: Props) => {
return ( return (
<Route <Route {...rest} render={(matchProps) => <Component {...matchProps} />} />
{...rest} );
render={(matchProps) => ( };
<Component {...matchProps} />
)}
/>
)
}
export const Routes = () => ( export const Routes = () => (
<Router> <Router>
<Switch> <Switch>
<CustomRoute exact path="/" component={HomeView} from="/" /> <CustomRoute exact path="/" component={HomeView} from="/" />
<Route path="/error"> <Route path="/error">
<ErrorView /> <ErrorView />
</Route> </Route>
</Switch> </Switch>
</Router> </Router>
) );

@ -1,30 +1,30 @@
import { CompiledContract, ABIParameter } from '@remixproject/plugin' import { CompiledContract, ABIParameter } from "@remixproject/plugin";
import sampleData from './sample-data/sample-artifact.json' import sampleData from "./sample-data/sample-artifact.json";
import sampleDataWithComments from './sample-data/sample-artifact-with-comments.json' import sampleDataWithComments from "./sample-data/sample-artifact-with-comments.json";
export const buildFakeArtifact: () => CompiledContract = () => { export const buildFakeArtifact: () => CompiledContract = () => {
const result = sampleData as never as CompiledContract const result = (sampleData as never) as CompiledContract;
return result return result;
} };
export const buildFakeArtifactWithComments: () => CompiledContract = () => { export const buildFakeArtifactWithComments: () => CompiledContract = () => {
const result = sampleDataWithComments as never as CompiledContract const result = (sampleDataWithComments as never) as CompiledContract;
return result return result;
} };
export const buildFakeABIParameter: () => ABIParameter = () => { export const buildFakeABIParameter: () => ABIParameter = () => {
return { return {
internalType: "address", internalType: "address",
name: "allocator", name: "allocator",
type: "address" type: "address",
} };
} };
export const buildFakeABIParameterWithDocumentation: () => ABIParameter = () => { export const buildFakeABIParameterWithDocumentation: () => ABIParameter = () => {
return { return {
internalType: "address", internalType: "address",
name: "allocator", name: "allocator",
type: "address" type: "address",
} };
} };

@ -1,2 +1,2 @@
export * from './utils' export * from "./utils";
export * from './publisher' export * from "./publisher";

@ -1,19 +1,19 @@
import { publish } from './publisher'; import { publish } from "./publisher";
const open = require('open') // tslint:disable-next-line
const open = require("open");
jest.setTimeout(10000) jest.setTimeout(10000);
describe('Publisher tests', () => { describe("Publisher tests", () => {
test("it can publish", async () => {
const result = await publish("hello 123");
test('it can publish', async () => { expect(result).toBeDefined();
const result = await publish("hello 123") });
expect(result).toBeDefined() test("it can publish html", async () => {
}) const result = await publish(`
test('it can publish html', async () => {
const result = await publish(`
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
@ -27,14 +27,14 @@ describe('Publisher tests', () => {
<div>Content custom</div> <div>Content custom</div>
</body> </body>
</html> </html>
`) `);
// Uncomment for testing // Uncomment for testing
// const url = `https://ipfs.io/ipfs/${result}`; // const url = `https://ipfs.io/ipfs/${result}`;
// await open(url, { app: ['google chrome', '--incognito'] }); // await open(url, { app: ['google chrome', '--incognito'] });
expect(result).toBeDefined() expect(result).toBeDefined();
}) });
}) });

@ -1,13 +1,18 @@
import { HTMLContent } from "./types"; import { HTMLContent } from "./types";
const IpfsClient = require('ipfs-mini') // tslint:disable-next-line
const IpfsClient = require("ipfs-mini");
export const publish = async (content: HTMLContent) => { export const publish = async (content: HTMLContent) => {
const ipfs = new IpfsClient({ host: 'ipfs.infura.io', port: 5001, protocol: 'https' }); const ipfs = new IpfsClient({
host: "ipfs.infura.io",
port: 5001,
protocol: "https",
});
const documentHash = await ipfs.add(content) const documentHash = await ipfs.add(content);
console.log("Document hash", documentHash) console.log("Document hash", documentHash);
return documentHash return documentHash;
} };

@ -1,5 +1,12 @@
import { FunctionDocumentation, TemplateDoc, MethodDoc, ContractDoc, ContractDocumentation, ParameterDocumentation } from "./types" import {
type HTMLContent = string FunctionDocumentation,
TemplateDoc,
MethodDoc,
ContractDoc,
ContractDocumentation,
ParameterDocumentation,
} from "./types";
type HTMLContent = string;
export const htmlTemplate = (content: HTMLContent) => ` export const htmlTemplate = (content: HTMLContent) => `
<!DOCTYPE html> <!DOCTYPE html>
@ -16,9 +23,13 @@ export const htmlTemplate = (content: HTMLContent) => `
${content} ${content}
</body> </body>
</html> </html>
` `;
export const template = (name: string, contractDoc: ContractDocumentation, functions: FunctionDocumentation[]) => ` export const template = (
name: string,
contractDoc: ContractDocumentation,
functions: FunctionDocumentation[]
) => `
<style> <style>
#ethdoc-viewer{ #ethdoc-viewer{
font-size: 0.8em; font-size: 0.8em;
@ -41,9 +52,15 @@ export const template = (name: string, contractDoc: ContractDocumentation, funct
<div id="ethdoc-viewer"> <div id="ethdoc-viewer">
${functions.length == 0 ? "No contract to display" : renderHeader(name, contractDoc)} ${
functions.length === 0
? "No contract to display"
: renderHeader(name, contractDoc)
}
${functions.map((item) => ` ${functions
.map(
(item) => `
<h6>${item.name} - ${item.type}</h6> <h6>${item.name} - ${item.type}</h6>
<hr> <hr>
${renderParameterDocumentation(item.inputs)} ${renderParameterDocumentation(item.inputs)}
@ -54,10 +71,12 @@ export const template = (name: string, contractDoc: ContractDocumentation, funct
${renderParameterDocumentation(item.outputs)} ${renderParameterDocumentation(item.outputs)}
`).join('\n')} `
)
.join("\n")}
</div> </div>
` `;
// const contractDocTemplate: TemplateDoc<ContractDoc> = { // const contractDocTemplate: TemplateDoc<ContractDoc> = {
// author: (author: string) => '',//`Author: ${author}`, // author: (author: string) => '',//`Author: ${author}`,
@ -71,26 +90,35 @@ export const template = (name: string, contractDoc: ContractDocumentation, funct
// methods: () => '' // Methods is managed by getMethod() // methods: () => '' // Methods is managed by getMethod()
// } // }
const devMethodDocTemplate: Partial<TemplateDoc<MethodDoc>> = { const devMethodDocTemplate: Partial<TemplateDoc<MethodDoc>> = {
author: (author: string) => `<p>Created By ${author}</p>`, author: (author: string) => `<p>Created By ${author}</p>`,
details: (details: string) => `<p>${details}</p>`, details: (details: string) => `<p>${details}</p>`,
return: (value: string) => `<p>Return : ${value}</p>`, return: (value: string) => `<p>Return : ${value}</p>`,
notice: (notice: string) => `<p>${notice}</p>`, notice: (notice: string) => `<p>${notice}</p>`,
// returns: () => '', // Implemented by getParams() // returns: () => '', // Implemented by getParams()
params: () => '' // Implemented by getParams() params: () => "", // Implemented by getParams()
} };
export const renderHeader = (name: string, contractDoc: ContractDocumentation) => ` export const renderHeader = (
<h3>${name} ${contractDoc.title ? `<small>: ${contractDoc.title}</small>` : ''}</h3> name: string,
contractDoc: ContractDocumentation
) => `
<h3>${name} ${
contractDoc.title ? `<small>: ${contractDoc.title}</small>` : ""
}</h3>
${contractDoc.notice ? `<p class="lead">${contractDoc.notice}</p>` : ''} ${contractDoc.notice ? `<p class="lead">${contractDoc.notice}</p>` : ""}
${contractDoc.author ? `<p>Author: ${contractDoc.author}</p>` : ''} ${contractDoc.author ? `<p>Author: ${contractDoc.author}</p>` : ""}
<p><strong>Functions</strong></p> <p><strong>Functions</strong></p>
` `;
export const renderParameterDocumentation = (parameters: ParameterDocumentation[]) => ` export const renderParameterDocumentation = (
${parameters.length > 0 ? ` parameters: ParameterDocumentation[]
) => `
${
parameters.length > 0
? `
<table class="table table-sm table-bordered table-striped"> <table class="table table-sm table-bordered table-striped">
<thead> <thead>
<tr> <tr>
@ -100,23 +128,26 @@ export const renderParameterDocumentation = (parameters: ParameterDocumentation[
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
${parameters.map((output) => (`<tr> ${parameters.map(
(output) => `<tr>
<td>${output.name}</td> <td>${output.name}</td>
<td>${output.type}</td> <td>${output.type}</td>
<td>${output.description}</td> <td>${output.description}</td>
</tr>`))} </tr>`
)}
</tbody> </tbody>
</table>` : </table>`
'<p>No parameters</p>'} : "<p>No parameters</p>"
` }
`;
export const getMethodDetails = (devMethod?: Partial<MethodDoc>) => { export const getMethodDetails = (devMethod?: Partial<MethodDoc>) => {
return !devMethod return !devMethod
? '<p><strong>**Add Documentation for the method here**</strong></p>' ? "<p><strong>**Add Documentation for the method here**</strong></p>"
: Object.keys(devMethod) : Object.keys(devMethod)
.filter((key) => key !== 'params') .filter((key) => key !== "params")
.map((key) => { .map((key) => {
(devMethodDocTemplate as any)[key]((devMethod as any)[key]) (devMethodDocTemplate as any)[key]((devMethod as any)[key]);
}) })
.join('\n') .join("\n");
} };

@ -1,36 +1,41 @@
import { UserMethodDoc, DevMethodDoc, DeveloperDocumentation, UserDocumentation } from "@remixproject/plugin"; import {
UserMethodDoc,
DevMethodDoc,
DeveloperDocumentation,
UserDocumentation,
} from "@remixproject/plugin";
export interface MethodsDocumentation { export interface MethodsDocumentation {
[x: string]: UserMethodDoc | DevMethodDoc [x: string]: UserMethodDoc | DevMethodDoc;
} }
export interface ContractDocumentation { export interface ContractDocumentation {
methods: MethodsDocumentation; methods: MethodsDocumentation;
author: string; author: string;
title: string; title: string;
details: string; details: string;
notice: string; notice: string;
} }
export type MethodDoc = DevMethodDoc & UserMethodDoc export type MethodDoc = DevMethodDoc & UserMethodDoc;
export type TemplateDoc<T> = { [key in keyof T]: (...params: any[]) => string } export type TemplateDoc<T> = { [key in keyof T]: (...params: any[]) => string };
// Contract // Contract
export type ContractDoc = DeveloperDocumentation & UserDocumentation export type ContractDoc = DeveloperDocumentation & UserDocumentation;
export interface FunctionDocumentation { export interface FunctionDocumentation {
name: string name: string;
type: string type: string;
devdoc?: Partial<MethodDoc> devdoc?: Partial<MethodDoc>;
inputs: ParameterDocumentation[] inputs: ParameterDocumentation[];
outputs: ParameterDocumentation[] outputs: ParameterDocumentation[];
} }
export interface ParameterDocumentation { export interface ParameterDocumentation {
name: string name: string;
type: string type: string;
description: string description: string;
} }
export type HTMLContent = string export type HTMLContent = string;

@ -1,93 +1,99 @@
const open = require('open') // tslint:disable-next-line
const open = require("open");
import { getContractDoc, mergeParametersWithDevdoc, getFunctionDocumentation, getContractDocumentation } from './utils';
import { FunctionDescription } from '@remixproject/plugin'; import {
import { buildFakeArtifactWithComments, buildFakeABIParameter } from './faker' getContractDoc,
mergeParametersWithDevdoc,
jest.setTimeout(10000) getFunctionDocumentation,
getContractDocumentation,
describe('Publisher tests', () => { } from "./utils";
import { FunctionDescription } from "@remixproject/plugin";
describe('getContractDocumentation', () => { import { buildFakeArtifactWithComments, buildFakeABIParameter } from "./faker";
test('getContractDocumentation', () => {
const result = getContractDocumentation(buildFakeArtifactWithComments()) jest.setTimeout(10000);
const result2 = { describe("Publisher tests", () => {
methods: describe("getContractDocumentation", () => {
{ test("getContractDocumentation", () => {
'age(uint256)': const result = getContractDocumentation(buildFakeArtifactWithComments());
{
author: 'Mary A. Botanist', const result2 = {
details: methods: {
'The Alexandr N. Tetearing algorithm could increase precision', "age(uint256)": {
params: [Object], author: "Mary A. Botanist",
return: 'age in years, rounded up for partial years' details:
} "The Alexandr N. Tetearing algorithm could increase precision",
}, params: [Object],
notice: return: "age in years, rounded up for partial years",
'You can use this contract for only the most basic simulation', },
author: 'Larry A. Gardner', },
details: notice: "You can use this contract for only the most basic simulation",
'All function calls are currently implemented without side effects', author: "Larry A. Gardner",
title: 'A simulator for trees' details:
} "All function calls are currently implemented without side effects",
title: "A simulator for trees",
expect(result).toBeDefined() };
})
}) expect(result).toBeDefined();
});
describe('getContractDoc', () => { });
test('getContractDoc', () => {
describe("getContractDoc", () => {
const template = getContractDoc("Fallout", buildFakeArtifactWithComments()); test("getContractDoc", () => {
const template = getContractDoc(
console.log("Template", template) "Fallout",
buildFakeArtifactWithComments()
expect(template).toBeDefined() );
})
}) console.log("Template", template);
describe('getFunctionDocumentation', () => { expect(template).toBeDefined();
test('getFunctionDocumentation', () => { });
const abiItem: FunctionDescription = { });
constant: false,
inputs: [], describe("getFunctionDocumentation", () => {
name: "Fal1out", test("getFunctionDocumentation", () => {
outputs: [], const abiItem: FunctionDescription = {
payable: true, constant: false,
stateMutability: "payable", inputs: [],
type: "function" name: "Fal1out",
} outputs: [],
payable: true,
const result = getFunctionDocumentation(abiItem, {}) stateMutability: "payable",
type: "function",
expect(result).toBeDefined() };
})
}) const result = getFunctionDocumentation(abiItem, {});
describe('mergeParametersWithDevdoc', () => { expect(result).toBeDefined();
test('mergeParametersWithDevdoc', () => { });
const abiParameters = [buildFakeABIParameter()] });
const devParams = {}
const result = mergeParametersWithDevdoc(abiParameters, devParams) describe("mergeParametersWithDevdoc", () => {
test("mergeParametersWithDevdoc", () => {
expect(result.length).toEqual(1) const abiParameters = [buildFakeABIParameter()];
}) const devParams = {};
const result = mergeParametersWithDevdoc(abiParameters, devParams);
test('mergeParametersWithDevdoc with documentation', () => {
const abiParameters = [buildFakeABIParameter()] expect(result.length).toEqual(1);
const devParams = {} });
const result = mergeParametersWithDevdoc(abiParameters, devParams)
test("mergeParametersWithDevdoc with documentation", () => {
expect(result.length).toEqual(1) const abiParameters = [buildFakeABIParameter()];
}) const devParams = {};
}) const result = mergeParametersWithDevdoc(abiParameters, devParams);
expect(result.length).toEqual(1);
test.skip('html generation', async () => { });
await open('https://ipfs.io/ipfs/QmPYQyWyTrUZt3tjiPsEnkRQxedChYUjgEk9zLQ36SfpyW', { app: ['google chrome', '--incognito'] }); });
// start server
// generate html test.skip("html generation", async () => {
// server it await open(
}) "https://ipfs.io/ipfs/QmPYQyWyTrUZt3tjiPsEnkRQxedChYUjgEk9zLQ36SfpyW",
}) { app: ["google chrome", "--incognito"] }
);
// start server
// generate html
// server it
});
});

@ -1,94 +1,130 @@
import { CompilationResult, CompiledContract, FunctionDescription, ABIDescription, DevMethodDoc, UserMethodDoc, ABIParameter, DeveloperDocumentation, UserDocumentation } from "@remixproject/plugin" import {
CompilationResult,
import { EthDocumentation, FileName, Documentation, ContractName } from '../types' CompiledContract,
import { template } from './template' FunctionDescription,
import { ContractDocumentation, MethodDoc, FunctionDocumentation, ParameterDocumentation, MethodsDocumentation } from './types' ABIDescription,
DevMethodDoc,
export const createDocumentation = (fileName: FileName, compilationResult: CompilationResult) => { UserMethodDoc,
console.log("Filename", fileName) ABIParameter,
const result = new Map<ContractName, Documentation>(); DeveloperDocumentation,
UserDocumentation,
const contracts = compilationResult.contracts[fileName] } from "@remixproject/plugin";
console.log("Contracts", contracts)
import {
Object.keys(contracts).forEach((name) => { EthDocumentation,
console.log("CompiledContract", JSON.stringify(contracts[name])) FileName,
result.set(name, getContractDoc(name, contracts[name])) Documentation,
}) ContractName,
} from "../types";
return result import { template } from "./template";
} import {
ContractDocumentation,
MethodDoc,
FunctionDocumentation,
ParameterDocumentation,
MethodsDocumentation,
} from "./types";
export const createDocumentation = (
fileName: FileName,
compilationResult: CompilationResult
) => {
console.log("Filename", fileName);
const result = new Map<ContractName, Documentation>();
const contracts = compilationResult.contracts[fileName];
console.log("Contracts", contracts);
Object.keys(contracts).forEach((name) => {
console.log("CompiledContract", JSON.stringify(contracts[name]));
result.set(name, getContractDoc(name, contracts[name]));
});
return result;
};
export const getContractDoc = (name: string, contract: CompiledContract) => { export const getContractDoc = (name: string, contract: CompiledContract) => {
const contractDoc: ContractDocumentation = getContractDocumentation(contract) const contractDoc: ContractDocumentation = getContractDocumentation(contract);
const functionsDocumentation = contract.abi const functionsDocumentation = contract.abi.map((def: ABIDescription) => {
.map((def: ABIDescription) => { if (def.type === "constructor") {
if (def.type === 'constructor') { def.name = "constructor";
def.name = 'constructor' // because "constructor" is a string and not a { notice } object for userdoc we need to do that
// because "constructor" is a string and not a { notice } object for userdoc we need to do that const methodDoc = {
const methodDoc = { ...(contract.devdoc.methods.constructor || {}),
...(contract.devdoc.methods.constructor || {}), notice: contract.userdoc.methods.constructor as string,
notice: contract.userdoc.methods.constructor as string };
} return getFunctionDocumentation(def, methodDoc);
return getFunctionDocumentation(def, methodDoc) } else {
} else { if (def.type === "fallback") {
if (def.type === 'fallback') { def.name = "fallback";
def.name = 'fallback' }
} const method = Object.keys(contractDoc.methods).find((key) =>
const method = Object.keys(contractDoc.methods).find((key) => key.includes(def.name as string)) as string key.includes(def.name as string)
const methodDoc = contractDoc.methods[method] ) as string;
return getFunctionDocumentation(def as FunctionDescription, methodDoc) const methodDoc = contractDoc.methods[method];
} return getFunctionDocumentation(def as FunctionDescription, methodDoc);
}) }
});
return template(name, contractDoc, functionsDocumentation)
} return template(name, contractDoc, functionsDocumentation);
};
export const getContractDocumentation = (contract: CompiledContract) => { export const getContractDocumentation = (contract: CompiledContract) => {
let methods: MethodsDocumentation = {}; const methods: MethodsDocumentation = {};
Object.keys(contract.userdoc.methods).map((item) => { Object.keys(contract.userdoc.methods).map((item) => {
if (contract.devdoc.methods[item]) { if (contract.devdoc.methods[item]) {
const finalResult = { const finalResult = {
...contract.userdoc.methods[item], ...contract.userdoc.methods[item],
...contract.devdoc.methods[item] ...contract.devdoc.methods[item],
} };
methods[item] = finalResult methods[item] = finalResult;
} else { } else {
methods[item] = contract.userdoc.methods[item] methods[item] = contract.userdoc.methods[item];
} }
}) });
const contractDoc = { ...contract.userdoc, ...contract.devdoc, methods } const contractDoc = { ...contract.userdoc, ...contract.devdoc, methods };
return contractDoc return contractDoc;
} };
export const getFunctionDocumentation = (def: FunctionDescription, devdoc?: Partial<MethodDoc>) => { export const getFunctionDocumentation = (
const doc = devdoc || {} def: FunctionDescription,
const devparams = doc.params || {} devdoc?: Partial<MethodDoc>
const inputsWithDescription = mergeParametersWithDevdoc(def.inputs || [], devparams) ) => {
const outputsWithDescription = mergeParametersWithDevdoc(def.outputs || [], devparams) const doc = devdoc || {};
const type = def.constant ? 'view' : 'read' const devparams = doc.params || {};
const inputsWithDescription = mergeParametersWithDevdoc(
def.inputs || [],
devparams
);
const outputsWithDescription = mergeParametersWithDevdoc(
def.outputs || [],
devparams
);
const type = def.constant ? "view" : "read";
return {
name: def.name,
type,
devdoc,
inputs: inputsWithDescription,
outputs: outputsWithDescription,
} as FunctionDocumentation;
};
export const mergeParametersWithDevdoc = (
params: ABIParameter[],
devparams: any
) => {
return params.map((input) => {
const description = devparams[input.name] || "";
return { return {
name: def.name, name: input.name,
type, type: input.type,
devdoc: devdoc, description,
inputs: inputsWithDescription, } as ParameterDocumentation;
outputs: outputsWithDescription });
} as FunctionDocumentation };
}
export const mergeParametersWithDevdoc = (params: ABIParameter[], devparams: any) => {
return params.map((input) => {
const description = devparams[input.name] || ''
return {
name: input.name,
type: input.type,
description
} as ParameterDocumentation
})
}

@ -1,4 +1,4 @@
import React from "react" import React from "react";
export const ErrorView: React.FC = () => { export const ErrorView: React.FC = () => {
return ( return (
@ -15,17 +15,14 @@ export const ErrorView: React.FC = () => {
width="250" width="250"
src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png" src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png"
alt="Error page" alt="Error page"
></img> />
<h5>Sorry, something unexpected happened. </h5> <h5>Sorry, something unexpected happened. </h5>
<h5> <h5>
Please raise an issue:{" "} Please raise an issue:{" "}
<a <a style={{ color: "red" }} href="">
style={{ color: "red" }}
href=""
>
Here Here
</a> </a>
</h5> </h5>
</div> </div>
) );
} };

@ -1,121 +1,145 @@
import React, { useState, useEffect } from "react" import React, { useState, useEffect } from "react";
import { AppContext } from "../AppContext" import { AppContext } from "../AppContext";
import { ContractName, Documentation } from "../types" import { ContractName, Documentation } from "../types";
import { publish } from '../utils' import { publish } from "../utils";
import { htmlTemplate } from "../utils/template" import { htmlTemplate } from "../utils/template";
export const HomeView: React.FC = () => { export const HomeView: React.FC = () => {
const [activeItem, setActiveItem] = useState("") const [activeItem, setActiveItem] = useState("");
const [isPublishing, setIsPublishing] = useState(false) const [isPublishing, setIsPublishing] = useState(false);
const [htmlDocumentation, setHtmlDocumentation] = useState("") const [htmlDocumentation, setHtmlDocumentation] = useState("");
useEffect(() => { useEffect(() => {
async function publishDocumentation() { async function publishDocumentation() {
try { try {
const hash = await publish(htmlDocumentation);
const hash = await publish(htmlDocumentation) console.log("Hash", hash);
console.log("Hash", hash) setIsPublishing(false);
setIsPublishing(false)
const url = `https://ipfs.io/ipfs/${hash}`; const url = `https://ipfs.io/ipfs/${hash}`;
window.open(url); window.open(url);
} catch (error) { } catch (error) {
setIsPublishing(false) setIsPublishing(false);
} }
} }
if (isPublishing) { if (isPublishing) {
publishDocumentation() publishDocumentation();
} }
}, [isPublishing]);
}, [isPublishing]) const displayDocumentation = (
client: any,
contractName: ContractName,
const displayDocumentation = (client: any, contractName: ContractName, documentation: Documentation) => { documentation: Documentation
) => {
console.log("Display Documentation", contractName, documentation);
console.log("Display Documentation", contractName, documentation) client.emit("documentation-generated", documentation);
};
client.emit('documentation-generated', documentation)
}
return ( return (
<AppContext.Consumer> <AppContext.Consumer>
{({ clientInstance, contracts, setContracts }) => {({ clientInstance, contracts, setContracts }) => (
<div id="ethdoc"> <div id="ethdoc">
{[...contracts.keys()].length === 0 && {[...contracts.keys()].length === 0 && (
<p>Compile a contract with Solidity Compiler</p> <p>Compile a contract with Solidity Compiler</p>
} )}
{[...contracts.keys()].length > 0 && {[...contracts.keys()].length > 0 && (
<div> <div>
<div className="list-group"> <div className="list-group">
{[...contracts.keys()].map((item) => { {[...contracts.keys()].map((item) => {
const documentation = contracts.get(item) as string; const documentation = contracts.get(item) as string;
return ( return (
<button key={item} <button
className={activeItem === item ? 'list-group-item list-group-item-action active' : 'list-group-item list-group-item-action'} key={item}
className={
activeItem === item
? "list-group-item list-group-item-action active"
: "list-group-item list-group-item-action"
}
aria-pressed="false" aria-pressed="false"
onClick={() => { onClick={() => {
setActiveItem(item) setActiveItem(item);
displayDocumentation(clientInstance, item, documentation) displayDocumentation(
const documentationAsHtml = htmlTemplate(documentation) clientInstance,
setHtmlDocumentation(documentationAsHtml) item,
}}> documentation
);
const documentationAsHtml = htmlTemplate(documentation);
setHtmlDocumentation(documentationAsHtml);
}}
>
{item} Documentation {item} Documentation
</button> </button>
) );
})} })}
</div> </div>
<div style={{ float: "right" }}> <div style={{ float: "right" }}>
<button type="button" className="btn btn-sm btn-link" onClick={() => { <button
setContracts(new Map()) type="button"
displayDocumentation(clientInstance, "", "") className="btn btn-sm btn-link"
onClick={() => {
}}>Clear</button> setContracts(new Map());
displayDocumentation(clientInstance, "", "");
}}
>
Clear
</button>
</div> </div>
<div style={{ width: "15em" }}> <div style={{ width: "15em" }}>
{activeItem !== "" && {activeItem !== "" && (
<PublishButton isPublishing={isPublishing} onClick={() => { <PublishButton
console.log("Is publishing") isPublishing={isPublishing}
setIsPublishing(true); onClick={() => {
}} />} console.log("Is publishing");
setIsPublishing(true);
}}
/>
)}
</div> </div>
</div>} </div>
)}
</div> </div>
} )}
</AppContext.Consumer> </AppContext.Consumer>
) );
} };
interface PublishButtonProps { interface PublishButtonProps {
isPublishing: boolean isPublishing: boolean;
onClick: any onClick: any;
} }
export const PublishButton: React.FC<PublishButtonProps> = ({ isPublishing, onClick }) => { export const PublishButton: React.FC<PublishButtonProps> = ({
return (<button isPublishing,
style={{ marginTop: "1em" }} onClick,
className="btn btn-secondary btn-sm btn-block" }) => {
disabled={isPublishing} return (
onClick={onClick}> <button
{!isPublishing && "Publish"} style={{ marginTop: "1em" }}
className="btn btn-secondary btn-sm btn-block"
{isPublishing && ( disabled={isPublishing}
<div> onClick={onClick}
<span >
className="spinner-border spinner-border-sm" {!isPublishing && "Publish"}
role="status"
aria-hidden="true" {isPublishing && (
style={{ marginRight: "0.3em" }} <div>
/> <span
Publishing...Please wait className="spinner-border spinner-border-sm"
</div> role="status"
)} aria-hidden="true"
style={{ marginRight: "0.3em" }}
</button>) />
} Publishing...Please wait
</div>
)}
</button>
);
};
// <label class="btn btn-secondary active"> // <label class="btn btn-secondary active">
// <input type="radio" name="options" id="option1" checked> Active // <input type="radio" name="options" id="option1" checked> Active

@ -1,2 +1,2 @@
export { HomeView } from "./HomeView" export { HomeView } from "./HomeView";
export { ErrorView } from "./ErrorView" export { ErrorView } from "./ErrorView";

Loading…
Cancel
Save