pull/5370/head
Solid Studio 4 years ago committed by Joseph Izang
parent eb7991a36d
commit c1dd4d748e
  1. 116
      src/App.tsx
  2. 20
      src/AppContext.tsx
  3. 24
      src/hooks/useLocalStorage.tsx
  4. 10
      src/index.tsx
  5. 23
      src/routes.tsx
  6. 30
      src/utils/faker.ts
  7. 4
      src/utils/index.ts
  8. 28
      src/utils/publisher.test.ts
  9. 17
      src/utils/publisher.ts
  10. 87
      src/utils/template.ts
  11. 33
      src/utils/types.ts
  12. 144
      src/utils/utils.test.ts
  13. 158
      src/utils/utils.ts
  14. 13
      src/views/ErrorView.tsx
  15. 136
      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",
(
fileName: string,
source: CompilationFileSources,
languageVersion: string,
data: CompilationResult
) => {
console.log("New compilation received");
const existingMap = contractsRef.current; const existingMap = contractsRef.current;
const newContractsMapWithDocumentation = createDocumentation(fileName, data) const newContractsMapWithDocumentation = createDocumentation(
const newMap = new Map([...existingMap, ...newContractsMapWithDocumentation]) fileName,
data
console.log("New Map", newMap) );
const newMap = new Map([
const status: Status = { key: 'succeed', type: 'success', title: 'New documentation ready' } ...existingMap,
clientInstanceRef.current.emit('statusChanged', status) ...newContractsMapWithDocumentation,
setContracts(newMap) ]);
})
console.log("New Map", newMap);
const status: Status = {
key: "succeed",
type: "success",
title: "New documentation ready",
};
clientInstanceRef.current.emit("statusChanged", status);
setContracts(newMap);
} }
);
};
loadClient() 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,28 +1,23 @@
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>
@ -33,4 +28,4 @@ export const Routes = () => (
</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,18 +1,18 @@
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 () => {
})
test('it can publish html', async () => {
const result = await publish(` const result = await publish(`
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
@ -27,7 +27,7 @@ describe('Publisher tests', () => {
<div>Content custom</div> <div>Content custom</div>
</body> </body>
</html> </html>
`) `);
// Uncomment for testing // Uncomment for testing
@ -35,6 +35,6 @@ describe('Publisher tests', () => {
// 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}`,
@ -76,21 +95,30 @@ const devMethodDocTemplate: Partial<TemplateDoc<MethodDoc>> = {
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,7 +1,12 @@
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 {
@ -12,25 +17,25 @@ export interface ContractDocumentation {
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,54 +1,58 @@
const open = require('open') // tslint:disable-next-line
const open = require("open");
import { getContractDoc, mergeParametersWithDevdoc, getFunctionDocumentation, getContractDocumentation } from './utils'; import {
import { FunctionDescription } from '@remixproject/plugin'; getContractDoc,
import { buildFakeArtifactWithComments, buildFakeABIParameter } from './faker' mergeParametersWithDevdoc,
getFunctionDocumentation,
getContractDocumentation,
} from "./utils";
import { FunctionDescription } from "@remixproject/plugin";
import { buildFakeArtifactWithComments, buildFakeABIParameter } from "./faker";
jest.setTimeout(10000) jest.setTimeout(10000);
describe('Publisher tests', () => { describe("Publisher tests", () => {
describe("getContractDocumentation", () => {
describe('getContractDocumentation', () => { test("getContractDocumentation", () => {
test('getContractDocumentation', () => { const result = getContractDocumentation(buildFakeArtifactWithComments());
const result = getContractDocumentation(buildFakeArtifactWithComments())
const result2 = { const result2 = {
methods: methods: {
{ "age(uint256)": {
'age(uint256)': author: "Mary A. Botanist",
{
author: 'Mary A. Botanist',
details: details:
'The Alexandr N. Tetearing algorithm could increase precision', "The Alexandr N. Tetearing algorithm could increase precision",
params: [Object], params: [Object],
return: 'age in years, rounded up for partial years' return: "age in years, rounded up for partial years",
} },
}, },
notice: notice: "You can use this contract for only the most basic simulation",
'You can use this contract for only the most basic simulation', author: "Larry A. Gardner",
author: 'Larry A. Gardner',
details: details:
'All function calls are currently implemented without side effects', "All function calls are currently implemented without side effects",
title: 'A simulator for trees' title: "A simulator for trees",
} };
expect(result).toBeDefined()
})
})
describe('getContractDoc', () => { expect(result).toBeDefined();
test('getContractDoc', () => { });
});
const template = getContractDoc("Fallout", buildFakeArtifactWithComments()); describe("getContractDoc", () => {
test("getContractDoc", () => {
const template = getContractDoc(
"Fallout",
buildFakeArtifactWithComments()
);
console.log("Template", template) console.log("Template", template);
expect(template).toBeDefined() expect(template).toBeDefined();
}) });
}) });
describe('getFunctionDocumentation', () => { describe("getFunctionDocumentation", () => {
test('getFunctionDocumentation', () => { test("getFunctionDocumentation", () => {
const abiItem: FunctionDescription = { const abiItem: FunctionDescription = {
constant: false, constant: false,
inputs: [], inputs: [],
@ -56,38 +60,40 @@ describe('Publisher tests', () => {
outputs: [], outputs: [],
payable: true, payable: true,
stateMutability: "payable", stateMutability: "payable",
type: "function" type: "function",
} };
const result = getFunctionDocumentation(abiItem, {}) const result = getFunctionDocumentation(abiItem, {});
expect(result).toBeDefined() expect(result).toBeDefined();
}) });
}) });
describe('mergeParametersWithDevdoc', () => { describe("mergeParametersWithDevdoc", () => {
test('mergeParametersWithDevdoc', () => { test("mergeParametersWithDevdoc", () => {
const abiParameters = [buildFakeABIParameter()] const abiParameters = [buildFakeABIParameter()];
const devParams = {} const devParams = {};
const result = mergeParametersWithDevdoc(abiParameters, devParams) const result = mergeParametersWithDevdoc(abiParameters, devParams);
expect(result.length).toEqual(1) expect(result.length).toEqual(1);
}) });
test('mergeParametersWithDevdoc with documentation', () => { test("mergeParametersWithDevdoc with documentation", () => {
const abiParameters = [buildFakeABIParameter()] const abiParameters = [buildFakeABIParameter()];
const devParams = {} const devParams = {};
const result = mergeParametersWithDevdoc(abiParameters, devParams) const result = mergeParametersWithDevdoc(abiParameters, devParams);
expect(result.length).toEqual(1) expect(result.length).toEqual(1);
}) });
}) });
test.skip("html generation", async () => {
test.skip('html generation', async () => { await open(
await open('https://ipfs.io/ipfs/QmPYQyWyTrUZt3tjiPsEnkRQxedChYUjgEk9zLQ36SfpyW', { app: ['google chrome', '--incognito'] }); "https://ipfs.io/ipfs/QmPYQyWyTrUZt3tjiPsEnkRQxedChYUjgEk9zLQ36SfpyW",
{ app: ["google chrome", "--incognito"] }
);
// start server // start server
// generate html // generate html
// server it // 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,
DeveloperDocumentation,
UserDocumentation,
} from "@remixproject/plugin";
import {
EthDocumentation,
FileName,
Documentation,
ContractName,
} from "../types";
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 result = new Map<ContractName, Documentation>();
const contracts = compilationResult.contracts[fileName] const contracts = compilationResult.contracts[fileName];
console.log("Contracts", contracts) console.log("Contracts", contracts);
Object.keys(contracts).forEach((name) => { Object.keys(contracts).forEach((name) => {
console.log("CompiledContract", JSON.stringify(contracts[name])) console.log("CompiledContract", JSON.stringify(contracts[name]));
result.set(name, getContractDoc(name, contracts[name])) result.set(name, getContractDoc(name, contracts[name]));
}) });
return result 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) => key.includes(def.name as string)) as string const method = Object.keys(contractDoc.methods).find((key) =>
const methodDoc = contractDoc.methods[method] key.includes(def.name as string)
return getFunctionDocumentation(def as FunctionDescription, methodDoc) ) as string;
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 }
return contractDoc
} }
});
export const getFunctionDocumentation = (def: FunctionDescription, devdoc?: Partial<MethodDoc>) => {
const doc = devdoc || {} const contractDoc = { ...contract.userdoc, ...contract.devdoc, methods };
const devparams = doc.params || {}
const inputsWithDescription = mergeParametersWithDevdoc(def.inputs || [], devparams) return contractDoc;
const outputsWithDescription = mergeParametersWithDevdoc(def.outputs || [], devparams) };
const type = def.constant ? 'view' : 'read'
export const getFunctionDocumentation = (
def: FunctionDescription,
devdoc?: Partial<MethodDoc>
) => {
const doc = devdoc || {};
const devparams = doc.params || {};
const inputsWithDescription = mergeParametersWithDevdoc(
def.inputs || [],
devparams
);
const outputsWithDescription = mergeParametersWithDevdoc(
def.outputs || [],
devparams
);
const type = def.constant ? "view" : "read";
return { return {
name: def.name, name: def.name,
type, type,
devdoc: devdoc, devdoc,
inputs: inputsWithDescription, inputs: inputsWithDescription,
outputs: outputsWithDescription outputs: outputsWithDescription,
} as FunctionDocumentation } as FunctionDocumentation;
} };
export const mergeParametersWithDevdoc = (params: ABIParameter[], devparams: any) => { export const mergeParametersWithDevdoc = (
params: ABIParameter[],
devparams: any
) => {
return params.map((input) => { return params.map((input) => {
const description = devparams[input.name] || '' const description = devparams[input.name] || "";
return { return {
name: input.name, name: input.name,
type: input.type, type: input.type,
description description,
} as ParameterDocumentation } 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,105 +1,129 @@
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,
documentation: Documentation
) => {
console.log("Display Documentation", contractName, documentation);
const displayDocumentation = (client: any, contractName: ContractName, documentation: Documentation) => { client.emit("documentation-generated", documentation);
};
console.log("Display Documentation", contractName, 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}
onClick={() => {
console.log("Is publishing");
setIsPublishing(true); 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,
onClick,
}) => {
return (
<button
style={{ marginTop: "1em" }} style={{ marginTop: "1em" }}
className="btn btn-secondary btn-sm btn-block" className="btn btn-secondary btn-sm btn-block"
disabled={isPublishing} disabled={isPublishing}
onClick={onClick}> onClick={onClick}
>
{!isPublishing && "Publish"} {!isPublishing && "Publish"}
{isPublishing && ( {isPublishing && (
@ -113,9 +137,9 @@ export const PublishButton: React.FC<PublishButtonProps> = ({ isPublishing, onCl
Publishing...Please wait Publishing...Please wait
</div> </div>
)} )}
</button>
</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