pull/3542/head
Solid Studio 4 years ago committed by Joseph Izang
parent d3489d7889
commit a51002a527
  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 {
createIframeClient,
CompilationFileSources,
CompilationResult,
Status
} from "@remixproject/plugin"
Status,
} from "@remixproject/plugin";
import { AppContext } from "./AppContext"
import { Routes } from "./routes"
import { useLocalStorage } from "./hooks/useLocalStorage"
import { createDocumentation } from "./utils/utils"
import { AppContext } from "./AppContext";
import { Routes } from "./routes";
import { useLocalStorage } from "./hooks/useLocalStorage";
import { createDocumentation } from "./utils/utils";
import "./App.css"
import { ContractName, Documentation } from "./types"
import "./App.css";
import { ContractName, Documentation } from "./types";
const devMode = { port: 8080 }
const devMode = { port: 8080 };
export const getNewContractNames = (compilationResult: CompilationResult) => {
const compiledContracts = compilationResult.contracts
let result: string[] = []
const compiledContracts = compilationResult.contracts;
let result: string[] = [];
for (const file of Object.keys(compiledContracts)) {
const newContractNames = Object.keys(compiledContracts[file])
result = [...result, ...newContractNames]
const newContractNames = Object.keys(compiledContracts[file]);
result = [...result, ...newContractNames];
}
return result
}
return result;
};
const sampleMap = new Map<ContractName, Documentation>()
const sampleMap = new Map<ContractName, Documentation>();
const App = () => {
const [clientInstance, setClientInstance] = useState(undefined as any)
const [contracts, setContracts] = useState(sampleMap)
const [sites, setSites] = useLocalStorage('sites', [])
const clientInstanceRef = useRef(clientInstance)
clientInstanceRef.current = clientInstance
const contractsRef = useRef(contracts)
contractsRef.current = contracts
const [clientInstance, setClientInstance] = useState(undefined as any);
const [contracts, setContracts] = useState(sampleMap);
const [sites, setSites] = useLocalStorage("sites", []);
const clientInstanceRef = useRef(clientInstance);
clientInstanceRef.current = clientInstance;
const contractsRef = useRef(contracts);
contractsRef.current = contracts;
useEffect(() => {
console.log("Remix EthDoc loading...")
const client = createIframeClient({ devMode })
console.log("Remix EthDoc loading...");
const client = createIframeClient({ devMode });
const loadClient = async () => {
await client.onload()
setClientInstance(client)
console.log("Remix EthDoc Plugin has been loaded")
client.solidity.on("compilationFinished", (fileName: string, source: CompilationFileSources, languageVersion: string, data: CompilationResult) => {
console.log("New compilation received")
const existingMap = contractsRef.current;
const newContractsMapWithDocumentation = createDocumentation(fileName, data)
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()
}, [])
await client.onload();
setClientInstance(client);
console.log("Remix EthDoc Plugin has been loaded");
client.solidity.on(
"compilationFinished",
(
fileName: string,
source: CompilationFileSources,
languageVersion: string,
data: CompilationResult
) => {
console.log("New compilation received");
const existingMap = contractsRef.current;
const newContractsMapWithDocumentation = createDocumentation(
fileName,
data
);
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 (
<AppContext.Provider
@ -74,11 +91,12 @@ const App = () => {
contracts,
setContracts,
sites,
setSites
}}>
setSites,
}}
>
<Routes />
</AppContext.Provider>
)
}
);
};
export default App
export default App;

@ -1,17 +1,23 @@
import React from "react"
import { PluginApi, IRemixApi, Api, PluginClient, CompilationResult } from "@remixproject/plugin"
import React from "react";
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({
clientInstance: {} as PluginApi<Readonly<IRemixApi>> &
PluginClient<Api, Readonly<IRemixApi>>,
contracts: new Map<ContractName, Documentation>(),
setContracts: (contracts: Map<ContractName, Documentation>) => {
console.log("Calling Set Contract Names")
console.log("Calling Set Contract Names");
},
sites: [],
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) {
// State to store our value
@ -6,15 +6,15 @@ export function useLocalStorage(key: string, initialValue: any) {
const [storedValue, setStoredValue] = useState(() => {
try {
// 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
return item ? JSON.parse(item) : initialValue
return item ? JSON.parse(item) : initialValue;
} catch (error) {
// If error also return initialValue
console.log(error)
return initialValue
console.log(error);
return initialValue;
}
})
});
// Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage.
@ -22,16 +22,16 @@ export function useLocalStorage(key: string, initialValue: any) {
try {
// Allow value to be a function so we have same API as useState
const valueToStore =
value instanceof Function ? value(storedValue) : value
value instanceof Function ? value(storedValue) : value;
// Save state
setStoredValue(valueToStore)
setStoredValue(valueToStore);
// Save to local storage
window.localStorage.setItem(key, JSON.stringify(valueToStore))
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
// 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 ReactDOM from 'react-dom';
import App from './App';
import { Routes } from './routes'
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Routes } from "./routes";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
document.getElementById("root")
);

@ -1,36 +1,31 @@
import React from "react"
import React from "react";
import {
BrowserRouter as Router,
Switch,
Route,
RouteProps,
} from "react-router-dom"
BrowserRouter as Router,
Switch,
Route,
RouteProps,
} from "react-router-dom";
import { ErrorView, HomeView } from "./views"
import { ErrorView, HomeView } from "./views";
interface Props extends RouteProps {
component: any // TODO: new (props: any) => React.Component
from: string
component: any; // TODO: new (props: any) => React.Component
from: string;
}
const CustomRoute = ({ component: Component, ...rest }: Props) => {
return (
<Route
{...rest}
render={(matchProps) => (
<Component {...matchProps} />
)}
/>
)
}
return (
<Route {...rest} render={(matchProps) => <Component {...matchProps} />} />
);
};
export const Routes = () => (
<Router>
<Switch>
<CustomRoute exact path="/" component={HomeView} from="/" />
<Route path="/error">
<ErrorView />
</Route>
</Switch>
</Router>
)
<Router>
<Switch>
<CustomRoute exact path="/" component={HomeView} from="/" />
<Route path="/error">
<ErrorView />
</Route>
</Switch>
</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 sampleDataWithComments from './sample-data/sample-artifact-with-comments.json'
import sampleData from "./sample-data/sample-artifact.json";
import sampleDataWithComments from "./sample-data/sample-artifact-with-comments.json";
export const buildFakeArtifact: () => CompiledContract = () => {
const result = sampleData as never as CompiledContract
return result
}
const result = (sampleData as never) as CompiledContract;
return result;
};
export const buildFakeArtifactWithComments: () => CompiledContract = () => {
const result = sampleDataWithComments as never as CompiledContract
return result
}
const result = (sampleDataWithComments as never) as CompiledContract;
return result;
};
export const buildFakeABIParameter: () => ABIParameter = () => {
return {
internalType: "address",
name: "allocator",
type: "address"
}
}
return {
internalType: "address",
name: "allocator",
type: "address",
};
};
export const buildFakeABIParameterWithDocumentation: () => ABIParameter = () => {
return {
internalType: "address",
name: "allocator",
type: "address"
}
}
return {
internalType: "address",
name: "allocator",
type: "address",
};
};

@ -1,2 +1,2 @@
export * from './utils'
export * from './publisher'
export * from "./utils";
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 () => {
const result = await publish("hello 123")
expect(result).toBeDefined();
});
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>
<html lang="en">
<head>
@ -27,14 +27,14 @@ describe('Publisher tests', () => {
<div>Content custom</div>
</body>
</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";
const IpfsClient = require('ipfs-mini')
// tslint:disable-next-line
const IpfsClient = require("ipfs-mini");
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"
type HTMLContent = string
import {
FunctionDocumentation,
TemplateDoc,
MethodDoc,
ContractDoc,
ContractDocumentation,
ParameterDocumentation,
} from "./types";
type HTMLContent = string;
export const htmlTemplate = (content: HTMLContent) => `
<!DOCTYPE html>
@ -16,9 +23,13 @@ export const htmlTemplate = (content: HTMLContent) => `
${content}
</body>
</html>
`
`;
export const template = (name: string, contractDoc: ContractDocumentation, functions: FunctionDocumentation[]) => `
export const template = (
name: string,
contractDoc: ContractDocumentation,
functions: FunctionDocumentation[]
) => `
<style>
#ethdoc-viewer{
font-size: 0.8em;
@ -41,9 +52,15 @@ export const template = (name: string, contractDoc: ContractDocumentation, funct
<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>
<hr>
${renderParameterDocumentation(item.inputs)}
@ -54,10 +71,12 @@ export const template = (name: string, contractDoc: ContractDocumentation, funct
${renderParameterDocumentation(item.outputs)}
`).join('\n')}
`
)
.join("\n")}
</div>
`
`;
// const contractDocTemplate: TemplateDoc<ContractDoc> = {
// author: (author: string) => '',//`Author: ${author}`,
@ -71,26 +90,35 @@ export const template = (name: string, contractDoc: ContractDocumentation, funct
// methods: () => '' // Methods is managed by getMethod()
// }
const devMethodDocTemplate: Partial<TemplateDoc<MethodDoc>> = {
author: (author: string) => `<p>Created By ${author}</p>`,
details: (details: string) => `<p>${details}</p>`,
return: (value: string) => `<p>Return : ${value}</p>`,
notice: (notice: string) => `<p>${notice}</p>`,
// returns: () => '', // Implemented by getParams()
params: () => '' // Implemented by getParams()
}
author: (author: string) => `<p>Created By ${author}</p>`,
details: (details: string) => `<p>${details}</p>`,
return: (value: string) => `<p>Return : ${value}</p>`,
notice: (notice: string) => `<p>${notice}</p>`,
// returns: () => '', // Implemented by getParams()
params: () => "", // Implemented by getParams()
};
export const renderHeader = (name: string, contractDoc: ContractDocumentation) => `
<h3>${name} ${contractDoc.title ? `<small>: ${contractDoc.title}</small>` : ''}</h3>
export const renderHeader = (
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>
`
`;
export const renderParameterDocumentation = (parameters: ParameterDocumentation[]) => `
${parameters.length > 0 ? `
export const renderParameterDocumentation = (
parameters: ParameterDocumentation[]
) => `
${
parameters.length > 0
? `
<table class="table table-sm table-bordered table-striped">
<thead>
<tr>
@ -100,23 +128,26 @@ export const renderParameterDocumentation = (parameters: ParameterDocumentation[
</tr>
</thead>
<tbody>
${parameters.map((output) => (`<tr>
${parameters.map(
(output) => `<tr>
<td>${output.name}</td>
<td>${output.type}</td>
<td>${output.description}</td>
</tr>`))}
</tr>`
)}
</tbody>
</table>` :
'<p>No parameters</p>'}
`
</table>`
: "<p>No parameters</p>"
}
`;
export const getMethodDetails = (devMethod?: Partial<MethodDoc>) => {
return !devMethod
? '<p><strong>**Add Documentation for the method here**</strong></p>'
: Object.keys(devMethod)
.filter((key) => key !== 'params')
.map((key) => {
(devMethodDocTemplate as any)[key]((devMethod as any)[key])
})
.join('\n')
}
return !devMethod
? "<p><strong>**Add Documentation for the method here**</strong></p>"
: Object.keys(devMethod)
.filter((key) => key !== "params")
.map((key) => {
(devMethodDocTemplate as any)[key]((devMethod as any)[key]);
})
.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 {
[x: string]: UserMethodDoc | DevMethodDoc
[x: string]: UserMethodDoc | DevMethodDoc;
}
export interface ContractDocumentation {
methods: MethodsDocumentation;
author: string;
title: string;
details: string;
notice: string;
methods: MethodsDocumentation;
author: string;
title: string;
details: 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
export type ContractDoc = DeveloperDocumentation & UserDocumentation
export type ContractDoc = DeveloperDocumentation & UserDocumentation;
export interface FunctionDocumentation {
name: string
type: string
devdoc?: Partial<MethodDoc>
inputs: ParameterDocumentation[]
outputs: ParameterDocumentation[]
name: string;
type: string;
devdoc?: Partial<MethodDoc>;
inputs: ParameterDocumentation[];
outputs: ParameterDocumentation[];
}
export interface ParameterDocumentation {
name: string
type: string
description: string
name: string;
type: string;
description: string;
}
export type HTMLContent = string
export type HTMLContent = string;

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

@ -1,94 +1,130 @@
import { CompilationResult, CompiledContract, FunctionDescription, ABIDescription, DevMethodDoc, UserMethodDoc, 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 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
}
import {
CompilationResult,
CompiledContract,
FunctionDescription,
ABIDescription,
DevMethodDoc,
UserMethodDoc,
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 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) => {
const contractDoc: ContractDocumentation = getContractDocumentation(contract)
const functionsDocumentation = contract.abi
.map((def: ABIDescription) => {
if (def.type === 'constructor') {
def.name = 'constructor'
// because "constructor" is a string and not a { notice } object for userdoc we need to do that
const methodDoc = {
...(contract.devdoc.methods.constructor || {}),
notice: contract.userdoc.methods.constructor as string
}
return getFunctionDocumentation(def, methodDoc)
} else {
if (def.type === 'fallback') {
def.name = 'fallback'
}
const method = Object.keys(contractDoc.methods).find((key) => key.includes(def.name as string)) as string
const methodDoc = contractDoc.methods[method]
return getFunctionDocumentation(def as FunctionDescription, methodDoc)
}
})
return template(name, contractDoc, functionsDocumentation)
}
const contractDoc: ContractDocumentation = getContractDocumentation(contract);
const functionsDocumentation = contract.abi.map((def: ABIDescription) => {
if (def.type === "constructor") {
def.name = "constructor";
// because "constructor" is a string and not a { notice } object for userdoc we need to do that
const methodDoc = {
...(contract.devdoc.methods.constructor || {}),
notice: contract.userdoc.methods.constructor as string,
};
return getFunctionDocumentation(def, methodDoc);
} else {
if (def.type === "fallback") {
def.name = "fallback";
}
const method = Object.keys(contractDoc.methods).find((key) =>
key.includes(def.name as string)
) as string;
const methodDoc = contractDoc.methods[method];
return getFunctionDocumentation(def as FunctionDescription, methodDoc);
}
});
return template(name, contractDoc, functionsDocumentation);
};
export const getContractDocumentation = (contract: CompiledContract) => {
let methods: MethodsDocumentation = {};
Object.keys(contract.userdoc.methods).map((item) => {
if (contract.devdoc.methods[item]) {
const finalResult = {
...contract.userdoc.methods[item],
...contract.devdoc.methods[item]
}
methods[item] = finalResult
} else {
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 devparams = doc.params || {}
const inputsWithDescription = mergeParametersWithDevdoc(def.inputs || [], devparams)
const outputsWithDescription = mergeParametersWithDevdoc(def.outputs || [], devparams)
const type = def.constant ? 'view' : 'read'
const methods: MethodsDocumentation = {};
Object.keys(contract.userdoc.methods).map((item) => {
if (contract.devdoc.methods[item]) {
const finalResult = {
...contract.userdoc.methods[item],
...contract.devdoc.methods[item],
};
methods[item] = finalResult;
} else {
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 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 {
name: def.name,
type,
devdoc: devdoc,
inputs: inputsWithDescription,
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
})
}
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 = () => {
return (
@ -15,17 +15,14 @@ export const ErrorView: React.FC = () => {
width="250"
src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png"
alt="Error page"
></img>
/>
<h5>Sorry, something unexpected happened. </h5>
<h5>
Please raise an issue:{" "}
<a
style={{ color: "red" }}
href=""
>
<a style={{ color: "red" }} href="">
Here
</a>
</h5>
</div>
)
}
);
};

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

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

Loading…
Cancel
Save