run cowsay from obsidian

This commit is contained in:
Taylor Hulsmans 2023-09-04 21:24:13 +02:00
parent 1dd6e863f7
commit f08f674596
11 changed files with 818 additions and 85 deletions

File diff suppressed because one or more lines are too long

View file

View file

@ -1,8 +1,10 @@
import { useApp } from 'components/hooks/useApp';
export const ReactView = () => {
const { vault } = useApp();
console.log('hello react')
return (<h4>vault name: {vault.getName()}</h4>);
return (<h4>vault name: {vault.getName()}</h4>);
};

View file

@ -1,6 +1,7 @@
import esbuild from "esbuild";
import process from "process";
import builtins from "builtin-modules";
import { polyfillNode } from "esbuild-plugin-polyfill-node";
const banner =
`/*
@ -38,6 +39,9 @@ const context = await esbuild.context({
sourcemap: prod ? false : "inline",
treeShaking: true,
outfile: "main.js",
plugins: [
polyfillNode({})
]
});
if (prod) {

258
main.tsx
View file

@ -1,27 +1,56 @@
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, ItemView, WorkspaceLeaf } from 'obsidian';
import { App, TFile, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, ItemView, WorkspaceLeaf } from 'obsidian';
import { AllCanvasNodeData } from 'obsidian/canvas'
import { CanvasView, addEdge } from './utils/canvas-util'
import { Canvas, CanvasNode, CreateNodeOptions } from './utils/canvas-internal'
import { ReactView } from './components/views/ReactView'
import { createContext, StrictMode, useContext } from "react";
import { createRoot, Root } from 'react-dom/client'
import { AppContext } from 'components/context/context';
const VIEW_TYPE_EXAMPLE = "example-view";
import { ethers, Signer, Provider, JsonRpcProvider, Wallet } from 'ethers';
import ExampleClient from './artifacts/ExampleClient.json'
interface ChainConfig {
name: string;
rpcUrl: string;
chainId: number;
}
// Remember to rename these classes and interfaces!
interface ObsidianLilypadSettings {
mySetting: string;
privateKey: string;
chain: ChainConfig
}
const DEFAULT_SETTINGS: ObsidianLilypadSettings = {
mySetting: 'default'
privateKey: '',
chain: {
name: 'lilypad',
rpcUrl: 'http://testnet.lilypadnetwork.org:8545',
chainId: 1337
}
}
export default class ObsidianLilypad extends Plugin {
settings: ObsidianLilypadSettings;
unloaded = false
provider: Provider
wallet: Wallet
signer: Signer
exampleClient: ethers.Contract
logDebug: (...args: unknown[]) => void = () => { }
async onload() {
await this.loadSettings();
this.provider = new JsonRpcProvider(this.settings.chain.rpcUrl)
if (this.settings.privateKey) {
this.wallet = new Wallet(this.settings.privateKey, this.provider)
this.signer = this.wallet.connect(this.provider)
this.exampleClient = new ethers.Contract(ExampleClient.address, ExampleClient.abi, this.signer)
} else {
console.log('no private key detected, web3 not enabled')
}
// This creates an icon in the left ribbon.
const ribbonIconEl = this.addRibbonIcon('dice', 'Obsidian Lilypad', (evt: MouseEvent) => {
@ -37,21 +66,13 @@ export default class ObsidianLilypad extends Plugin {
// This adds a simple command that can be triggered anywhere
this.addCommand({
id: 'open-sample-modal-simple',
name: 'Open sample modal (simple)',
id: 'runCowsay',
name: 'execute the cowsay program through a smart contract',
callback: () => {
new SampleModal(this.app).open();
}
});
// This adds an editor command that can perform some operation on the current editor instance
this.addCommand({
id: 'sample-editor-command',
name: 'Sample editor command',
editorCallback: (editor: Editor, view: MarkdownView) => {
console.log(editor.getSelection());
editor.replaceSelection('Sample Editor Command');
this.runCowsay()
}
});
// This adds a complex command that can check whether the current state of the app allows execution of the command
this.addCommand({
id: 'open-sample-modal-complex',
@ -88,6 +109,74 @@ export default class ObsidianLilypad extends Plugin {
onunload() {
}
async runCowsay() {
if (this.unloaded) return
this.logDebug("Running Cowsay")
const canvas = this.getActiveCanvas()
if (!canvas) {
this.logDebug('No active canvas')
return
}
const selection = canvas.selection
if (selection?.size !== 1) return
const values = Array.from(selection.values())
const node = values[0]
if (node) {
await canvas.requestSave()
await sleep(200)
const settings = this.settings
const nodeData = node.getData()
let nodeText = await getNodeText(node) || ''
if (nodeText.length == 0) {
this.logDebug('no node Text found')
return
}
const created = createNode(canvas, node,
{
text: `Calling Lilpad Cowsay with ${nodeText}`,
size: { height: placeholderNoteHeight }
},
{
color: assistantColor,
chat_role: 'assistant'
}
)
try {
const tx = await this.exampleClient.runCowsay(
nodeText, {
value: ethers.parseUnits('2', 'ether')
}
)
console.log('tx', tx)
created.setText(`success! tx hash: ${tx.hash}, fetching ipfs.io cid`)
const res = await this.exampleClient.fetchAllResults()
console.log(res)
created.setText(`${res}`)
} catch (e) {
created.setText(`error :( ${e}`)
this.logDebug(`e: ${e}`)
}
}
}
getActiveCanvas() {
const maybeCanvasView = this.app.workspace.getActiveViewOfType(ItemView) as CanvasView | null
return maybeCanvasView ? maybeCanvasView['canvas'] : null
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
@ -98,6 +187,28 @@ export default class ObsidianLilypad extends Plugin {
}
}
async function getNodeText(node: CanvasNode) {
const nodeData = node.getData()
switch (nodeData.type) {
case 'text':
return nodeData.text
case 'file':
return readFile(nodeData.file)
}
}
async function readFile(path: string) {
const file = this.app.vault.getAbstractFileByPath(path)
if (file instanceof TFile) {
const body = await app.vault.read(file)
return `## ${file.basename}\n${body}`
}
}
const calcHeight = (options: { parentHeight: number, text: string }) => {
const calcTextHeight = Math.round(textPaddingHeight + pxPerLine * options.text.length / (minWidth / pxPerChar))
return Math.max(options.parentHeight, calcTextHeight)
}
class SampleModal extends Modal {
constructor(app: App) {
super(app);
@ -132,9 +243,9 @@ class ObsidianLilypadSettingTab extends PluginSettingTab {
.setDesc('It\'s a secret')
.addText(text => text
.setPlaceholder('Enter your Ethereum Private Key')
.setValue(this.plugin.settings.mySetting)
.setValue(this.plugin.settings.privateKey)
.onChange(async (value) => {
this.plugin.settings.mySetting = value;
this.plugin.settings.privateKey = value;
await this.plugin.saveSettings();
}));
}
@ -170,4 +281,113 @@ class ExampleView extends ItemView {
async onClose() {
this.root?.unmount();
}
}
const minWidth = 360
/**
* Assumed pixel width per character
*/
const pxPerChar = 5
/**
* Assumed pixel height per line
*/
const pxPerLine = 28
/**
* Assumed height of top + bottom text area padding
*/
const textPaddingHeight = 12
/**
* Color for assistant notes: 6 == purple
*/
const assistantColor = "6"
/**
* Margin between new notes
*/
const newNoteMargin = 60
/**
* Min height of new notes
*/
const minHeight = 60
/**
* Height to use for new empty note
*/
const emptyNoteHeight = 100
/**
* Height to use for placeholder note
*/
const placeholderNoteHeight = 60
const randomHexString = (len: number) => {
const t = []
for (let n = 0; n < len; n++) {
t.push((16 * Math.random() | 0).toString(16))
}
return t.join("")
}
const createNode = (
canvas: Canvas,
parentNode: CanvasNode,
nodeOptions: CreateNodeOptions,
nodeData?: Partial<AllCanvasNodeData>
) => {
if (!canvas) {
throw new Error('Invalid arguments')
}
const { text } = nodeOptions
const width = nodeOptions?.size?.width || Math.max(minWidth, parentNode?.width)
const height = nodeOptions?.size?.height
|| Math.max(minHeight, (parentNode && calcHeight({ text, parentHeight: parentNode.height })))
const siblings = parent && canvas.getEdgesForNode(parentNode)
.filter(n => n.from.node.id == parentNode.id)
.map(e => e.to.node)
const siblingsRight = siblings && siblings.reduce((right, sib) => Math.max(right, sib.x + sib.width), 0)
const priorSibling = siblings[siblings.length - 1]
// Position left at right of prior sibling, otherwise aligned with parent
const x = siblingsRight ? siblingsRight + newNoteMargin : parentNode.x
// Position top at prior sibling top, otherwise offset below parent
const y = (priorSibling
? priorSibling.y
: (parentNode.y + parentNode.height + newNoteMargin))
// Using position=left, y value is treated as vertical center
+ height * 0.5
const newNode = canvas.createTextNode(
{
pos: { x, y },
position: 'left',
size: { height, width },
text,
focus: false
}
)
if (nodeData) {
newNode.setData(nodeData)
}
canvas.deselectAll()
canvas.addNode(newNode)
addEdge(canvas, randomHexString(16), {
fromOrTo: "from",
side: "bottom",
node: parentNode,
}, {
fromOrTo: "to",
side: "top",
node: newNode,
})
return newNode
}

260
package-lock.json generated
View file

@ -9,6 +9,9 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"esbuild-plugin-polyfill-node": "^0.3.0",
"ethers": "^6.7.1",
"eventemitter3": "^5.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
@ -36,6 +39,11 @@
"node": ">=0.10.0"
}
},
"node_modules/@adraffy/ens-normalize": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.9.2.tgz",
"integrity": "sha512-0h+FrQDqe2Wn+IIGFkTCd4aAwTJ+7834Ek1COohCyV26AXhwQ7WQaz+4F/nLOeVl/3BtWHOHLPsq46V8YB46Eg=="
},
"node_modules/@chainsafe/as-sha256": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/@chainsafe/as-sha256/-/as-sha256-0.3.1.tgz",
@ -88,7 +96,6 @@
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"android"
@ -104,7 +111,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"android"
@ -120,7 +126,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"android"
@ -136,7 +141,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
@ -152,7 +156,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
@ -168,7 +171,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
@ -184,7 +186,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"freebsd"
@ -200,7 +201,6 @@
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -216,7 +216,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -232,7 +231,6 @@
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -248,7 +246,6 @@
"cpu": [
"loong64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -264,7 +261,6 @@
"cpu": [
"mips64el"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -280,7 +276,6 @@
"cpu": [
"ppc64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -296,7 +291,6 @@
"cpu": [
"riscv64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -312,7 +306,6 @@
"cpu": [
"s390x"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -328,7 +321,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
@ -344,7 +336,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"netbsd"
@ -360,7 +351,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"openbsd"
@ -376,7 +366,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"sunos"
@ -392,7 +381,6 @@
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
@ -408,7 +396,6 @@
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
@ -424,7 +411,6 @@
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
@ -805,6 +791,12 @@
"scrypt-js": "3.0.1"
}
},
"node_modules/@ethersproject/json-wallets/node_modules/aes-js": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz",
"integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==",
"dev": true
},
"node_modules/@ethersproject/keccak256": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz",
@ -1251,6 +1243,11 @@
"dev": true,
"peer": true
},
"node_modules/@jspm/core": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@jspm/core/-/core-2.0.1.tgz",
"integrity": "sha512-Lg3PnLp0QXpxwLIAuuJboLeRaIhrgJjeuh797QADg3xz8wGLugQOS5DpsE8A6i6Adgzf+bacllkKZG3J0tGfDw=="
},
"node_modules/@metamask/eth-sig-util": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz",
@ -1283,7 +1280,6 @@
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz",
"integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==",
"dev": true,
"funding": [
{
"type": "individual",
@ -1367,6 +1363,54 @@
"setimmediate": "^1.0.5"
}
},
"node_modules/@nomicfoundation/ethereumjs-block/node_modules/ethers": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz",
"integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
},
{
"type": "individual",
"url": "https://www.buymeacoffee.com/ricmoo"
}
],
"dependencies": {
"@ethersproject/abi": "5.7.0",
"@ethersproject/abstract-provider": "5.7.0",
"@ethersproject/abstract-signer": "5.7.0",
"@ethersproject/address": "5.7.0",
"@ethersproject/base64": "5.7.0",
"@ethersproject/basex": "5.7.0",
"@ethersproject/bignumber": "5.7.0",
"@ethersproject/bytes": "5.7.0",
"@ethersproject/constants": "5.7.0",
"@ethersproject/contracts": "5.7.0",
"@ethersproject/hash": "5.7.0",
"@ethersproject/hdnode": "5.7.0",
"@ethersproject/json-wallets": "5.7.0",
"@ethersproject/keccak256": "5.7.0",
"@ethersproject/logger": "5.7.0",
"@ethersproject/networks": "5.7.1",
"@ethersproject/pbkdf2": "5.7.0",
"@ethersproject/properties": "5.7.0",
"@ethersproject/providers": "5.7.2",
"@ethersproject/random": "5.7.0",
"@ethersproject/rlp": "5.7.0",
"@ethersproject/sha2": "5.7.0",
"@ethersproject/signing-key": "5.7.0",
"@ethersproject/solidity": "5.7.0",
"@ethersproject/strings": "5.7.0",
"@ethersproject/transactions": "5.7.0",
"@ethersproject/units": "5.7.0",
"@ethersproject/wallet": "5.7.0",
"@ethersproject/web": "5.7.1",
"@ethersproject/wordlists": "5.7.0"
}
},
"node_modules/@nomicfoundation/ethereumjs-blockchain": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-7.0.2.tgz",
@ -1570,6 +1614,54 @@
"setimmediate": "^1.0.5"
}
},
"node_modules/@nomicfoundation/ethereumjs-statemanager/node_modules/ethers": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz",
"integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==",
"dev": true,
"funding": [
{
"type": "individual",
"url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
},
{
"type": "individual",
"url": "https://www.buymeacoffee.com/ricmoo"
}
],
"dependencies": {
"@ethersproject/abi": "5.7.0",
"@ethersproject/abstract-provider": "5.7.0",
"@ethersproject/abstract-signer": "5.7.0",
"@ethersproject/address": "5.7.0",
"@ethersproject/base64": "5.7.0",
"@ethersproject/basex": "5.7.0",
"@ethersproject/bignumber": "5.7.0",
"@ethersproject/bytes": "5.7.0",
"@ethersproject/constants": "5.7.0",
"@ethersproject/contracts": "5.7.0",
"@ethersproject/hash": "5.7.0",
"@ethersproject/hdnode": "5.7.0",
"@ethersproject/json-wallets": "5.7.0",
"@ethersproject/keccak256": "5.7.0",
"@ethersproject/logger": "5.7.0",
"@ethersproject/networks": "5.7.1",
"@ethersproject/pbkdf2": "5.7.0",
"@ethersproject/properties": "5.7.0",
"@ethersproject/providers": "5.7.2",
"@ethersproject/random": "5.7.0",
"@ethersproject/rlp": "5.7.0",
"@ethersproject/sha2": "5.7.0",
"@ethersproject/signing-key": "5.7.0",
"@ethersproject/solidity": "5.7.0",
"@ethersproject/strings": "5.7.0",
"@ethersproject/transactions": "5.7.0",
"@ethersproject/units": "5.7.0",
"@ethersproject/wallet": "5.7.0",
"@ethersproject/web": "5.7.1",
"@ethersproject/wordlists": "5.7.0"
}
},
"node_modules/@nomicfoundation/ethereumjs-trie": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-6.0.2.tgz",
@ -2459,10 +2551,9 @@
}
},
"node_modules/aes-js": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz",
"integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==",
"dev": true
"version": "4.0.0-beta.5",
"resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz",
"integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q=="
},
"node_modules/agent-base": {
"version": "6.0.2",
@ -3199,7 +3290,6 @@
"version": "0.17.3",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.3.tgz",
"integrity": "sha512-9n3AsBRe6sIyOc6kmoXg2ypCLgf3eZSraWFRpnkto+svt8cZNuKTkb1bhQcitBcvIqjNiK7K0J3KPmwGSfkA8g==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
@ -3232,6 +3322,18 @@
"@esbuild/win32-x64": "0.17.3"
}
},
"node_modules/esbuild-plugin-polyfill-node": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/esbuild-plugin-polyfill-node/-/esbuild-plugin-polyfill-node-0.3.0.tgz",
"integrity": "sha512-SHG6CKUfWfYyYXGpW143NEZtcVVn8S/WHcEOxk62LuDXnY4Zpmc+WmxJKN6GMTgTClXJXhEM5KQlxKY6YjbucQ==",
"dependencies": {
"@jspm/core": "^2.0.1",
"import-meta-resolve": "^3.0.0"
},
"peerDependencies": {
"esbuild": "*"
}
},
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@ -3550,14 +3652,13 @@
}
},
"node_modules/ethers": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz",
"integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==",
"dev": true,
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/ethers/-/ethers-6.7.1.tgz",
"integrity": "sha512-qX5kxIFMfg1i+epfgb0xF4WM7IqapIIu50pOJ17aebkxxa4BacW5jFrQRmCJpDEg2ZK2oNtR5QjrQ1WDBF29dA==",
"funding": [
{
"type": "individual",
"url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
"url": "https://github.com/sponsors/ethers-io/"
},
{
"type": "individual",
@ -3565,36 +3666,52 @@
}
],
"dependencies": {
"@ethersproject/abi": "5.7.0",
"@ethersproject/abstract-provider": "5.7.0",
"@ethersproject/abstract-signer": "5.7.0",
"@ethersproject/address": "5.7.0",
"@ethersproject/base64": "5.7.0",
"@ethersproject/basex": "5.7.0",
"@ethersproject/bignumber": "5.7.0",
"@ethersproject/bytes": "5.7.0",
"@ethersproject/constants": "5.7.0",
"@ethersproject/contracts": "5.7.0",
"@ethersproject/hash": "5.7.0",
"@ethersproject/hdnode": "5.7.0",
"@ethersproject/json-wallets": "5.7.0",
"@ethersproject/keccak256": "5.7.0",
"@ethersproject/logger": "5.7.0",
"@ethersproject/networks": "5.7.1",
"@ethersproject/pbkdf2": "5.7.0",
"@ethersproject/properties": "5.7.0",
"@ethersproject/providers": "5.7.2",
"@ethersproject/random": "5.7.0",
"@ethersproject/rlp": "5.7.0",
"@ethersproject/sha2": "5.7.0",
"@ethersproject/signing-key": "5.7.0",
"@ethersproject/solidity": "5.7.0",
"@ethersproject/strings": "5.7.0",
"@ethersproject/transactions": "5.7.0",
"@ethersproject/units": "5.7.0",
"@ethersproject/wallet": "5.7.0",
"@ethersproject/web": "5.7.1",
"@ethersproject/wordlists": "5.7.0"
"@adraffy/ens-normalize": "1.9.2",
"@noble/hashes": "1.1.2",
"@noble/secp256k1": "1.7.1",
"@types/node": "18.15.13",
"aes-js": "4.0.0-beta.5",
"tslib": "2.4.0",
"ws": "8.5.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/ethers/node_modules/@noble/hashes": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.2.tgz",
"integrity": "sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
},
"node_modules/ethers/node_modules/@types/node": {
"version": "18.15.13",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz",
"integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q=="
},
"node_modules/ethers/node_modules/ws": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz",
"integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/ethjs-util": {
@ -3611,6 +3728,11 @@
"npm": ">=3"
}
},
"node_modules/eventemitter3": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="
},
"node_modules/evp_bytestokey": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
@ -4303,6 +4425,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/import-meta-resolve": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-3.0.0.tgz",
"integrity": "sha512-4IwhLhNNA8yy445rPjD/lWh++7hMDOml2eHtd58eG7h+qK3EryMuuRbsHGPikCoAgIkkDnckKfWSk2iDla/ejg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
@ -5835,8 +5966,7 @@
"node_modules/tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
"dev": true
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
},
"node_modules/tsort": {
"version": "0.0.1",

View file

@ -25,6 +25,9 @@
"typescript": "4.7.4"
},
"dependencies": {
"esbuild-plugin-polyfill-node": "^0.3.0",
"ethers": "^6.7.1",
"eventemitter3": "^5.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
}

View file

@ -1,6 +1,8 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"resolveJsonModule": true,
"esModuleInterop": true,
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,

76
utils/canvas-internal.ts Normal file
View file

@ -0,0 +1,76 @@
import { App } from 'obsidian'
import { AllCanvasNodeData, CanvasData } from 'obsidian/canvas'
export interface CanvasNode {
id: string
app: App
canvas: Canvas
child: Partial<CanvasNode>
color: string
containerEl: HTMLElement
containerBlockerEl: HTMLElement
contentEl: HTMLElement
destroyted: boolean
height: number
initialized: boolean
isContentMounted: boolean
isEditing: boolean
nodeEl: HTMLElement
placeholderEl: HTMLElement
renderedZIndex: number
resizeDirty: boolean
text: string
unknownData: Record<string, string>
width: number
x: number
y: number
zIndex: number
convertToFile(): Promise<void>
focus(): void
getData(): AllCanvasNodeData
initialize(): void
moveAndResize(options: MoveAndResizeOptions): void
render(): void
setData(data: Partial<AllCanvasNodeData>): void
setText(text: string): Promise<void>
showMenu(): void
startEditing(): void
}
export interface MoveAndResizeOptions {
x?: number, y?: number, width?: number, height?: number
}
export interface CanvasEdge {
from: {
node: CanvasNode
}
to: {
node: CanvasNode
}
}
export interface Canvas {
edges: CanvasEdge[]
selection: Set<CanvasNode>
nodes: CanvasNode[]
wrapperEl: HTMLElement | null
addNode(node: CanvasNode): void
createTextNode(options: CreateNodeOptions): CanvasNode
deselectAll(): void
getData(): CanvasData
getEdgesForNode(node: CanvasNode): CanvasEdge[]
importData(data: { nodes: object[], edges: object[] }): void
removeNode(node: CanvasNode): void
requestFrame(): Promise<void>
requestSave(): Promise<void>
selectOnly(node: CanvasNode, startEditing: boolean): void
}
export interface CreateNodeOptions {
text: string,
pos?: { x: number, y: number }
position?: 'left' | 'right' | 'top' | 'bottom',
size?: { height?: number, width?: number },
focus?: boolean
}

50
utils/canvas-util.ts Normal file
View file

@ -0,0 +1,50 @@
import { ItemView } from 'obsidian'
import { Canvas } from './canvas-internal'
interface CanvasEdge {
fromOrTo: string
side: string,
node: CanvasElement,
}
interface CanvasElement {
id: string
}
export type CanvasView = ItemView & {
canvas: Canvas
}
/**
* Add edge entry to canvas.
*/
export const addEdge = (canvas: Canvas, edgeID: string, fromEdge: CanvasEdge, toEdge: CanvasEdge) => {
if (!canvas) return
const data = canvas.getData()
if (!data) return
canvas.importData({
"edges": [
...data.edges,
{ "id": edgeID, "fromNode": fromEdge.node.id, "fromSide": fromEdge.side, "toNode": toEdge.node.id, "toSide": toEdge.side }
],
"nodes": data.nodes,
})
canvas.requestFrame()
}
/**
* Trap exception and write to console.error.
*/
export function trapError<T>(fn: (...params: unknown[]) => T) {
return (...params: unknown[]) => {
try {
return fn(...params)
} catch (e) {
console.error(e)
}
}
}

10
utils/utils.ts Normal file
View file

@ -0,0 +1,10 @@
/**
* Generate a string of random hexadecimal chars
*/
export const randomHexString = (len: number) => {
const t = []
for (let n = 0; n < len; n++) {
t.push((16 * Math.random() | 0).toString(16))
}
return t.join("")
}