Merge pull request #285 from kinode-dao/tm/unify
Move homepage & appstore UIs to main repo
1
.gitignore
vendored
@ -12,6 +12,7 @@ wit/
|
|||||||
/home
|
/home
|
||||||
packages/**/pkg/*.wasm
|
packages/**/pkg/*.wasm
|
||||||
packages/**/wit
|
packages/**/wit
|
||||||
|
*/**/node_modules
|
||||||
.env
|
.env
|
||||||
kinode/src/bootstrapped_processes.rs
|
kinode/src/bootstrapped_processes.rs
|
||||||
kinode/packages/**/wasi_snapshot_preview1.wasm
|
kinode/packages/**/wasi_snapshot_preview1.wasm
|
||||||
|
@ -29,7 +29,7 @@ fn build_and_zip_package(
|
|||||||
) -> anyhow::Result<(String, String, Vec<u8>)> {
|
) -> anyhow::Result<(String, String, Vec<u8>)> {
|
||||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
rt.block_on(async {
|
rt.block_on(async {
|
||||||
kit::build::execute(&entry_path, false, false, true, features).await?;
|
kit::build::execute(&entry_path, true, false, true, features).await?;
|
||||||
|
|
||||||
let mut writer = Cursor::new(Vec::new());
|
let mut writer = Cursor::new(Vec::new());
|
||||||
let options = FileOptions::default()
|
let options = FileOptions::default()
|
||||||
|
@ -15,8 +15,8 @@
|
|||||||
<meta name="viewport"
|
<meta name="viewport"
|
||||||
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
|
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
|
||||||
<link href='https://fonts.googleapis.com/css?family=Montserrat' rel='stylesheet'>
|
<link href='https://fonts.googleapis.com/css?family=Montserrat' rel='stylesheet'>
|
||||||
<script type="module" crossorigin src="/main:app_store:sys/assets/index-CPkF34RS.js"></script>
|
<script type="module" crossorigin src="/main:app_store:sys/assets/index-YeOEFbyC.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/main:app_store:sys/assets/index-jnOcECnM.css">
|
<link rel="stylesheet" crossorigin href="/main:app_store:sys/assets/index-JESB3UJK.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
18
kinode/packages/app_store/ui/.eslintrc.cjs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: { browser: true, es2020: true },
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
],
|
||||||
|
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: ['react-refresh'],
|
||||||
|
rules: {
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
24
kinode/packages/app_store/ui/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
65
kinode/packages/app_store/ui/README.md
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# App Store UI
|
||||||
|
|
||||||
|
This UI template uses the React framework compiled with Vite.
|
||||||
|
It is based on the Vite React Typescript template.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
There are 2 ways to set up this repo:
|
||||||
|
1. Place this repo next to the `pkg` repo of your Kinode project (usually the top level).
|
||||||
|
2. Set `BASE_URL` in `vite.config.ts` to the URL of your Kinode project (i.e. `/chess:chess:sys`) and comment out lines 8 and 9.
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Run `npm i`, `npm run tc`, and then `npm start` to start working on the UI.
|
||||||
|
By default, the dev server will proxy requests to `http://localhost:8080`.
|
||||||
|
You can change the proxy target by setting `VITE_NODE_URL` like so:
|
||||||
|
`VITE_NODE_URL=http://localhost:8081 npm start`
|
||||||
|
|
||||||
|
You may see an error:
|
||||||
|
|
||||||
|
```
|
||||||
|
[vite] Pre-transform error: Failed to load url /our.js (resolved id: /our.js). Does the file exist?
|
||||||
|
```
|
||||||
|
|
||||||
|
You can safely ignore this error. The file will be served by the node via the proxy.
|
||||||
|
|
||||||
|
#### public vs assets
|
||||||
|
|
||||||
|
The `public/assets` folder contains files that are referenced in `index.html`, `src/assets` is for asset files that are only referenced in `src` code.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
Run `npm run build`, the build will be generated in the `dist` directory.
|
||||||
|
If this repo is next to your Kinode `pkg` directory then you can `npm run build:copy` to build and copy it for installation.
|
||||||
|
|
||||||
|
## About Vite + React
|
||||||
|
|
||||||
|
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||||
|
|
||||||
|
Currently, two official plugins are available:
|
||||||
|
|
||||||
|
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||||
|
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||||
|
|
||||||
|
## Expanding the ESLint configuration
|
||||||
|
|
||||||
|
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
|
||||||
|
|
||||||
|
- Configure the top-level `parserOptions` property like this:
|
||||||
|
|
||||||
|
```js
|
||||||
|
export default {
|
||||||
|
// other rules...
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
project: ['./tsconfig.json', './tsconfig.node.json'],
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
|
||||||
|
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
|
||||||
|
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
|
22
kinode/packages/app_store/ui/chat_metadata.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "Chat Template",
|
||||||
|
"subtitle": "The chat template from kit",
|
||||||
|
"description": "The kit chat template is the default app when starting a new kit project. This app is the basic version of that, packaged for the app store.",
|
||||||
|
"image": "https://st4.depositphotos.com/7662228/30134/v/450/depositphotos_301343880-stock-illustration-best-chat-speech-bubble-icon.jpg",
|
||||||
|
"version": "0.1.2",
|
||||||
|
"license": "MIT",
|
||||||
|
"website": "https://kinode.org",
|
||||||
|
"screenshots": [
|
||||||
|
"https://pongo-uploads.s3.us-east-2.amazonaws.com/Screenshot+2024-01-30+at+10.01.46+PM.png",
|
||||||
|
"https://pongo-uploads.s3.us-east-2.amazonaws.com/Screenshot+2024-01-30+at+10.01.52+PM.png"
|
||||||
|
],
|
||||||
|
"mirrors": [
|
||||||
|
"odinsbadeye.os"
|
||||||
|
],
|
||||||
|
"versions": [
|
||||||
|
"a2c584bf63a730efdc79ec0a3c93bc97eba4e8745c633e3abe090b4f7e270e92",
|
||||||
|
"c13f7ae39fa7f652164cfc1db305cd864cc1dc5f33827a2d74f7dde70ef36662",
|
||||||
|
"09d24205d8e1f3634448e881db200b88ad691bbdaabbccb885b225147ba4a93e",
|
||||||
|
"733be24324802a35944a73f355595f781de65d9d6e393bdabe879edcb77dfb62"
|
||||||
|
]
|
||||||
|
}
|
25
kinode/packages/app_store/ui/index.html
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<!-- This sets window.our.node -->
|
||||||
|
<script src="/our.js"></script>
|
||||||
|
|
||||||
|
<title>Package Store</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="pragma" content="no-cache" />
|
||||||
|
<meta http-equiv="cache-control" content="no-cache" />
|
||||||
|
<link rel="icon"
|
||||||
|
href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzc5IiBoZWlnaHQ9IjUxNCIgdmlld0JveD0iMCAwIDc3OSA1MTQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8c3R5bGU+CiAgICAgICAgQG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyaykgewogICAgICAgICAgICBzdmcgeyBmaWxsOiB3aGl0ZTsgfQogICAgICAgIH0KICAgICAgICBAbWVkaWEgKHByZWZlcnMtY29sb3Itc2NoZW1lOiBsaWdodCkgewogICAgICAgICAgICBzdmcgeyBmaWxsOiBibGFjazsgfQogICAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8cGF0aCBkPSJNNzUzLjA5MiA1LjkxOTMyQzc1Ni41NTcgNS4wOTk3NiA3NTUuOTYyIC0wLjAwMDEyMjA3IDc1Mi40MDEgLTAuMDAwMTIyMDdINDI2LjAwMUM0MjQuNzU1IC0wLjAwMDEyMjA3IDQyMy42MzkgMC43NzAyNyA0MjMuMTk3IDEuOTM1MzVMMjM2Ljk2OCA0OTIuNkMyMzUuNzI5IDQ5NS44NjUgMjQwLjEyMyA0OTguMjU1IDI0Mi4xOTEgNDk1LjQ0MUw1NjkuMzU3IDUwLjExMzJDNTY5Ljc3OCA0OS41MzkyIDU3MC4zOTEgNDkuMTMzOSA1NzEuMDg0IDQ4Ljk3TDc1My4wOTIgNS45MTkzMloiLz4KICAgIDxwYXRoIGQ9Ik0xMS45NjY1IDQwLjIyODhDOS4xMDk0OSAzOC43NzcgMTAuMjEzNSAzNC40NTgzIDEzLjQxNjcgMzQuNTU1N0w0MDQuMjczIDQ2LjQzNjdDNDA2LjMzNCA0Ni40OTkzIDQwNy43MTkgNDguNTc0OSA0MDYuOTg2IDUwLjUwMjNMMzQ3LjQzOCAyMDYuOTgxQzM0Ni44MDQgMjA4LjY0NyAzNDQuODY1IDIwOS4zOTYgMzQzLjI3NSAyMDguNTg4TDExLjk2NjUgNDAuMjI4OFoiLz4KPC9zdmc+Cg==">
|
||||||
|
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover" />
|
||||||
|
<link href='https://fonts.googleapis.com/css?family=Montserrat' rel='stylesheet'>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
9718
kinode/packages/app_store/ui/package-lock.json
generated
Normal file
54
kinode/packages/app_store/ui/package.json
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"name": "kit-ui",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite --port 3000",
|
||||||
|
"start": "vite --port 3000",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"copy": "mkdir -p ../pkg/ui && rm -rf ../pkg/ui/* && cp -r dist/* ../pkg/ui/",
|
||||||
|
"build:copy": "npm run tc && npm run build && npm run copy",
|
||||||
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"tc": "typechain --target ethers-v5 --out-dir src/abis/types/ \"./src/abis/**/*.json\""
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@ethersproject/hash": "^5.7.0",
|
||||||
|
"@kinode/client-api": "^0.1.0",
|
||||||
|
"@szhsin/react-menu": "^4.1.0",
|
||||||
|
"@web3-react/coinbase-wallet": "^8.2.3",
|
||||||
|
"@web3-react/core": "^8.2.2",
|
||||||
|
"@web3-react/gnosis-safe": "^8.2.4",
|
||||||
|
"@web3-react/injected-connector": "^6.0.7",
|
||||||
|
"@web3-react/metamask": "^8.2.3",
|
||||||
|
"@web3-react/network": "^8.2.3",
|
||||||
|
"@web3-react/types": "^8.2.2",
|
||||||
|
"@web3-react/walletconnect": "^8.2.3",
|
||||||
|
"@web3-react/walletconnect-connector": "^6.2.13",
|
||||||
|
"@web3-react/walletconnect-v2": "^8.5.1",
|
||||||
|
"ethers": "^5.7.2",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-icons": "^5.0.1",
|
||||||
|
"react-router-dom": "^6.21.3",
|
||||||
|
"tailwindcss": "^3.4.3",
|
||||||
|
"zustand": "^4.4.7"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@typechain/ethers-v5": "^11.1.1",
|
||||||
|
"@types/node": "^20.10.4",
|
||||||
|
"@types/react": "^18.2.43",
|
||||||
|
"@types/react-dom": "^18.2.17",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||||
|
"@typescript-eslint/parser": "^6.14.0",
|
||||||
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
|
"eslint": "^8.55.0",
|
||||||
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
|
"eslint-plugin-react-refresh": "^0.4.5",
|
||||||
|
"http-proxy-middleware": "^2.0.6",
|
||||||
|
"typechain": "^8.3.1",
|
||||||
|
"typescript": "^5.2.2",
|
||||||
|
"vite": "^5.0.8"
|
||||||
|
}
|
||||||
|
}
|
1
kinode/packages/app_store/ui/public/assets/vite.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
381
kinode/packages/app_store/ui/src/App.css
Normal file
@ -0,0 +1,381 @@
|
|||||||
|
#root {
|
||||||
|
max-width: 700px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem 0;
|
||||||
|
text-align: center;
|
||||||
|
width: 75%;
|
||||||
|
max-height: calc(100vh - 64px);
|
||||||
|
min-height: calc(100vh - 64px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* General */
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row.center {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row.between {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row.around {
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col.center {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background-color: var(--input-background);
|
||||||
|
border-radius: 0.75em;
|
||||||
|
border: 1px solid var(--orange-medium);
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.action-btn {
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.small {
|
||||||
|
padding: 0.25em 0.5em;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Specific */
|
||||||
|
|
||||||
|
.searchbar {
|
||||||
|
height: 2.25em;
|
||||||
|
padding: .5em 1em;
|
||||||
|
border-radius: 16px;
|
||||||
|
flex: 1;
|
||||||
|
background-color: var(--input-background);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchbar>input {
|
||||||
|
border: none;
|
||||||
|
height: 1.5em;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.connect-wallet {
|
||||||
|
margin: 1em auto 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-pkg-btn {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-pkg-btn.selected {
|
||||||
|
background-color: var(--bg-gray-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header {
|
||||||
|
cursor: pointer;
|
||||||
|
width: calc(100% - 10.3em);
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header.large:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header.small>img {
|
||||||
|
height: 3em;
|
||||||
|
margin-right: 1em;
|
||||||
|
border-radius: 0.375em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header>img {
|
||||||
|
height: 3em;
|
||||||
|
margin-right: 1em;
|
||||||
|
border-radius: 0.375em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header.large>img {
|
||||||
|
height: 5em;
|
||||||
|
margin-right: 1em;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header.large .app-name {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-entry {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-actions {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown {
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown>ul {
|
||||||
|
background-color: var(--orange-medium);
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
align-items: flex-start;
|
||||||
|
text-align: left;
|
||||||
|
border: 1px solid var(--orange-medium);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown .dropdown-header {
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown .dropdown-list {
|
||||||
|
position: absolute;
|
||||||
|
top: 1em;
|
||||||
|
right: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-selector {
|
||||||
|
margin: 0.25em 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-selector.selected {
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn {
|
||||||
|
margin-right: 1em;
|
||||||
|
justify-content: center;
|
||||||
|
width: 2.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-details {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-details .title {
|
||||||
|
width: 8em;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-details .value {
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
text-align: left;
|
||||||
|
max-width: calc(100% - 8em);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-details .value.underline {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-details .value.permission {
|
||||||
|
background-color: var(--bg-gray-medium);
|
||||||
|
border-radius: 2em;
|
||||||
|
padding: 0.25em 0.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-screenshots {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
overflow-x: scroll;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-screenshots>img {
|
||||||
|
margin-right: 1em;
|
||||||
|
max-height: 10em;
|
||||||
|
max-width: 100%;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
border: 1px solid var(--bg-gray-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--bg-gray-solid);
|
||||||
|
font-size: 1.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.f-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loading h3 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loader {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loader div {
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
margin: 6px;
|
||||||
|
border: 6px solid #fff;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: loader 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
|
||||||
|
border-color: #fff transparent transparent transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loader div:nth-child(1) {
|
||||||
|
animation-delay: -0.45s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loader div:nth-child(2) {
|
||||||
|
animation-delay: -0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#loader div:nth-child(3) {
|
||||||
|
animation-delay: -0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loader {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-entry {
|
||||||
|
margin-bottom: 0.25em;
|
||||||
|
color: inherit;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-entry:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-entry:first-child {
|
||||||
|
margin-top: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-apps-list {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
max-height: calc(100vh - 10em);
|
||||||
|
border-radius: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ellipsis {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
width: calc(100% - 6em);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title>div {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-backdrop {
|
||||||
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 3;
|
||||||
|
min-height: 10em;
|
||||||
|
min-width: 20em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-backdrop .close {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.5em;
|
||||||
|
right: 0.5em;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 200;
|
||||||
|
cursor: pointer;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
position: relative;
|
||||||
|
background-color: var(--dark-background);
|
||||||
|
color: black;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
max-width: 500px;
|
||||||
|
min-width: 300px;
|
||||||
|
color: var(--text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .modal-title {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal .modal-content {
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.new {
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.metadata {
|
||||||
|
gap: 0.5em;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.metadata input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.metadata .row {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.metadata .col.label {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title {
|
||||||
|
align-items: center;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
125
kinode/packages/app_store/ui/src/App.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
|
||||||
|
import { ethers } from "ethers";
|
||||||
|
import { Web3ReactProvider, Web3ReactHooks } from '@web3-react/core';
|
||||||
|
import type { MetaMask } from '@web3-react/metamask'
|
||||||
|
|
||||||
|
import { PackageStore, PackageStore__factory } from "./abis/types";
|
||||||
|
import StorePage from "./pages/StorePage";
|
||||||
|
import MyAppsPage from "./pages/MyAppsPage";
|
||||||
|
import AppPage from "./pages/AppPage";
|
||||||
|
import { MY_APPS_PATH } from "./constants/path";
|
||||||
|
import { ChainId, PACKAGE_STORE_ADDRESSES } from "./constants/chain";
|
||||||
|
import PublishPage from "./pages/PublishPage";
|
||||||
|
import { hooks as metaMaskHooks, metaMask } from './utils/metamask'
|
||||||
|
import "./App.css";
|
||||||
|
|
||||||
|
const connectors: [MetaMask, Web3ReactHooks][] = [
|
||||||
|
[metaMask, metaMaskHooks],
|
||||||
|
]
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface ImportMeta {
|
||||||
|
env: {
|
||||||
|
VITE_SEPOLIA_RPC_URL: string;
|
||||||
|
BASE_URL: string;
|
||||||
|
VITE_NODE_URL?: string;
|
||||||
|
DEV: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
interface Window {
|
||||||
|
our: {
|
||||||
|
node: string;
|
||||||
|
process: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
useProvider,
|
||||||
|
} = metaMaskHooks;
|
||||||
|
|
||||||
|
const RPC_URL = import.meta.env.VITE_SEPOLIA_RPC_URL;
|
||||||
|
const BASE_URL = import.meta.env.BASE_URL;
|
||||||
|
if (window.our) window.our.process = BASE_URL?.replace("/", "");
|
||||||
|
|
||||||
|
const PROXY_TARGET = `${
|
||||||
|
import.meta.env.VITE_NODE_URL || "http://localhost:8080"
|
||||||
|
}${BASE_URL}`;
|
||||||
|
|
||||||
|
// This env also has BASE_URL which should match the process + package name
|
||||||
|
const WEBSOCKET_URL = import.meta.env.DEV // eslint-disable-line
|
||||||
|
? `${PROXY_TARGET.replace("http", "ws")}`
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const provider = useProvider();
|
||||||
|
const [nodeConnected, setNodeConnected] = useState(true); // eslint-disable-line
|
||||||
|
|
||||||
|
const [packageAbi, setPackageAbi] = useState<PackageStore>(
|
||||||
|
PackageStore__factory.connect(
|
||||||
|
PACKAGE_STORE_ADDRESSES[ChainId.SEPOLIA],
|
||||||
|
new ethers.providers.JsonRpcProvider(RPC_URL)) // TODO: get the RPC URL from the wallet
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
provider?.getNetwork().then(network => {
|
||||||
|
if (network.chainId === ChainId.SEPOLIA) {
|
||||||
|
setPackageAbi(PackageStore__factory.connect(
|
||||||
|
PACKAGE_STORE_ADDRESSES[ChainId.SEPOLIA],
|
||||||
|
provider!.getSigner())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [provider])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// if (window.our?.node && window.our?.process) {
|
||||||
|
// const api = new KinodeClientApi({
|
||||||
|
// uri: WEBSOCKET_URL,
|
||||||
|
// nodeId: window.our.node,
|
||||||
|
// processId: window.our.process,
|
||||||
|
// onOpen: (_event, _api) => {
|
||||||
|
// console.log("Connected to Kinode");
|
||||||
|
// // api.send({ data: "Hello World" });
|
||||||
|
// },
|
||||||
|
// onMessage: (json, _api) => {
|
||||||
|
// console.log('UNEXPECTED WEBSOCKET MESSAGE', json)
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
// setApi(api);
|
||||||
|
// } else {
|
||||||
|
// setNodeConnected(false);
|
||||||
|
// }
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!nodeConnected) {
|
||||||
|
return (
|
||||||
|
<div className="node-not-connected">
|
||||||
|
<h2 style={{ color: "red" }}>Node not connected</h2>
|
||||||
|
<h4>
|
||||||
|
You need to start a node at {PROXY_TARGET} before you can use this UI
|
||||||
|
in development.
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = { provider, packageAbi };
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Web3ReactProvider connectors={connectors}>
|
||||||
|
<Router basename={BASE_URL}>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={<StorePage {...props} />} />
|
||||||
|
<Route path={MY_APPS_PATH} element={<MyAppsPage {...props} />} />
|
||||||
|
<Route path="/app-details/:id" element={<AppPage {...props} />} />
|
||||||
|
<Route path="/publish" element={<PublishPage {...props} />} />
|
||||||
|
</Routes>
|
||||||
|
</Router>
|
||||||
|
</Web3ReactProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
978
kinode/packages/app_store/ui/src/abis/PackageStore.json
Normal file
@ -0,0 +1,978 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "UPGRADE_INTERFACE_VERSION",
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "approve",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "to",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tokenId",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "apps",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "packageName",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "publisherKnsNodeId",
|
||||||
|
"type": "bytes32",
|
||||||
|
"internalType": "bytes32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "metadataUrl",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "metadataHash",
|
||||||
|
"type": "bytes32",
|
||||||
|
"internalType": "bytes32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "balanceOf",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "contractURI",
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "getApproved",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "tokenId",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "getInitializedVersion",
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint64",
|
||||||
|
"internalType": "uint64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "getPackageId",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "packageName",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "publisherName",
|
||||||
|
"type": "bytes",
|
||||||
|
"internalType": "bytes"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "pure"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "getPackageInfo",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "package",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "tuple",
|
||||||
|
"internalType": "struct IKinodeAppStore.PackageInfo",
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"name": "packageName",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "publisherKnsNodeId",
|
||||||
|
"type": "bytes32",
|
||||||
|
"internalType": "bytes32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "metadataUrl",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "metadataHash",
|
||||||
|
"type": "bytes32",
|
||||||
|
"internalType": "bytes32"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "getPackageInfo",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "packageName",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "publisherName",
|
||||||
|
"type": "bytes",
|
||||||
|
"internalType": "bytes"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "tuple",
|
||||||
|
"internalType": "struct IKinodeAppStore.PackageInfo",
|
||||||
|
"components": [
|
||||||
|
{
|
||||||
|
"name": "packageName",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "publisherKnsNodeId",
|
||||||
|
"type": "bytes32",
|
||||||
|
"internalType": "bytes32"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "metadataUrl",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "metadataHash",
|
||||||
|
"type": "bytes32",
|
||||||
|
"internalType": "bytes32"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "initialize",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "_knsResolver",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "contract KNSRegistryResolver"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "isApprovedForAll",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "operator",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool",
|
||||||
|
"internalType": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "knsResolver",
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "contract KNSRegistryResolver"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "name",
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "owner",
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "ownerOf",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "tokenId",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "proxiableUUID",
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bytes32",
|
||||||
|
"internalType": "bytes32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "registerApp",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "packageName",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "publisherName",
|
||||||
|
"type": "bytes",
|
||||||
|
"internalType": "bytes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "metadataUrl",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "metadataHash",
|
||||||
|
"type": "bytes32",
|
||||||
|
"internalType": "bytes32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "renounceOwnership",
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "safeTransferFrom",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "from",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "to",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tokenId",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "safeTransferFrom",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "from",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "to",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tokenId",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "data",
|
||||||
|
"type": "bytes",
|
||||||
|
"internalType": "bytes"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "setApprovalForAll",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "operator",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "approved",
|
||||||
|
"type": "bool",
|
||||||
|
"internalType": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "supportsInterface",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "interfaceId",
|
||||||
|
"type": "bytes4",
|
||||||
|
"internalType": "bytes4"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "bool",
|
||||||
|
"internalType": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "symbol",
|
||||||
|
"inputs": [],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "tokenURI",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "tokenId",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"stateMutability": "view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "transferFrom",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "from",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "to",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tokenId",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "transferOwnership",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "newOwner",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "unlistPacakge",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "package",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "updateContractURI",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "uri",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "updateMetadata",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "package",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "metadataUrl",
|
||||||
|
"type": "string",
|
||||||
|
"internalType": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "metadataHash",
|
||||||
|
"type": "bytes32",
|
||||||
|
"internalType": "bytes32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "nonpayable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "function",
|
||||||
|
"name": "upgradeToAndCall",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "newImplementation",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "data",
|
||||||
|
"type": "bytes",
|
||||||
|
"internalType": "bytes"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputs": [],
|
||||||
|
"stateMutability": "payable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"name": "AppMetadataUpdated",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "package",
|
||||||
|
"type": "uint256",
|
||||||
|
"indexed": true,
|
||||||
|
"internalType": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "metadataUrl",
|
||||||
|
"type": "string",
|
||||||
|
"indexed": false,
|
||||||
|
"internalType": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "metadataHash",
|
||||||
|
"type": "bytes32",
|
||||||
|
"indexed": false,
|
||||||
|
"internalType": "bytes32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"anonymous": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"name": "AppRegistered",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "package",
|
||||||
|
"type": "uint256",
|
||||||
|
"indexed": true,
|
||||||
|
"internalType": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "packageName",
|
||||||
|
"type": "string",
|
||||||
|
"indexed": false,
|
||||||
|
"internalType": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "publisherName",
|
||||||
|
"type": "bytes",
|
||||||
|
"indexed": false,
|
||||||
|
"internalType": "bytes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "metadataUrl",
|
||||||
|
"type": "string",
|
||||||
|
"indexed": false,
|
||||||
|
"internalType": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "metadataHash",
|
||||||
|
"type": "bytes32",
|
||||||
|
"indexed": false,
|
||||||
|
"internalType": "bytes32"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"anonymous": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"name": "Approval",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"type": "address",
|
||||||
|
"indexed": true,
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "approved",
|
||||||
|
"type": "address",
|
||||||
|
"indexed": true,
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tokenId",
|
||||||
|
"type": "uint256",
|
||||||
|
"indexed": true,
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"anonymous": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"name": "ApprovalForAll",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"type": "address",
|
||||||
|
"indexed": true,
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "operator",
|
||||||
|
"type": "address",
|
||||||
|
"indexed": true,
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "approved",
|
||||||
|
"type": "bool",
|
||||||
|
"indexed": false,
|
||||||
|
"internalType": "bool"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"anonymous": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"name": "Initialized",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "version",
|
||||||
|
"type": "uint64",
|
||||||
|
"indexed": false,
|
||||||
|
"internalType": "uint64"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"anonymous": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"name": "OwnershipTransferred",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "previousOwner",
|
||||||
|
"type": "address",
|
||||||
|
"indexed": true,
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "newOwner",
|
||||||
|
"type": "address",
|
||||||
|
"indexed": true,
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"anonymous": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"name": "Transfer",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "from",
|
||||||
|
"type": "address",
|
||||||
|
"indexed": true,
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "to",
|
||||||
|
"type": "address",
|
||||||
|
"indexed": true,
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tokenId",
|
||||||
|
"type": "uint256",
|
||||||
|
"indexed": true,
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"anonymous": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "event",
|
||||||
|
"name": "Upgraded",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "implementation",
|
||||||
|
"type": "address",
|
||||||
|
"indexed": true,
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"anonymous": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"name": "AddressEmptyCode",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "target",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"name": "ERC1967InvalidImplementation",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "implementation",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"name": "ERC1967NonPayable",
|
||||||
|
"inputs": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"name": "ERC721IncorrectOwner",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "sender",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tokenId",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"name": "ERC721InsufficientApproval",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "operator",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tokenId",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"name": "ERC721InvalidApprover",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "approver",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"name": "ERC721InvalidOperator",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "operator",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"name": "ERC721InvalidOwner",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"name": "ERC721InvalidReceiver",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "receiver",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"name": "ERC721InvalidSender",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "sender",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"name": "ERC721NonexistentToken",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "tokenId",
|
||||||
|
"type": "uint256",
|
||||||
|
"internalType": "uint256"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"name": "FailedInnerCall",
|
||||||
|
"inputs": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"name": "InvalidInitialization",
|
||||||
|
"inputs": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"name": "NotInitializing",
|
||||||
|
"inputs": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"name": "OwnableInvalidOwner",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"name": "OwnableUnauthorizedAccount",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "account",
|
||||||
|
"type": "address",
|
||||||
|
"internalType": "address"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"name": "UUPSUnauthorizedCallContext",
|
||||||
|
"inputs": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"name": "UUPSUnsupportedProxiableUUID",
|
||||||
|
"inputs": [
|
||||||
|
{
|
||||||
|
"name": "slot",
|
||||||
|
"type": "bytes32",
|
||||||
|
"internalType": "bytes32"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"name": "Unauthorized",
|
||||||
|
"inputs": []
|
||||||
|
}
|
||||||
|
]
|
1270
kinode/packages/app_store/ui/src/abis/types/PackageStore.ts
Normal file
44
kinode/packages/app_store/ui/src/abis/types/common.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/* Autogenerated file. Do not edit manually. */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
import type { Listener } from "@ethersproject/providers";
|
||||||
|
import type { Event, EventFilter } from "ethers";
|
||||||
|
|
||||||
|
export interface TypedEvent<
|
||||||
|
TArgsArray extends Array<any> = any,
|
||||||
|
TArgsObject = any
|
||||||
|
> extends Event {
|
||||||
|
args: TArgsArray & TArgsObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TypedEventFilter<_TEvent extends TypedEvent>
|
||||||
|
extends EventFilter {}
|
||||||
|
|
||||||
|
export interface TypedListener<TEvent extends TypedEvent> {
|
||||||
|
(...listenerArg: [...__TypechainArgsArray<TEvent>, TEvent]): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type __TypechainArgsArray<T> = T extends TypedEvent<infer U> ? U : never;
|
||||||
|
|
||||||
|
export interface OnEvent<TRes> {
|
||||||
|
<TEvent extends TypedEvent>(
|
||||||
|
eventFilter: TypedEventFilter<TEvent>,
|
||||||
|
listener: TypedListener<TEvent>
|
||||||
|
): TRes;
|
||||||
|
(eventName: string, listener: Listener): TRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MinEthersFactory<C, ARGS> = {
|
||||||
|
deploy(...a: ARGS[]): Promise<C>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type GetContractTypeFromFactory<F> = F extends MinEthersFactory<
|
||||||
|
infer C,
|
||||||
|
any
|
||||||
|
>
|
||||||
|
? C
|
||||||
|
: never;
|
||||||
|
|
||||||
|
export type GetARGsTypeFromFactory<F> = F extends MinEthersFactory<any, any>
|
||||||
|
? Parameters<F["deploy"]>
|
||||||
|
: never;
|
@ -0,0 +1,999 @@
|
|||||||
|
/* Autogenerated file. Do not edit manually. */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
import { Contract, Signer, utils } from "ethers";
|
||||||
|
import type { Provider } from "@ethersproject/providers";
|
||||||
|
import type { PackageStore, PackageStoreInterface } from "../PackageStore";
|
||||||
|
|
||||||
|
const _abi = [
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "UPGRADE_INTERFACE_VERSION",
|
||||||
|
inputs: [],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "string",
|
||||||
|
internalType: "string",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: "view",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "approve",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "to",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tokenId",
|
||||||
|
type: "uint256",
|
||||||
|
internalType: "uint256",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: "nonpayable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "apps",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "uint256",
|
||||||
|
internalType: "uint256",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: "packageName",
|
||||||
|
type: "string",
|
||||||
|
internalType: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "publisherKnsNodeId",
|
||||||
|
type: "bytes32",
|
||||||
|
internalType: "bytes32",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metadataUrl",
|
||||||
|
type: "string",
|
||||||
|
internalType: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metadataHash",
|
||||||
|
type: "bytes32",
|
||||||
|
internalType: "bytes32",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: "view",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "balanceOf",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "owner",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "uint256",
|
||||||
|
internalType: "uint256",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: "view",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "contractURI",
|
||||||
|
inputs: [],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "string",
|
||||||
|
internalType: "string",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: "view",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "getApproved",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "tokenId",
|
||||||
|
type: "uint256",
|
||||||
|
internalType: "uint256",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: "view",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "getInitializedVersion",
|
||||||
|
inputs: [],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "uint64",
|
||||||
|
internalType: "uint64",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: "view",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "getPackageId",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "packageName",
|
||||||
|
type: "string",
|
||||||
|
internalType: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "publisherName",
|
||||||
|
type: "bytes",
|
||||||
|
internalType: "bytes",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "uint256",
|
||||||
|
internalType: "uint256",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: "pure",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "getPackageInfo",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "package",
|
||||||
|
type: "uint256",
|
||||||
|
internalType: "uint256",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "tuple",
|
||||||
|
internalType: "struct IKinodeAppStore.PackageInfo",
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
name: "packageName",
|
||||||
|
type: "string",
|
||||||
|
internalType: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "publisherKnsNodeId",
|
||||||
|
type: "bytes32",
|
||||||
|
internalType: "bytes32",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metadataUrl",
|
||||||
|
type: "string",
|
||||||
|
internalType: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metadataHash",
|
||||||
|
type: "bytes32",
|
||||||
|
internalType: "bytes32",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: "view",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "getPackageInfo",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "packageName",
|
||||||
|
type: "string",
|
||||||
|
internalType: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "publisherName",
|
||||||
|
type: "bytes",
|
||||||
|
internalType: "bytes",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "tuple",
|
||||||
|
internalType: "struct IKinodeAppStore.PackageInfo",
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
name: "packageName",
|
||||||
|
type: "string",
|
||||||
|
internalType: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "publisherKnsNodeId",
|
||||||
|
type: "bytes32",
|
||||||
|
internalType: "bytes32",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metadataUrl",
|
||||||
|
type: "string",
|
||||||
|
internalType: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metadataHash",
|
||||||
|
type: "bytes32",
|
||||||
|
internalType: "bytes32",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: "view",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "initialize",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "_knsResolver",
|
||||||
|
type: "address",
|
||||||
|
internalType: "contract KNSRegistryResolver",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: "nonpayable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "isApprovedForAll",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "owner",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "operator",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "bool",
|
||||||
|
internalType: "bool",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: "view",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "knsResolver",
|
||||||
|
inputs: [],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "address",
|
||||||
|
internalType: "contract KNSRegistryResolver",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: "view",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "name",
|
||||||
|
inputs: [],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "string",
|
||||||
|
internalType: "string",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: "view",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "owner",
|
||||||
|
inputs: [],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: "view",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "ownerOf",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "tokenId",
|
||||||
|
type: "uint256",
|
||||||
|
internalType: "uint256",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: "view",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "proxiableUUID",
|
||||||
|
inputs: [],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "bytes32",
|
||||||
|
internalType: "bytes32",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: "view",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "registerApp",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "packageName",
|
||||||
|
type: "string",
|
||||||
|
internalType: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "publisherName",
|
||||||
|
type: "bytes",
|
||||||
|
internalType: "bytes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metadataUrl",
|
||||||
|
type: "string",
|
||||||
|
internalType: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metadataHash",
|
||||||
|
type: "bytes32",
|
||||||
|
internalType: "bytes32",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: "nonpayable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "renounceOwnership",
|
||||||
|
inputs: [],
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: "nonpayable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "safeTransferFrom",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "from",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "to",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tokenId",
|
||||||
|
type: "uint256",
|
||||||
|
internalType: "uint256",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: "nonpayable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "safeTransferFrom",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "from",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "to",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tokenId",
|
||||||
|
type: "uint256",
|
||||||
|
internalType: "uint256",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "data",
|
||||||
|
type: "bytes",
|
||||||
|
internalType: "bytes",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: "nonpayable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "setApprovalForAll",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "operator",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "approved",
|
||||||
|
type: "bool",
|
||||||
|
internalType: "bool",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: "nonpayable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "supportsInterface",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "interfaceId",
|
||||||
|
type: "bytes4",
|
||||||
|
internalType: "bytes4",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "bool",
|
||||||
|
internalType: "bool",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: "view",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "symbol",
|
||||||
|
inputs: [],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "string",
|
||||||
|
internalType: "string",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: "view",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "tokenURI",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "tokenId",
|
||||||
|
type: "uint256",
|
||||||
|
internalType: "uint256",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
type: "string",
|
||||||
|
internalType: "string",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stateMutability: "view",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "transferFrom",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "from",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "to",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tokenId",
|
||||||
|
type: "uint256",
|
||||||
|
internalType: "uint256",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: "nonpayable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "transferOwnership",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "newOwner",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: "nonpayable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "unlistPacakge",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "package",
|
||||||
|
type: "uint256",
|
||||||
|
internalType: "uint256",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: "nonpayable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "updateContractURI",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "uri",
|
||||||
|
type: "string",
|
||||||
|
internalType: "string",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: "nonpayable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "updateMetadata",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "package",
|
||||||
|
type: "uint256",
|
||||||
|
internalType: "uint256",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metadataUrl",
|
||||||
|
type: "string",
|
||||||
|
internalType: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metadataHash",
|
||||||
|
type: "bytes32",
|
||||||
|
internalType: "bytes32",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: "nonpayable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "function",
|
||||||
|
name: "upgradeToAndCall",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "newImplementation",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "data",
|
||||||
|
type: "bytes",
|
||||||
|
internalType: "bytes",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
outputs: [],
|
||||||
|
stateMutability: "payable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "event",
|
||||||
|
name: "AppMetadataUpdated",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "package",
|
||||||
|
type: "uint256",
|
||||||
|
indexed: true,
|
||||||
|
internalType: "uint256",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metadataUrl",
|
||||||
|
type: "string",
|
||||||
|
indexed: false,
|
||||||
|
internalType: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metadataHash",
|
||||||
|
type: "bytes32",
|
||||||
|
indexed: false,
|
||||||
|
internalType: "bytes32",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
anonymous: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "event",
|
||||||
|
name: "AppRegistered",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "package",
|
||||||
|
type: "uint256",
|
||||||
|
indexed: true,
|
||||||
|
internalType: "uint256",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "packageName",
|
||||||
|
type: "string",
|
||||||
|
indexed: false,
|
||||||
|
internalType: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "publisherName",
|
||||||
|
type: "bytes",
|
||||||
|
indexed: false,
|
||||||
|
internalType: "bytes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metadataUrl",
|
||||||
|
type: "string",
|
||||||
|
indexed: false,
|
||||||
|
internalType: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "metadataHash",
|
||||||
|
type: "bytes32",
|
||||||
|
indexed: false,
|
||||||
|
internalType: "bytes32",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
anonymous: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "event",
|
||||||
|
name: "Approval",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "owner",
|
||||||
|
type: "address",
|
||||||
|
indexed: true,
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "approved",
|
||||||
|
type: "address",
|
||||||
|
indexed: true,
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tokenId",
|
||||||
|
type: "uint256",
|
||||||
|
indexed: true,
|
||||||
|
internalType: "uint256",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
anonymous: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "event",
|
||||||
|
name: "ApprovalForAll",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "owner",
|
||||||
|
type: "address",
|
||||||
|
indexed: true,
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "operator",
|
||||||
|
type: "address",
|
||||||
|
indexed: true,
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "approved",
|
||||||
|
type: "bool",
|
||||||
|
indexed: false,
|
||||||
|
internalType: "bool",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
anonymous: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "event",
|
||||||
|
name: "Initialized",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "version",
|
||||||
|
type: "uint64",
|
||||||
|
indexed: false,
|
||||||
|
internalType: "uint64",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
anonymous: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "event",
|
||||||
|
name: "OwnershipTransferred",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "previousOwner",
|
||||||
|
type: "address",
|
||||||
|
indexed: true,
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "newOwner",
|
||||||
|
type: "address",
|
||||||
|
indexed: true,
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
anonymous: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "event",
|
||||||
|
name: "Transfer",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "from",
|
||||||
|
type: "address",
|
||||||
|
indexed: true,
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "to",
|
||||||
|
type: "address",
|
||||||
|
indexed: true,
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tokenId",
|
||||||
|
type: "uint256",
|
||||||
|
indexed: true,
|
||||||
|
internalType: "uint256",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
anonymous: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "event",
|
||||||
|
name: "Upgraded",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "implementation",
|
||||||
|
type: "address",
|
||||||
|
indexed: true,
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
anonymous: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
name: "AddressEmptyCode",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "target",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
name: "ERC1967InvalidImplementation",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "implementation",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
name: "ERC1967NonPayable",
|
||||||
|
inputs: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
name: "ERC721IncorrectOwner",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "sender",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tokenId",
|
||||||
|
type: "uint256",
|
||||||
|
internalType: "uint256",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "owner",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
name: "ERC721InsufficientApproval",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "operator",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tokenId",
|
||||||
|
type: "uint256",
|
||||||
|
internalType: "uint256",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
name: "ERC721InvalidApprover",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "approver",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
name: "ERC721InvalidOperator",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "operator",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
name: "ERC721InvalidOwner",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "owner",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
name: "ERC721InvalidReceiver",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "receiver",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
name: "ERC721InvalidSender",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "sender",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
name: "ERC721NonexistentToken",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "tokenId",
|
||||||
|
type: "uint256",
|
||||||
|
internalType: "uint256",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
name: "FailedInnerCall",
|
||||||
|
inputs: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
name: "InvalidInitialization",
|
||||||
|
inputs: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
name: "NotInitializing",
|
||||||
|
inputs: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
name: "OwnableInvalidOwner",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "owner",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
name: "OwnableUnauthorizedAccount",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "account",
|
||||||
|
type: "address",
|
||||||
|
internalType: "address",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
name: "UUPSUnauthorizedCallContext",
|
||||||
|
inputs: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
name: "UUPSUnsupportedProxiableUUID",
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
name: "slot",
|
||||||
|
type: "bytes32",
|
||||||
|
internalType: "bytes32",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "error",
|
||||||
|
name: "Unauthorized",
|
||||||
|
inputs: [],
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export class PackageStore__factory {
|
||||||
|
static readonly abi = _abi;
|
||||||
|
static createInterface(): PackageStoreInterface {
|
||||||
|
return new utils.Interface(_abi) as PackageStoreInterface;
|
||||||
|
}
|
||||||
|
static connect(
|
||||||
|
address: string,
|
||||||
|
signerOrProvider: Signer | Provider
|
||||||
|
): PackageStore {
|
||||||
|
return new Contract(address, _abi, signerOrProvider) as PackageStore;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
/* Autogenerated file. Do not edit manually. */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export { PackageStore__factory } from "./PackageStore__factory";
|
6
kinode/packages/app_store/ui/src/abis/types/index.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
/* Autogenerated file. Do not edit manually. */
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
export type { PackageStore } from "./PackageStore";
|
||||||
|
export * as factories from "./factories";
|
||||||
|
export { PackageStore__factory } from "./factories/PackageStore__factory";
|
BIN
kinode/packages/app_store/ui/src/assets/background.jpg
Normal file
After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 431 B After Width: | Height: | Size: 431 B |
1
kinode/packages/app_store/ui/src/assets/react.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
After Width: | Height: | Size: 4.0 KiB |
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#FFF5D9" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M6 9l6 6 6-6"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 188 B |
1
kinode/packages/app_store/ui/src/assets/vite.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
221
kinode/packages/app_store/ui/src/components/ActionButton.tsx
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
import React, { FormEvent, useCallback, useEffect, useMemo, useState } from "react";
|
||||||
|
import { AppInfo } from "../types/Apps";
|
||||||
|
import useAppsStore from "../store/apps-store";
|
||||||
|
import Modal from "./Modal";
|
||||||
|
import { getAppName } from "../utils/app";
|
||||||
|
import Loader from "./Loader";
|
||||||
|
|
||||||
|
interface ActionButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
|
||||||
|
app: AppInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ActionButton({ app, ...props }: ActionButtonProps) {
|
||||||
|
const { updateApp, downloadApp, installApp, getCaps, getMyApp } =
|
||||||
|
useAppsStore();
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
const [mirror, setMirror] = useState(app.metadata?.properties?.mirrors?.[0] || "Other");
|
||||||
|
const [customMirror, setCustomMirror] = useState("");
|
||||||
|
const [caps, setCaps] = useState<string[]>([]);
|
||||||
|
const [loading, setLoading] = useState("");
|
||||||
|
|
||||||
|
const { clean, installed, downloaded, updatable } = useMemo(() => {
|
||||||
|
const versions = Object.entries(app?.metadata?.properties?.code_hashes || {});
|
||||||
|
const latestHash = (versions.find(([v]) => v === app.metadata?.properties?.current_version) || [])[1];
|
||||||
|
|
||||||
|
const installed = app.installed;
|
||||||
|
const downloaded = Boolean(app.state);
|
||||||
|
|
||||||
|
const updatable =
|
||||||
|
Boolean(app.state?.our_version && latestHash) &&
|
||||||
|
app.state?.our_version !== latestHash &&
|
||||||
|
app.publisher !== window.our.node;
|
||||||
|
return {
|
||||||
|
clean: !installed && !downloaded && !updatable,
|
||||||
|
installed,
|
||||||
|
downloaded,
|
||||||
|
updatable,
|
||||||
|
};
|
||||||
|
}, [app]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setMirror(app.metadata?.properties?.mirrors?.[0] || "Other");
|
||||||
|
}, [app.metadata?.properties?.mirrors]);
|
||||||
|
|
||||||
|
const onClick = useCallback(async () => {
|
||||||
|
if (installed && !updatable) {
|
||||||
|
window.alert("App is installed");
|
||||||
|
} else {
|
||||||
|
if (downloaded) {
|
||||||
|
getCaps(app).then((manifest) => {
|
||||||
|
setCaps(manifest.request_capabilities);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setShowModal(true);
|
||||||
|
}
|
||||||
|
}, [app, installed, downloaded, updatable, setShowModal, getCaps]);
|
||||||
|
|
||||||
|
const download = useCallback(async (e: FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const targetMirror = mirror === "Other" ? customMirror : mirror;
|
||||||
|
|
||||||
|
if (!targetMirror) {
|
||||||
|
window.alert("Please select a mirror");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(`Downloading ${getAppName(app)}...`);
|
||||||
|
await downloadApp(app, targetMirror);
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
getMyApp(app)
|
||||||
|
.then(() => {
|
||||||
|
setLoading("");
|
||||||
|
setShowModal(false);
|
||||||
|
clearInterval(interval);
|
||||||
|
})
|
||||||
|
.catch(console.log);
|
||||||
|
}, 2000);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
window.alert(
|
||||||
|
`Failed to download app from ${targetMirror}, please try a different mirror.`
|
||||||
|
);
|
||||||
|
setLoading("");
|
||||||
|
}
|
||||||
|
}, [mirror, customMirror, app, downloadApp, getMyApp]);
|
||||||
|
|
||||||
|
const install = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setLoading(`Installing ${getAppName(app)}...`);
|
||||||
|
await installApp(app);
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
getMyApp(app)
|
||||||
|
.then((app) => {
|
||||||
|
if (!app.installed) return;
|
||||||
|
setLoading("");
|
||||||
|
setShowModal(false);
|
||||||
|
clearInterval(interval);
|
||||||
|
})
|
||||||
|
.catch(console.log);
|
||||||
|
}, 2000);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
window.alert(`Failed to install, please try again.`);
|
||||||
|
setLoading("");
|
||||||
|
}
|
||||||
|
}, [app, installApp, getMyApp]);
|
||||||
|
|
||||||
|
const update = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
setLoading(`Updating ${getAppName(app)}...`);
|
||||||
|
await updateApp(app);
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
getMyApp(app)
|
||||||
|
.then((app) => {
|
||||||
|
if (!app.installed) return;
|
||||||
|
setLoading("");
|
||||||
|
setShowModal(false);
|
||||||
|
clearInterval(interval);
|
||||||
|
})
|
||||||
|
.catch(console.log);
|
||||||
|
}, 2000);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
window.alert(`Failed to update, please try again.`);
|
||||||
|
setLoading("");
|
||||||
|
}
|
||||||
|
}, [app, updateApp, getMyApp]);
|
||||||
|
|
||||||
|
const appName = getAppName(app);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
{...props}
|
||||||
|
type="button"
|
||||||
|
className={`small action-btn ${props.className || ""}`}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{installed && updatable
|
||||||
|
? "Update"
|
||||||
|
: installed
|
||||||
|
? "Installed"
|
||||||
|
: downloaded
|
||||||
|
? "Install"
|
||||||
|
: "Download"}
|
||||||
|
</button>
|
||||||
|
<Modal show={showModal} hide={() => setShowModal(false)}>
|
||||||
|
{loading ? (
|
||||||
|
<Loader msg={loading} />
|
||||||
|
) : clean ? (
|
||||||
|
<form className="col" style={{alignItems: "center", gap: "1em"}} onSubmit={download}>
|
||||||
|
<h4>Download '{appName}'</h4>
|
||||||
|
<h5 style={{ margin: 0 }}>Select Mirror</h5>
|
||||||
|
<select value={mirror} onChange={(e) => setMirror(e.target.value)}>
|
||||||
|
{((app.metadata?.properties?.mirrors || []).concat(["Other"])).map((m) => (
|
||||||
|
<option key={m} value={m}>
|
||||||
|
{m}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
{mirror === "Other" && (
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={customMirror}
|
||||||
|
onChange={(e) => setCustomMirror(e.target.value)}
|
||||||
|
placeholder="Mirror, i.e. 'template.os'"
|
||||||
|
style={{ padding: "0.5em", maxWidth: 240, width: "100%" }}
|
||||||
|
required
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<button type="submit">
|
||||||
|
Download
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
) : downloaded ? (
|
||||||
|
<>
|
||||||
|
<h4>Approve App Permissions</h4>
|
||||||
|
<h5 style={{ margin: 0 }}>
|
||||||
|
{getAppName(app)} needs the following permissions:
|
||||||
|
</h5>
|
||||||
|
<ul className="col" style={{ alignItems: "flex-start" }}>
|
||||||
|
{caps.map((cap) => (
|
||||||
|
<li key={cap}>{cap}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<button type="button" onClick={install}>
|
||||||
|
Approve & Install
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<h4>Approve App Permissions</h4>
|
||||||
|
<h5 style={{ margin: 0 }}>
|
||||||
|
{getAppName(app)} needs the following permissions:
|
||||||
|
</h5>
|
||||||
|
{/* <h5>Send Messages:</h5> */}
|
||||||
|
<br />
|
||||||
|
<ul className="col" style={{ alignItems: "flex-start" }}>
|
||||||
|
{caps.map((cap) => (
|
||||||
|
<li key={cap}>{cap}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
{/* <h5>Receive Messages:</h5>
|
||||||
|
<ul>
|
||||||
|
{caps.map((cap) => (
|
||||||
|
<li key={cap}>{cap}</li>
|
||||||
|
))}
|
||||||
|
</ul> */}
|
||||||
|
<button type="button" onClick={update}>
|
||||||
|
Approve & Update
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
23
kinode/packages/app_store/ui/src/components/AppEntry.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import AppHeader from "./AppHeader";
|
||||||
|
import ActionButton from "./ActionButton";
|
||||||
|
import { AppInfo } from "../types/Apps";
|
||||||
|
import { appId } from "../utils/app";
|
||||||
|
import MoreActions from "./MoreActions";
|
||||||
|
|
||||||
|
interface AppEntryProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
|
app: AppInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AppEntry({ app, ...props }: AppEntryProps) {
|
||||||
|
return (
|
||||||
|
<div {...props} key={appId(app)} className="app-entry row between">
|
||||||
|
<AppHeader app={app} size="small" />
|
||||||
|
<div className="app-actions row">
|
||||||
|
{!app.state?.caps_approved && <ActionButton app={app} style={{ marginRight: "1em" }} />}
|
||||||
|
<MoreActions app={app} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
39
kinode/packages/app_store/ui/src/components/AppHeader.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { AppInfo } from "../types/Apps";
|
||||||
|
import { appId } from "../utils/app";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
interface AppHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
|
app: AppInfo;
|
||||||
|
size?: "small" | "medium" | "large";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AppHeader({
|
||||||
|
app,
|
||||||
|
size = "medium",
|
||||||
|
...props
|
||||||
|
}: AppHeaderProps) {
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...props}
|
||||||
|
className={`app-header row ${size} ${props.className || ""}`}
|
||||||
|
onClick={() => navigate(`/app-details/${appId(app)}`)}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={
|
||||||
|
app.metadata?.image ||
|
||||||
|
"https://png.pngtree.com/png-vector/20190215/ourmid/pngtree-vector-question-mark-icon-png-image_515448.jpg"
|
||||||
|
}
|
||||||
|
alt="app icon"
|
||||||
|
/>
|
||||||
|
<div className="col title">
|
||||||
|
<div className="app-name ellipsis">{app.metadata?.name || appId(app)}</div>
|
||||||
|
{app.metadata?.description && size !== "large" && (
|
||||||
|
<div className="ellipsis">{app.metadata?.description?.slice(0, 100)}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
30
kinode/packages/app_store/ui/src/components/Checkbox.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default function Checkbox({
|
||||||
|
readOnly = false,
|
||||||
|
checked,
|
||||||
|
setChecked,
|
||||||
|
}: {
|
||||||
|
readOnly?: boolean;
|
||||||
|
checked: boolean;
|
||||||
|
setChecked?: (checked: boolean) => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div style={{ position: "relative" }}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="checked"
|
||||||
|
name="checked"
|
||||||
|
checked={checked}
|
||||||
|
onChange={(e) => setChecked && setChecked(e.target.checked)}
|
||||||
|
autoFocus
|
||||||
|
readOnly={readOnly}
|
||||||
|
/>
|
||||||
|
{checked && (
|
||||||
|
<span onClick={() => setChecked && setChecked(false)} className="checkmark">
|
||||||
|
✓
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
16
kinode/packages/app_store/ui/src/components/Dropdown.tsx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FaEllipsisH } from 'react-icons/fa';
|
||||||
|
import { Menu, MenuButton } from '@szhsin/react-menu';
|
||||||
|
|
||||||
|
interface DropdownProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Dropdown({ ...props }: DropdownProps) {
|
||||||
|
return (
|
||||||
|
<Menu {...props} className={"dropdown " + props.className} menuButton={<MenuButton className="small">
|
||||||
|
<FaEllipsisH style={{ marginBottom: '-0.125em' }} />
|
||||||
|
</MenuButton>}>
|
||||||
|
{props.children}
|
||||||
|
</Menu>
|
||||||
|
)
|
||||||
|
}
|
14
kinode/packages/app_store/ui/src/components/Loader.tsx
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
type LoaderProps = {
|
||||||
|
msg: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Loader({ msg }: LoaderProps) {
|
||||||
|
return (
|
||||||
|
<div id="loading" className="flex flex-col text-center">
|
||||||
|
<h4>{msg}</h4>
|
||||||
|
<div id="loader"> <div /> <div /> <div /> <div /> </div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
293
kinode/packages/app_store/ui/src/components/MetadataForm.tsx
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import { AppInfo } from "../types/Apps";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
app?: AppInfo;
|
||||||
|
packageName: string;
|
||||||
|
publisherId: string;
|
||||||
|
goBack: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VALID_VERSION_REGEX = /^\d+\.\d+\.\d+$/;
|
||||||
|
|
||||||
|
const MetadataForm = ({ app, packageName, publisherId, goBack }: Props) => {
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: app?.metadata?.name || "",
|
||||||
|
description: app?.metadata?.description || "",
|
||||||
|
image: app?.metadata?.image || "",
|
||||||
|
external_url: app?.metadata?.external_url || "",
|
||||||
|
animation_url: app?.metadata?.animation_url || "",
|
||||||
|
// properties, which can come from the app itself
|
||||||
|
package_name: packageName,
|
||||||
|
current_version: "",
|
||||||
|
publisher: publisherId,
|
||||||
|
mirrors: [publisherId],
|
||||||
|
});
|
||||||
|
|
||||||
|
const [codeHashes, setCodeHashes] = useState<[string, string][]>(
|
||||||
|
Object.entries(app?.metadata?.properties?.code_hashes || {}).concat([
|
||||||
|
["", app?.state?.our_version || ""],
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleFieldChange = (field, value) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
[field]: value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleFieldChange("package_name", packageName);
|
||||||
|
}, [packageName]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handleFieldChange("publisher", publisherId);
|
||||||
|
}, [publisherId]);
|
||||||
|
|
||||||
|
const handleSubmit = useCallback(() => {
|
||||||
|
const code_hashes = codeHashes.reduce((acc, [version, hash]) => {
|
||||||
|
acc[version] = hash;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
if (!VALID_VERSION_REGEX.test(formData.current_version)) {
|
||||||
|
window.alert("Current version must be in the format x.y.z");
|
||||||
|
return;
|
||||||
|
} else if (!code_hashes[formData.current_version]) {
|
||||||
|
window.alert(
|
||||||
|
`Code hashes must include current version (${formData.current_version})`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
} else if (
|
||||||
|
!Object.keys(code_hashes).reduce(
|
||||||
|
(valid, version) => valid && VALID_VERSION_REGEX.test(version),
|
||||||
|
true
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
window.alert("Code hashes must be a JSON object with valid version keys");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsonData = JSON.stringify({
|
||||||
|
name: formData.name,
|
||||||
|
description: formData.description,
|
||||||
|
image: formData.image,
|
||||||
|
external_url: formData.external_url,
|
||||||
|
animation_url: formData.animation_url,
|
||||||
|
properties: {
|
||||||
|
package_name: formData.package_name,
|
||||||
|
current_version: formData.current_version,
|
||||||
|
publisher: formData.publisher,
|
||||||
|
mirrors: formData.mirrors,
|
||||||
|
code_hashes,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const blob = new Blob([jsonData], { type: "application/json" });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = url;
|
||||||
|
a.download =
|
||||||
|
formData.package_name + "_" + formData.publisher + "_metadata.json";
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}, [formData, codeHashes]);
|
||||||
|
|
||||||
|
const handleClearForm = () => {
|
||||||
|
setFormData({
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
image: "",
|
||||||
|
external_url: "",
|
||||||
|
animation_url: "",
|
||||||
|
|
||||||
|
package_name: "",
|
||||||
|
current_version: "",
|
||||||
|
publisher: "",
|
||||||
|
mirrors: [],
|
||||||
|
});
|
||||||
|
setCodeHashes([]);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className="col card metadata" style={{ gap: "0.5em" }}>
|
||||||
|
<h4>Fill out metadata</h4>
|
||||||
|
<div className="col label">
|
||||||
|
<label className="metadata-label">Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Name"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) => handleFieldChange("name", e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col label">
|
||||||
|
<label className="metadata-label">Description</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Description"
|
||||||
|
value={formData.description}
|
||||||
|
onChange={(e) => handleFieldChange("description", e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col label">
|
||||||
|
<label className="metadata-label">Image URL</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Image URL"
|
||||||
|
value={formData.image}
|
||||||
|
onChange={(e) => handleFieldChange("image", e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col label">
|
||||||
|
<label className="metadata-label">External URL</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="External URL"
|
||||||
|
value={formData.external_url}
|
||||||
|
onChange={(e) => handleFieldChange("external_url", e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col label">
|
||||||
|
<label className="metadata-label">Animation URL</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Animation URL"
|
||||||
|
value={formData.animation_url}
|
||||||
|
onChange={(e) => handleFieldChange("animation_url", e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col label">
|
||||||
|
<label className="metadata-label">Package Name</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Package Name"
|
||||||
|
value={formData.package_name}
|
||||||
|
onChange={(e) => handleFieldChange("package_name", e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col label">
|
||||||
|
<label className="metadata-label">Current Version</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Current Version"
|
||||||
|
value={formData.current_version}
|
||||||
|
onChange={(e) => handleFieldChange("current_version", e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col label">
|
||||||
|
<label className="metadata-label">Publisher</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Publisher"
|
||||||
|
value={formData.publisher}
|
||||||
|
onChange={(e) => handleFieldChange("publisher", e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col label">
|
||||||
|
<label className="metadata-label">Mirrors (separated by commas)</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Mirrors (separated by commas)"
|
||||||
|
value={formData.mirrors.join(",")}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleFieldChange(
|
||||||
|
"mirrors",
|
||||||
|
e.target.value.split(",").map((m) => m.trim())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="col label"
|
||||||
|
style={{
|
||||||
|
gap: "0.5em",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="row"
|
||||||
|
style={{
|
||||||
|
gap: "0.5em",
|
||||||
|
marginTop: 0,
|
||||||
|
justifyContent: "space-between",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h5 style={{ margin: 0 }}>Code Hashes</h5>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setCodeHashes([...codeHashes, ["", ""]])}
|
||||||
|
className="small"
|
||||||
|
>
|
||||||
|
Add code hash
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{codeHashes.map(([version, hash], ind, arr) => (
|
||||||
|
<div
|
||||||
|
key={ind + "_code_hash"}
|
||||||
|
className="row"
|
||||||
|
style={{ gap: "0.5em", marginTop: 0, width: "100%" }}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Version"
|
||||||
|
value={version}
|
||||||
|
onChange={(e) =>
|
||||||
|
setCodeHashes((prev) => {
|
||||||
|
const newHashes = [...prev];
|
||||||
|
newHashes[ind][0] = e.target.value;
|
||||||
|
return newHashes;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Hash"
|
||||||
|
value={hash}
|
||||||
|
onChange={(e) =>
|
||||||
|
setCodeHashes((prev) => {
|
||||||
|
const newHashes = [...prev];
|
||||||
|
newHashes[ind][1] = e.target.value;
|
||||||
|
return newHashes;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
style={{ flex: 5 }}
|
||||||
|
/>
|
||||||
|
{arr.length > 1 && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
|
setCodeHashes((prev) => prev.filter((_, i) => i !== ind))
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
fontSize: "2em",
|
||||||
|
height: 32,
|
||||||
|
lineHeight: "1em",
|
||||||
|
padding: "0 0.2em",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="row" style={{ gap: "0.5em", margin: "1em 0" }}>
|
||||||
|
<button type="button" onClick={handleSubmit}>
|
||||||
|
Download JSON
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={handleClearForm}>
|
||||||
|
Clear Form
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={goBack}>
|
||||||
|
Done
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MetadataForm;
|
42
kinode/packages/app_store/ui/src/components/Modal.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import React, { MouseEvent } from 'react'
|
||||||
|
import { FaPlus } from 'react-icons/fa'
|
||||||
|
|
||||||
|
export interface ModalProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
|
show: boolean
|
||||||
|
hide: () => void
|
||||||
|
hideClose?: boolean
|
||||||
|
children: React.ReactNode,
|
||||||
|
title?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Modal: React.FC<ModalProps> = ({
|
||||||
|
show,
|
||||||
|
hide,
|
||||||
|
hideClose = false,
|
||||||
|
title,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const dontHide = (e: MouseEvent) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!show) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`modal-backdrop ${show ? 'show' : ''}`} onClick={hide}>
|
||||||
|
<div {...props} className={`col modal ${props.className || ''}`} onClick={dontHide}>
|
||||||
|
{Boolean(title) && <h4 className='modal-title'>{title}</h4>}
|
||||||
|
{!hideClose && (
|
||||||
|
<FaPlus className='close' onClick={hide} />
|
||||||
|
)}
|
||||||
|
<div className='col modal-content' onClick={dontHide}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Modal
|
82
kinode/packages/app_store/ui/src/components/MoreActions.tsx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { MenuItem } from "@szhsin/react-menu";
|
||||||
|
|
||||||
|
import Dropdown from "./Dropdown";
|
||||||
|
import { AppInfo } from "../types/Apps";
|
||||||
|
import { appId } from "../utils/app";
|
||||||
|
import useAppsStore from "../store/apps-store";
|
||||||
|
|
||||||
|
interface MoreActionsProps extends React.HTMLAttributes<HTMLButtonElement> {
|
||||||
|
app: AppInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MoreActions({ app }: MoreActionsProps) {
|
||||||
|
const { uninstallApp, setMirroring, setAutoUpdate } = useAppsStore();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const downloaded = Boolean(app.state);
|
||||||
|
|
||||||
|
if (!downloaded) {
|
||||||
|
if (!app.metadata) return <div style={{ width: 38 }} />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown>
|
||||||
|
{app.metadata?.description && (
|
||||||
|
<MenuItem
|
||||||
|
className="action-entry"
|
||||||
|
onClick={() => navigate(`/app-details/${appId(app)}`)}
|
||||||
|
>
|
||||||
|
View Details
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{app.metadata?.external_url && (
|
||||||
|
<MenuItem>
|
||||||
|
<a
|
||||||
|
style={{
|
||||||
|
color: "inherit",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
cursor: "pointer",
|
||||||
|
marginTop: "0.25em",
|
||||||
|
}}
|
||||||
|
target="_blank"
|
||||||
|
href={app.metadata?.external_url}
|
||||||
|
>
|
||||||
|
View Site
|
||||||
|
</a>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown>
|
||||||
|
<MenuItem
|
||||||
|
className="action-entry"
|
||||||
|
onClick={() => navigate(`/app-details/${appId(app)}`)}
|
||||||
|
>
|
||||||
|
View Details
|
||||||
|
</MenuItem>
|
||||||
|
{app.installed && (
|
||||||
|
<>
|
||||||
|
<MenuItem className="action-entry" onClick={() => uninstallApp(app)}>
|
||||||
|
Uninstall
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
className="action-entry"
|
||||||
|
onClick={() => setMirroring(app, !app.state?.mirroring)}
|
||||||
|
>
|
||||||
|
{app.state?.mirroring ? "Stop" : "Start"} Mirroring
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
className="action-entry"
|
||||||
|
onClick={() => setAutoUpdate(app, !app.state?.auto_update)}
|
||||||
|
>
|
||||||
|
{app.state?.auto_update ? "Disable" : "Enable"} Auto Update
|
||||||
|
</MenuItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
}
|
86
kinode/packages/app_store/ui/src/components/SearchHeader.tsx
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
import {
|
||||||
|
FaArrowLeft,
|
||||||
|
FaDownload,
|
||||||
|
FaRegTimesCircle,
|
||||||
|
FaSearch,
|
||||||
|
FaUpload,
|
||||||
|
} from "react-icons/fa";
|
||||||
|
|
||||||
|
import { MY_APPS_PATH } from "../constants/path";
|
||||||
|
|
||||||
|
interface SearchHeaderProps {
|
||||||
|
value?: string;
|
||||||
|
onChange?: (value: string) => void;
|
||||||
|
onBack?: () => void;
|
||||||
|
onlyMyApps?: boolean;
|
||||||
|
hideSearch?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SearchHeader({
|
||||||
|
value = "",
|
||||||
|
onChange = () => null,
|
||||||
|
onBack,
|
||||||
|
hideSearch = false,
|
||||||
|
}: SearchHeaderProps) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const location = useLocation();
|
||||||
|
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const canGoBack = location.key !== "default";
|
||||||
|
const isMyAppsPage = location.pathname === MY_APPS_PATH;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="search-header row between">
|
||||||
|
{location.pathname !== '/' ? (
|
||||||
|
<button className="back-btn col center" onClick={() => {
|
||||||
|
if (onBack) {
|
||||||
|
onBack()
|
||||||
|
} else {
|
||||||
|
canGoBack ? navigate(-1) : navigate('/')
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<FaArrowLeft />
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
className="back-btn col center"
|
||||||
|
onClick={() => navigate("/publish")}
|
||||||
|
>
|
||||||
|
<FaUpload />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{!hideSearch && (
|
||||||
|
<div className="searchbar row">
|
||||||
|
<FaSearch
|
||||||
|
className="search-icon"
|
||||||
|
onClick={() => inputRef.current?.focus()}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
onChange={(event) => onChange(event.target.value)}
|
||||||
|
value={value}
|
||||||
|
placeholder="Search for apps..."
|
||||||
|
/>
|
||||||
|
{value.length > 0 && (
|
||||||
|
<FaRegTimesCircle
|
||||||
|
className="search-icon"
|
||||||
|
style={{ margin: "0 -0.25em 0 0.25em" }}
|
||||||
|
onClick={() => onChange("")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="row">
|
||||||
|
<button
|
||||||
|
className={`my-pkg-btn row ${isMyAppsPage ? "selected" : ""}`}
|
||||||
|
onClick={() => (isMyAppsPage ? navigate(-1) : navigate(MY_APPS_PATH))}
|
||||||
|
>
|
||||||
|
<FaDownload style={{ marginRight: "0.5em" }} />
|
||||||
|
My Packages
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
16
kinode/packages/app_store/ui/src/constants/chain.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export enum ChainId {
|
||||||
|
SEPOLIA = 11155111,
|
||||||
|
OPTIMISM = 10,
|
||||||
|
OPTIMISM_GOERLI = 420,
|
||||||
|
LOCAL = 1337,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SEPOLIA_OPT_HEX = '0xaa36a7';
|
||||||
|
export const OPTIMISM_OPT_HEX = '0xa';
|
||||||
|
export const SEPOLIA_OPT_INT = '11155111';
|
||||||
|
|
||||||
|
// Sepolia (for now)
|
||||||
|
export const PACKAGE_STORE_ADDRESSES = {
|
||||||
|
[ChainId.SEPOLIA]: '0x18c39eB547A0060C6034f8bEaFB947D1C16eADF1',
|
||||||
|
// [ChainId.OPTIMISM]: '0x8f6e1c9C5a0fE0A7f9Cf0e9b3aF1A9c4f5c6A9e0',
|
||||||
|
};
|
22
kinode/packages/app_store/ui/src/constants/http.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
export enum HTTP_STATUS {
|
||||||
|
OK = 200,
|
||||||
|
CREATED = 201,
|
||||||
|
ACCEPTED = 202,
|
||||||
|
NO_CONTENT = 204,
|
||||||
|
MOVED_PERMANENTLY = 301,
|
||||||
|
FOUND = 302,
|
||||||
|
SEE_OTHER = 303,
|
||||||
|
NOT_MODIFIED = 304,
|
||||||
|
TEMPORARY_REDIRECT = 307,
|
||||||
|
PERMANENT_REDIRECT = 308,
|
||||||
|
BAD_REQUEST = 400,
|
||||||
|
UNAUTHORIZED = 401,
|
||||||
|
FORBIDDEN = 403,
|
||||||
|
NOT_FOUND = 404,
|
||||||
|
PAYLOAD_TOO_LARGE = 413,
|
||||||
|
UNSUPPORTED_MEDIA_TYPE = 415,
|
||||||
|
TOO_MANY_REQUESTS = 429,
|
||||||
|
INTERNAL_SERVER_ERROR = 500,
|
||||||
|
BAD_GATEWAY = 502,
|
||||||
|
SERVICE_UNAVAILABLE = 503
|
||||||
|
}
|
1
kinode/packages/app_store/ui/src/constants/path.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const MY_APPS_PATH = '/my-apps';
|
222
kinode/packages/app_store/ui/src/index.css
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--text-light: #FFF5D9;
|
||||||
|
--text-dark: #22211F;
|
||||||
|
--text-orange: #FF7533;
|
||||||
|
--orange-light: #F36822;
|
||||||
|
--orange-medium: #F35422;
|
||||||
|
--orange-burnt: #E25F35;
|
||||||
|
|
||||||
|
--medium-gray: #7E7E7E;
|
||||||
|
--gray-button: rgba(253, 245, 220, 0.25);
|
||||||
|
--dark-background: rgb(130, 59, 28);
|
||||||
|
--input-background: rgba(243, 84, 34, 0.25);
|
||||||
|
/* orange-medium */
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--text-light);
|
||||||
|
font-weight: 400;
|
||||||
|
background: url('./assets/background.jpg') no-repeat center center fixed;
|
||||||
|
background-size: cover;
|
||||||
|
background-color: var(--dark-background);
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
body,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
p,
|
||||||
|
a,
|
||||||
|
button,
|
||||||
|
input {
|
||||||
|
font-family: 'Barlow Condensed', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
line-height: 1.5em;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button {
|
||||||
|
all: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
input[type="password"],
|
||||||
|
input[type="checkbox"] {
|
||||||
|
padding: 1em;
|
||||||
|
border: 1px solid var(--orange-medium);
|
||||||
|
border-radius: 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 1em;
|
||||||
|
background-color: var(--input-background);
|
||||||
|
color: var(--text-light);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
input[type="password"] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
padding: 0.25em 0.8em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]:checked {
|
||||||
|
background-color: var(--orange-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkmark {
|
||||||
|
position: absolute;
|
||||||
|
left: 4px;
|
||||||
|
font-size: 24px;
|
||||||
|
top: -5px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
::placeholder {
|
||||||
|
color: var(--text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-input-placeholder::placeholder {
|
||||||
|
color: var(--text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-moz-placeholder::placeholder {
|
||||||
|
color: var(--text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-ms-input-placeholder {
|
||||||
|
color: var(--text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
[type='button'],
|
||||||
|
[type='reset'],
|
||||||
|
[type='submit'] {
|
||||||
|
padding: 0.75em 1em;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: 500;
|
||||||
|
border-width: 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: var(--orange-medium);
|
||||||
|
/* border-image: linear-gradient(to right, var(--orange-medium), var(--orange-light)); */
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--orange-medium);
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.125em;
|
||||||
|
transition: all 0.1s;
|
||||||
|
box-shadow: 0 1px 2px var(--orange-light);
|
||||||
|
color: var(--text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
button.alt {
|
||||||
|
background-color: var(--text-light);
|
||||||
|
color: var(--text-dark);
|
||||||
|
border-color: var(--text-light);
|
||||||
|
box-shadow: 0 1px 2px var(--text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
background-color: var(--medium-gray);
|
||||||
|
border: 1px solid var(--medium-gray);
|
||||||
|
box-shadow: 0 1px 2px var(--medium-gray);
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul,
|
||||||
|
li {
|
||||||
|
all: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
padding: 0.25em 0.5em;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
border: 1px solid var(--orange-medium);
|
||||||
|
background-color: var(--input-background);
|
||||||
|
color: var(--text-light);
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
/* Use a custom chevron image */
|
||||||
|
background-image: url('./assets/select-chevron.svg');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 8px center;
|
||||||
|
/* Adjust the horizontal position to control padding */
|
||||||
|
background-size: 16px;
|
||||||
|
/* Adjust size of the chevron */
|
||||||
|
padding-right: 2em;
|
||||||
|
/* Adjust the padding to make room for the chevron */
|
||||||
|
|
||||||
|
-webkit-appearance: none;
|
||||||
|
/* Removes default styling on WebKit browsers like Safari */
|
||||||
|
-moz-appearance: none;
|
||||||
|
/* Removes default styling on Firefox */
|
||||||
|
appearance: none;
|
||||||
|
/* Standard property, currently not fully supported */
|
||||||
|
}
|
10
kinode/packages/app_store/ui/src/main.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom/client'
|
||||||
|
import App from './App.tsx'
|
||||||
|
import './index.css'
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>,
|
||||||
|
)
|
127
kinode/packages/app_store/ui/src/pages/AppPage.tsx
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import React, { useState, useEffect, useMemo, useCallback } from "react";
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
|
||||||
|
import { AppInfo } from "../types/Apps";
|
||||||
|
import useAppsStore from "../store/apps-store";
|
||||||
|
import ActionButton from "../components/ActionButton";
|
||||||
|
import AppHeader from "../components/AppHeader";
|
||||||
|
import SearchHeader from "../components/SearchHeader";
|
||||||
|
import { PageProps } from "../types/Page";
|
||||||
|
import { appId } from "../utils/app";
|
||||||
|
|
||||||
|
interface AppPageProps extends PageProps {}
|
||||||
|
|
||||||
|
export default function AppPage(props: AppPageProps) {
|
||||||
|
// eslint-disable-line
|
||||||
|
const { myApps, listedApps, getListedApp } = useAppsStore();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const params = useParams();
|
||||||
|
const [app, setApp] = useState<AppInfo | undefined>(undefined);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const myApp = myApps.local.find((a) => appId(a) === params.id);
|
||||||
|
if (myApp) return setApp(myApp);
|
||||||
|
|
||||||
|
if (params.id) {
|
||||||
|
const app = listedApps.find((a) => appId(a) === params.id);
|
||||||
|
if (app) {
|
||||||
|
setApp(app);
|
||||||
|
} else {
|
||||||
|
getListedApp(params.id)
|
||||||
|
.then((app) => setApp(app))
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [params.id]);
|
||||||
|
|
||||||
|
const goToPublish = useCallback(() => {
|
||||||
|
navigate("/publish", { state: { app } });
|
||||||
|
}, [app, navigate]);
|
||||||
|
|
||||||
|
const version = useMemo(
|
||||||
|
() => app?.metadata?.properties?.current_version || "Unknown",
|
||||||
|
[app]
|
||||||
|
);
|
||||||
|
const versions = Object.entries(app?.metadata?.properties?.code_hashes || {});
|
||||||
|
const hash =
|
||||||
|
app?.state?.our_version ||
|
||||||
|
(versions[(versions.length || 1) - 1] || ["", ""])[1];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ width: "100%" }}>
|
||||||
|
<SearchHeader value="" onChange={() => null} hideSearch />
|
||||||
|
<div className="card" style={{ marginTop: "1em" }}>
|
||||||
|
{app ? (
|
||||||
|
<>
|
||||||
|
<div className="row between">
|
||||||
|
<AppHeader app={app} size="large" />
|
||||||
|
<ActionButton app={app} style={{ marginRight: "0.5em" }} />
|
||||||
|
</div>
|
||||||
|
<div className="col" style={{ marginTop: "1em" }}>
|
||||||
|
<div className="app-details row">
|
||||||
|
<div className="title">Description</div>
|
||||||
|
<div className="value">
|
||||||
|
{(app.metadata?.description || "No description given").slice(
|
||||||
|
0,
|
||||||
|
2000
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="app-details row">
|
||||||
|
<div className="title">Publisher</div>
|
||||||
|
<div className="value underline">{app.publisher}</div>
|
||||||
|
</div>
|
||||||
|
<div className="app-details row">
|
||||||
|
<div className="title">Version</div>
|
||||||
|
<div className="value">{version}</div>
|
||||||
|
</div>
|
||||||
|
<div className="app-details row">
|
||||||
|
<div className="title">Mirrors</div>
|
||||||
|
<div className="col">
|
||||||
|
{(app.metadata?.properties?.mirrors || []).map(
|
||||||
|
(mirror, index) => (
|
||||||
|
<div key={index + mirror} className="value underline">
|
||||||
|
{mirror}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* <div className="app-details row">
|
||||||
|
<div className="title">Permissions</div>
|
||||||
|
<div className="col">
|
||||||
|
{app.permissions?.map((permission, index) => (
|
||||||
|
<div key={index + permission} className="value permission">{permission}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div> */}
|
||||||
|
<div className="app-details row">
|
||||||
|
<div className="title">Hash</div>
|
||||||
|
<div className="value" style={{ wordBreak: "break-all" }}>
|
||||||
|
{hash}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="app-screenshots row">
|
||||||
|
{(app.metadata?.properties?.screenshots || []).map(
|
||||||
|
(screenshot, index) => (
|
||||||
|
<img key={index + screenshot} src={screenshot} />
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{app.installed && (
|
||||||
|
<button type="button" onClick={goToPublish}>
|
||||||
|
Publish
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<h4>App details not found for </h4>
|
||||||
|
<h4>{params.id}</h4>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
80
kinode/packages/app_store/ui/src/pages/MyAppsPage.tsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
|
import { FaUpload } from "react-icons/fa";
|
||||||
|
|
||||||
|
import { AppInfo, MyApps } from "../types/Apps";
|
||||||
|
import useAppsStore from "../store/apps-store";
|
||||||
|
import AppEntry from "../components/AppEntry";
|
||||||
|
import SearchHeader from "../components/SearchHeader";
|
||||||
|
import { PageProps } from "../types/Page";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import { appId } from "../utils/app";
|
||||||
|
|
||||||
|
interface MyAppsPageProps extends PageProps {}
|
||||||
|
|
||||||
|
export default function MyAppsPage(props: MyAppsPageProps) { // eslint-disable-line
|
||||||
|
const { myApps, getMyApps } = useAppsStore()
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [searchQuery, setSearchQuery] = useState<string>("");
|
||||||
|
const [displayedApps, setDisplayedApps] = useState<MyApps>(myApps);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getMyApps()
|
||||||
|
.then(setDisplayedApps)
|
||||||
|
.catch((error) => console.error(error));
|
||||||
|
}, []); // eslint-disable-line
|
||||||
|
|
||||||
|
const searchMyApps = useCallback((query: string) => {
|
||||||
|
setSearchQuery(query);
|
||||||
|
const filteredApps = Object.keys(myApps).reduce((acc, key) => {
|
||||||
|
acc[key] = myApps[key].filter((app) => {
|
||||||
|
return app.package.toLowerCase().includes(query.toLowerCase())
|
||||||
|
|| app.metadata?.description?.toLowerCase().includes(query.toLowerCase())
|
||||||
|
|| app.metadata?.description?.toLowerCase().includes(query.toLowerCase());
|
||||||
|
})
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {
|
||||||
|
downloaded: [] as AppInfo[],
|
||||||
|
installed: [] as AppInfo[],
|
||||||
|
local: [] as AppInfo[],
|
||||||
|
system: [] as AppInfo[],
|
||||||
|
} as MyApps)
|
||||||
|
|
||||||
|
setDisplayedApps(filteredApps);
|
||||||
|
}, [myApps]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (searchQuery) {
|
||||||
|
searchMyApps(searchQuery);
|
||||||
|
} else {
|
||||||
|
setDisplayedApps(myApps);
|
||||||
|
}
|
||||||
|
}, [myApps]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ width: "100%", height: '100%' }}>
|
||||||
|
<SearchHeader value={searchQuery} onChange={searchMyApps} />
|
||||||
|
<div className="row between page-title">
|
||||||
|
<h4 style={{ marginBottom: "0.5em" }}>My Packages</h4>
|
||||||
|
<button className="row" onClick={() => navigate('/publish')}>
|
||||||
|
<FaUpload style={{ marginRight: "0.5em" }} />
|
||||||
|
Publish Package
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="my-apps-list">
|
||||||
|
<div className="new card col" style={{ gap: "1em" }}>
|
||||||
|
<h4>Downloaded</h4>
|
||||||
|
{(displayedApps.downloaded || []).map((app) => <AppEntry key={appId(app)} app={app} />)}
|
||||||
|
<h4>Installed</h4>
|
||||||
|
{(displayedApps.installed || []).map((app) => <AppEntry key={appId(app)} app={app} />)}
|
||||||
|
<h4>Local</h4>
|
||||||
|
{(displayedApps.local || []).map((app) => <AppEntry key={appId(app)} app={app} />)}
|
||||||
|
<h4>System</h4>
|
||||||
|
{(displayedApps.system || []).map((app) => <AppEntry key={appId(app)} app={app} />)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
305
kinode/packages/app_store/ui/src/pages/PublishPage.tsx
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
import React, { useState, useCallback, FormEvent, useEffect } from "react";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { BigNumber, utils } from "ethers";
|
||||||
|
import { useWeb3React } from "@web3-react/core";
|
||||||
|
|
||||||
|
import SearchHeader from "../components/SearchHeader";
|
||||||
|
import { PageProps } from "../types/Page";
|
||||||
|
import { setChain } from "../utils/chain";
|
||||||
|
import { SEPOLIA_OPT_HEX } from "../constants/chain";
|
||||||
|
import { hooks, metaMask } from "../utils/metamask";
|
||||||
|
import Loader from "../components/Loader";
|
||||||
|
import { toDNSWireFormat } from "../utils/dnsWire";
|
||||||
|
import useAppsStore from "../store/apps-store";
|
||||||
|
import MetadataForm from "../components/MetadataForm";
|
||||||
|
import { AppInfo } from "../types/Apps";
|
||||||
|
import Checkbox from "../components/Checkbox";
|
||||||
|
|
||||||
|
const { useIsActivating } = hooks;
|
||||||
|
|
||||||
|
interface PublishPageProps extends PageProps {}
|
||||||
|
|
||||||
|
export default function PublishPage({
|
||||||
|
provider,
|
||||||
|
packageAbi,
|
||||||
|
}: PublishPageProps) {
|
||||||
|
// get state from router
|
||||||
|
const { state } = useLocation();
|
||||||
|
const { listedApps } = useAppsStore();
|
||||||
|
// TODO: figure out how to handle provider
|
||||||
|
const { account, isActive } = useWeb3React();
|
||||||
|
const isActivating = useIsActivating();
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState("");
|
||||||
|
const [publishSuccess, setPublishSuccess] = useState<
|
||||||
|
{ packageName: string; publisherId: string } | undefined
|
||||||
|
>();
|
||||||
|
const [showMetadataForm, setShowMetadataForm] = useState<boolean>(false);
|
||||||
|
const [packageName, setPackageName] = useState<string>("");
|
||||||
|
const [publisherId, setPublisherId] = useState<string>(
|
||||||
|
window.our?.node || ""
|
||||||
|
); // BytesLike
|
||||||
|
const [metadataUrl, setMetadataUrl] = useState<string>("");
|
||||||
|
const [metadataHash, setMetadataHash] = useState<string>(""); // BytesLike
|
||||||
|
const [isUpdate, setIsUpdate] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const app: AppInfo | undefined = state?.app;
|
||||||
|
if (app) {
|
||||||
|
setPackageName(app.package);
|
||||||
|
setPublisherId(app.publisher);
|
||||||
|
setIsUpdate(true);
|
||||||
|
}
|
||||||
|
}, [state])
|
||||||
|
|
||||||
|
const connectWallet = useCallback(async () => {
|
||||||
|
await metaMask.activate().catch(() => {});
|
||||||
|
|
||||||
|
try {
|
||||||
|
setChain(SEPOLIA_OPT_HEX);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const calculateMetadataHash = useCallback(async () => {
|
||||||
|
if (!metadataUrl) {
|
||||||
|
setMetadataHash("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const metadataResponse = await fetch(metadataUrl);
|
||||||
|
const metadataText = await metadataResponse.text();
|
||||||
|
JSON.parse(metadataText); // confirm it's valid JSON
|
||||||
|
const metadataHash = utils.keccak256(utils.toUtf8Bytes(metadataText));
|
||||||
|
setMetadataHash(metadataHash);
|
||||||
|
} catch (error) {
|
||||||
|
window.alert(
|
||||||
|
"Error calculating metadata hash. Please ensure the URL is valid and the metadata is in JSON format."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [metadataUrl]);
|
||||||
|
|
||||||
|
const publishPackage = useCallback(
|
||||||
|
async (e: FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
let metadata = metadataHash;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!metadata) {
|
||||||
|
// https://pongo-uploads.s3.us-east-2.amazonaws.com/chat_metadata.json
|
||||||
|
const metadataResponse = await fetch(metadataUrl);
|
||||||
|
await metadataResponse.json(); // confirm it's valid JSON
|
||||||
|
const metadataText = await metadataResponse.text(); // hash as text
|
||||||
|
metadata = utils.keccak256(utils.toUtf8Bytes(metadataText));
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading("Please confirm the transaction in your wallet");
|
||||||
|
const publisherIdDnsWireFormat = toDNSWireFormat(publisherId);
|
||||||
|
await setChain(SEPOLIA_OPT_HEX);
|
||||||
|
|
||||||
|
// TODO: have a checkbox to show if it's an update of an existing package
|
||||||
|
|
||||||
|
const tx = await (isUpdate
|
||||||
|
? packageAbi.updateMetadata(
|
||||||
|
BigNumber.from(
|
||||||
|
utils.solidityKeccak256(
|
||||||
|
["string", "bytes"],
|
||||||
|
[packageName, publisherIdDnsWireFormat]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
metadataUrl,
|
||||||
|
metadata
|
||||||
|
)
|
||||||
|
: packageAbi.registerApp(
|
||||||
|
packageName,
|
||||||
|
publisherIdDnsWireFormat,
|
||||||
|
metadataUrl,
|
||||||
|
metadata
|
||||||
|
));
|
||||||
|
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
|
|
||||||
|
setLoading("Publishing package...");
|
||||||
|
await tx.wait();
|
||||||
|
setPublishSuccess({ packageName, publisherId });
|
||||||
|
setPackageName("");
|
||||||
|
setPublisherId(window.our?.node || publisherId);
|
||||||
|
setMetadataUrl("");
|
||||||
|
setMetadataHash("");
|
||||||
|
setIsUpdate(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
window.alert(
|
||||||
|
"Error publishing package. Please ensure the package name and publisher ID are valid, and the metadata is in JSON format."
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setLoading("");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
packageName,
|
||||||
|
isUpdate,
|
||||||
|
publisherId,
|
||||||
|
metadataUrl,
|
||||||
|
metadataHash,
|
||||||
|
packageAbi,
|
||||||
|
setPublishSuccess,
|
||||||
|
setPackageName,
|
||||||
|
setPublisherId,
|
||||||
|
setMetadataUrl,
|
||||||
|
setMetadataHash,
|
||||||
|
setIsUpdate,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const checkIfUpdate = useCallback(async () => {
|
||||||
|
if (isUpdate) return;
|
||||||
|
|
||||||
|
if (
|
||||||
|
packageName &&
|
||||||
|
publisherId &&
|
||||||
|
listedApps.find(
|
||||||
|
(app) => app.package === packageName && app.publisher === publisherId
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
setIsUpdate(true);
|
||||||
|
}
|
||||||
|
}, [listedApps, packageName, publisherId, isUpdate, setIsUpdate]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ width: "100%" }}>
|
||||||
|
<SearchHeader hideSearch onBack={showMetadataForm ? () => setShowMetadataForm(false) : undefined} />
|
||||||
|
<div className="row between page-title">
|
||||||
|
<h4>Publish Package</h4>
|
||||||
|
{Boolean(account) && (
|
||||||
|
<div style={{ textAlign: "right", lineHeight: 1.5 }}>
|
||||||
|
{" "}
|
||||||
|
Connected as{" "}
|
||||||
|
{account?.slice(0, 6) + "..." + account?.slice(account.length - 6)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<div className="col center">
|
||||||
|
<Loader msg={loading} />
|
||||||
|
</div>
|
||||||
|
) : publishSuccess ? (
|
||||||
|
<div className="col center">
|
||||||
|
<h4 style={{ marginBottom: "0.5em" }}>Package Published!</h4>
|
||||||
|
<div style={{ marginBottom: "0.5em" }}>
|
||||||
|
<strong>Package Name:</strong> {publishSuccess.packageName}
|
||||||
|
</div>
|
||||||
|
<div style={{ marginBottom: "0.5em" }}>
|
||||||
|
<strong>Publisher ID:</strong> {publishSuccess.publisherId}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className={`my-pkg-btn row`}
|
||||||
|
style={{ marginTop: "1em" }}
|
||||||
|
onClick={() => setPublishSuccess(undefined)}
|
||||||
|
>
|
||||||
|
Publish Another Package
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : showMetadataForm ? (
|
||||||
|
<MetadataForm {...{packageName, publisherId, app: state?.app}} goBack={() => setShowMetadataForm(false)} />
|
||||||
|
) : !account || !isActive ? (
|
||||||
|
<>
|
||||||
|
<h4 style={{}}>Please connect your wallet to publish a package</h4>
|
||||||
|
<button className={`connect-wallet row`} onClick={connectWallet}>
|
||||||
|
Connect Wallet
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : isActivating ? (
|
||||||
|
<Loader msg="Approve connection in your wallet" />
|
||||||
|
) : (
|
||||||
|
<form
|
||||||
|
className="new card col"
|
||||||
|
style={{ flex: 1, overflowY: "scroll" }}
|
||||||
|
onSubmit={publishPackage}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="row between"
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
padding: "0.5em",
|
||||||
|
margin: "0 0 0 -0.5em",
|
||||||
|
}}
|
||||||
|
onClick={() => setIsUpdate(!isUpdate)}
|
||||||
|
>
|
||||||
|
<Checkbox checked={isUpdate} readOnly />
|
||||||
|
<label htmlFor="update" style={{ cursor: "pointer", marginLeft: 8 }}>
|
||||||
|
Update existing package
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="col f-width">
|
||||||
|
<label htmlFor="package-name">Package Name</label>
|
||||||
|
<input
|
||||||
|
style={{ minWidth: "80%" }}
|
||||||
|
id="package-name"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
placeholder="my-package"
|
||||||
|
value={packageName}
|
||||||
|
onChange={(e) => setPackageName(e.target.value)}
|
||||||
|
onBlur={checkIfUpdate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col f-width">
|
||||||
|
<label htmlFor="publisher-id">Publisher ID</label>
|
||||||
|
<input
|
||||||
|
style={{ minWidth: "80%" }}
|
||||||
|
id="publisher-id"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
value={publisherId}
|
||||||
|
onChange={(e) => setPublisherId(e.target.value)}
|
||||||
|
onBlur={checkIfUpdate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col f-width">
|
||||||
|
<label htmlFor="metadata-url">
|
||||||
|
Metadata URL
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
style={{ minWidth: "80%" }}
|
||||||
|
id="metadata-url"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
value={metadataUrl}
|
||||||
|
onChange={(e) => setMetadataUrl(e.target.value)}
|
||||||
|
onBlur={calculateMetadataHash}
|
||||||
|
placeholder="https://github/my-org/my-repo/metadata.json"
|
||||||
|
/>
|
||||||
|
<div style={{ textAlign: "left", margin: "0.5em 0 0" }}>
|
||||||
|
Metadata is a JSON file that describes your package.
|
||||||
|
<br /> You can{" "}
|
||||||
|
<a onClick={() => setShowMetadataForm(true)} style={{ cursor: "pointer", textDecoration: "underline" }}>
|
||||||
|
fill out a template here
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col f-width">
|
||||||
|
<label htmlFor="metadata-hash">Metadata Hash</label>
|
||||||
|
<input
|
||||||
|
style={{ minWidth: "80%" }}
|
||||||
|
readOnly
|
||||||
|
id="metadata-hash"
|
||||||
|
type="text"
|
||||||
|
value={metadataHash}
|
||||||
|
onChange={(e) => setMetadataHash(e.target.value)}
|
||||||
|
placeholder="Calculated automatically from metadata URL"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button type="submit" className="primary">
|
||||||
|
Publish
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
182
kinode/packages/app_store/ui/src/pages/StorePage.tsx
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
import React, { useState, useEffect, useCallback, useMemo } from "react";
|
||||||
|
import { FaChevronLeft, FaChevronRight } from "react-icons/fa";
|
||||||
|
|
||||||
|
import { AppInfo } from "../types/Apps";
|
||||||
|
import useAppsStore from "../store/apps-store";
|
||||||
|
import AppEntry from "../components/AppEntry";
|
||||||
|
import SearchHeader from "../components/SearchHeader";
|
||||||
|
import { PageProps } from "../types/Page";
|
||||||
|
import { appId } from "../utils/app";
|
||||||
|
|
||||||
|
interface StorePageProps extends PageProps { }
|
||||||
|
|
||||||
|
export default function StorePage(props: StorePageProps) {
|
||||||
|
// eslint-disable-line
|
||||||
|
const { listedApps, getListedApps } = useAppsStore();
|
||||||
|
|
||||||
|
const [resultsSort, setResultsSort] = useState<string>("Recently published");
|
||||||
|
|
||||||
|
const [searchQuery, setSearchQuery] = useState<string>("");
|
||||||
|
const [displayedApps, setDisplayedApps] = useState<AppInfo[]>(listedApps);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
|
||||||
|
const pages = useMemo(
|
||||||
|
() =>
|
||||||
|
Array.from(
|
||||||
|
{ length: Math.ceil(displayedApps.length / 10) },
|
||||||
|
(_, index) => index + 1
|
||||||
|
),
|
||||||
|
[displayedApps]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const start = (page - 1) * 10;
|
||||||
|
const end = start + 10;
|
||||||
|
setDisplayedApps(listedApps.slice(start, end));
|
||||||
|
}, [listedApps]);
|
||||||
|
|
||||||
|
// GET on load
|
||||||
|
useEffect(() => {
|
||||||
|
getListedApps()
|
||||||
|
.then((apps) => {
|
||||||
|
setDisplayedApps(Object.values(apps));
|
||||||
|
})
|
||||||
|
.catch((error) => console.error(error));
|
||||||
|
}, []); // eslint-disable-line
|
||||||
|
|
||||||
|
// const pages = useMemo(
|
||||||
|
// () => {
|
||||||
|
// const displayedApps = query ? searchResults : latestApps;
|
||||||
|
|
||||||
|
// return Array.from(
|
||||||
|
// { length: Math.ceil((displayedApps.length - 2) / 10) },
|
||||||
|
// (_, index) => index + 1
|
||||||
|
// )
|
||||||
|
// },
|
||||||
|
// [query, searchResults, latestApps]
|
||||||
|
// );
|
||||||
|
|
||||||
|
// const featuredApps = useMemo(() => latestApps.slice(0, 2), [latestApps]);
|
||||||
|
// const displayedApps = useMemo(
|
||||||
|
// () => {
|
||||||
|
// const displayedApps = query ? searchResults : latestApps.slice(2);
|
||||||
|
// return displayedApps.slice((page - 1) * 10, page * 10)
|
||||||
|
// },
|
||||||
|
// [latestApps, searchResults, page, query]
|
||||||
|
// );
|
||||||
|
|
||||||
|
const sortApps = useCallback(async (sort: string) => {
|
||||||
|
switch (sort) {
|
||||||
|
case "Recently published":
|
||||||
|
break;
|
||||||
|
case "Most popular":
|
||||||
|
break;
|
||||||
|
case "Best rating":
|
||||||
|
break;
|
||||||
|
case "Recently updated":
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// const viewDetails = useCallback(
|
||||||
|
// (app: AppInfo) => () => {
|
||||||
|
// navigate(`/app-details/${appId(app)}`);
|
||||||
|
// },
|
||||||
|
// [navigate]
|
||||||
|
// );
|
||||||
|
|
||||||
|
const searchApps = useCallback(
|
||||||
|
(query: string) => {
|
||||||
|
setSearchQuery(query);
|
||||||
|
const filteredApps = listedApps.filter(
|
||||||
|
(app) => {
|
||||||
|
return (
|
||||||
|
app.package.toLowerCase().includes(query.toLowerCase()) ||
|
||||||
|
app.metadata?.description
|
||||||
|
?.toLowerCase()
|
||||||
|
.includes(query.toLowerCase()) ||
|
||||||
|
app.metadata?.description
|
||||||
|
?.toLowerCase()
|
||||||
|
.includes(query.toLowerCase())
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[listedApps]
|
||||||
|
);
|
||||||
|
setDisplayedApps(filteredApps);
|
||||||
|
},
|
||||||
|
[listedApps]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ width: "100%" }}>
|
||||||
|
{/* <div style={{ position: "absolute", top: 4, left: 8 }}>
|
||||||
|
ID: <strong>{window.our?.node}</strong>
|
||||||
|
</div> */}
|
||||||
|
<SearchHeader value={searchQuery} onChange={searchApps} />
|
||||||
|
{/* <h3 style={{ marginBottom: "0.5em" }}>Featured</h3>
|
||||||
|
<div className="featured row">
|
||||||
|
{featuredApps.map((app, i) => (
|
||||||
|
<div
|
||||||
|
key={app.name + app.metadata_hash}
|
||||||
|
className="card col"
|
||||||
|
style={{
|
||||||
|
marginLeft: i === 1 ? "1em" : 0,
|
||||||
|
flex: 1,
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={viewDetails(app)}
|
||||||
|
>
|
||||||
|
<AppHeader app={app} />
|
||||||
|
<div style={{ marginTop: "0.25em" }}>
|
||||||
|
{app.metadata?.description || "No description provided."}
|
||||||
|
</div>
|
||||||
|
<ActionButton style={{ marginTop: "0.5em" }} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div> */}
|
||||||
|
<div className="row between page-title">
|
||||||
|
<h4>New</h4>
|
||||||
|
|
||||||
|
<select
|
||||||
|
value={resultsSort}
|
||||||
|
onChange={(e) => {
|
||||||
|
setResultsSort(e.target.value);
|
||||||
|
sortApps(e.target.value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option>Recently published</option>
|
||||||
|
<option>Most popular</option>
|
||||||
|
<option>Best rating</option>
|
||||||
|
<option>Recently updated</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="new card col" style={{ flex: 1, overflowY: "auto", gap: "1em" }}>
|
||||||
|
{displayedApps.map((app) => (
|
||||||
|
<AppEntry
|
||||||
|
key={appId(app) + (app.state?.our_version || "")}
|
||||||
|
app={app}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{pages.length > 1 && (
|
||||||
|
<div className="row" style={{ alignSelf: "center" }}>
|
||||||
|
{page !== pages[0] && (
|
||||||
|
<FaChevronLeft onClick={() => setPage(page - 1)} />
|
||||||
|
)}
|
||||||
|
{pages.map((p) => (
|
||||||
|
<div
|
||||||
|
key={`page-${p}`}
|
||||||
|
className={`page-selector ${p === page ? "selected" : ""}`}
|
||||||
|
onClick={() => setPage(p)}
|
||||||
|
>
|
||||||
|
{p}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{page !== pages[pages.length - 1] && (
|
||||||
|
<FaChevronRight onClick={() => setPage(page + 1)} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
204
kinode/packages/app_store/ui/src/store/apps-store.ts
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
import { create } from 'zustand'
|
||||||
|
import { persist, createJSONStorage } from 'zustand/middleware'
|
||||||
|
import { MyApps, AppInfo, PackageManifest } from '../types/Apps'
|
||||||
|
import { HTTP_STATUS } from '../constants/http';
|
||||||
|
import { appId, getAppType } from '../utils/app';
|
||||||
|
|
||||||
|
const BASE_URL = (import.meta as any).env.BASE_URL; // eslint-disable-line
|
||||||
|
|
||||||
|
const isApp = (a1: AppInfo, a2: AppInfo) => a1.package === a2.package && a1.publisher === a2.publisher
|
||||||
|
|
||||||
|
export interface AppsStore {
|
||||||
|
myApps: MyApps
|
||||||
|
listedApps: AppInfo[]
|
||||||
|
searchResults: AppInfo[]
|
||||||
|
query: string
|
||||||
|
|
||||||
|
getMyApps: () => Promise<MyApps>
|
||||||
|
getListedApps: () => Promise<AppInfo[]>
|
||||||
|
getMyApp: (app: AppInfo) => Promise<AppInfo>
|
||||||
|
installApp: (app: AppInfo) => Promise<void>
|
||||||
|
updateApp: (app: AppInfo) => Promise<void>
|
||||||
|
uninstallApp: (app: AppInfo) => Promise<void>
|
||||||
|
getListedApp: (packageName: string) => Promise<AppInfo>
|
||||||
|
downloadApp: (app: AppInfo, download_from: string) => Promise<void>
|
||||||
|
getCaps: (app: AppInfo) => Promise<PackageManifest>
|
||||||
|
approveCaps: (app: AppInfo) => Promise<void>
|
||||||
|
setMirroring: (info: AppInfo, mirroring: boolean) => Promise<void>
|
||||||
|
setAutoUpdate: (app: AppInfo, autoUpdate: boolean) => Promise<void>
|
||||||
|
|
||||||
|
// searchApps: (query: string, onlyMyApps?: boolean) => Promise<AppInfo[]>
|
||||||
|
|
||||||
|
get: () => AppsStore
|
||||||
|
set: (partial: AppsStore | Partial<AppsStore>) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const useAppsStore = create<AppsStore>()(
|
||||||
|
persist(
|
||||||
|
(set, get) => ({
|
||||||
|
myApps: {
|
||||||
|
downloaded: [] as AppInfo[],
|
||||||
|
installed: [] as AppInfo[],
|
||||||
|
local: [] as AppInfo[],
|
||||||
|
system: [] as AppInfo[],
|
||||||
|
},
|
||||||
|
listedApps: [] as AppInfo[],
|
||||||
|
searchResults: [] as AppInfo[],
|
||||||
|
query: '',
|
||||||
|
getMyApps: async () => {
|
||||||
|
const res = await fetch(`${BASE_URL}/apps`)
|
||||||
|
const apps = await res.json() as AppInfo[]
|
||||||
|
|
||||||
|
const myApps = apps.reduce((acc, app) => {
|
||||||
|
const appType = getAppType(app)
|
||||||
|
|
||||||
|
acc[appType].push(app)
|
||||||
|
return acc
|
||||||
|
}, {
|
||||||
|
downloaded: [],
|
||||||
|
installed: [],
|
||||||
|
local: [],
|
||||||
|
system: [],
|
||||||
|
} as MyApps)
|
||||||
|
|
||||||
|
set(() => ({ myApps }))
|
||||||
|
return myApps
|
||||||
|
},
|
||||||
|
getListedApps: async () => {
|
||||||
|
const res = await fetch(`${BASE_URL}/apps/listed`)
|
||||||
|
const apps = await res.json() as AppInfo[]
|
||||||
|
set({ listedApps: apps })
|
||||||
|
return apps
|
||||||
|
},
|
||||||
|
getMyApp: async (info: AppInfo) => {
|
||||||
|
const res = await fetch(`${BASE_URL}/apps/${appId(info)}`)
|
||||||
|
const app = await res.json() as AppInfo
|
||||||
|
const appType = getAppType(app)
|
||||||
|
const myApps = get().myApps
|
||||||
|
myApps[appType] = myApps[appType].map((a) => isApp(a, app) ? app : a)
|
||||||
|
const listedApps = [...get().listedApps].map((a) => isApp(a, app) ? app : a)
|
||||||
|
set({ myApps, listedApps })
|
||||||
|
return app
|
||||||
|
},
|
||||||
|
installApp: async (info: AppInfo) => {
|
||||||
|
const approveRes = await fetch(`${BASE_URL}/apps/${appId(info)}/caps`, {
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
if (approveRes.status !== HTTP_STATUS.OK) {
|
||||||
|
throw new Error(`Failed to approve caps for app: ${appId(info)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const installRes = await fetch(`${BASE_URL}/apps/${appId(info)}`, {
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
if (installRes.status !== HTTP_STATUS.CREATED) {
|
||||||
|
throw new Error(`Failed to install app: ${appId(info)}`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateApp: async (app: AppInfo) => {
|
||||||
|
const res = await fetch(`${BASE_URL}/apps/${appId(app)}`, {
|
||||||
|
method: 'PUT'
|
||||||
|
})
|
||||||
|
if (res.status !== HTTP_STATUS.NO_CONTENT) {
|
||||||
|
throw new Error(`Failed to update app: ${appId(app)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: get the app from the server instead of updating locally
|
||||||
|
},
|
||||||
|
uninstallApp: async (app: AppInfo) => {
|
||||||
|
if (!confirm(`Are you sure you want to remove ${appId(app)}?`)) return
|
||||||
|
|
||||||
|
const res = await fetch(`${BASE_URL}/apps/${appId(app)}`, {
|
||||||
|
method: 'DELETE'
|
||||||
|
})
|
||||||
|
if (res.status !== HTTP_STATUS.NO_CONTENT) {
|
||||||
|
throw new Error(`Failed to remove app: ${appId(app)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const myApps = { ...get().myApps }
|
||||||
|
const appType = getAppType(app)
|
||||||
|
myApps[appType] = myApps[appType].filter((a) => !isApp(a, app))
|
||||||
|
const listedApps = get().listedApps.map((a) => isApp(a, app) ? { ...a, state: undefined, installed: false } : a)
|
||||||
|
set({ myApps, listedApps })
|
||||||
|
},
|
||||||
|
getListedApp: async (packageName: string) => {
|
||||||
|
const res = await fetch(`${BASE_URL}/apps/listed/${packageName}`)
|
||||||
|
if (res.status !== HTTP_STATUS.OK) {
|
||||||
|
throw new Error(`Failed to get app: ${packageName}`)
|
||||||
|
}
|
||||||
|
const app = await res.json() as AppInfo
|
||||||
|
return app
|
||||||
|
},
|
||||||
|
downloadApp: async (info: AppInfo, download_from: string) => {
|
||||||
|
const res = await fetch(`${BASE_URL}/apps/listed/${appId(info)}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ download_from }),
|
||||||
|
})
|
||||||
|
if (res.status !== HTTP_STATUS.CREATED) {
|
||||||
|
throw new Error(`Failed to get app: ${appId(info)}`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getCaps: async (info: AppInfo) => {
|
||||||
|
const res = await fetch(`${BASE_URL}/apps/${appId(info)}/caps`)
|
||||||
|
if (res.status !== HTTP_STATUS.OK) {
|
||||||
|
throw new Error(`Failed to get app: ${appId(info)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const caps = await res.json() as PackageManifest[]
|
||||||
|
return caps[0]
|
||||||
|
},
|
||||||
|
approveCaps: async (info: AppInfo) => {
|
||||||
|
const res = await fetch(`${BASE_URL}/apps/${appId(info)}/caps`, {
|
||||||
|
method: 'POST'
|
||||||
|
})
|
||||||
|
if (res.status !== HTTP_STATUS.OK) {
|
||||||
|
throw new Error(`Failed to get app: ${appId(info)}`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setMirroring: async (info: AppInfo, mirroring: boolean) => {
|
||||||
|
const res = await fetch(`${BASE_URL}/apps/${appId(info)}/mirror`, {
|
||||||
|
method: mirroring ? 'PUT' : 'DELETE',
|
||||||
|
})
|
||||||
|
if (res.status !== HTTP_STATUS.OK) {
|
||||||
|
throw new Error(`Failed to start mirror: ${appId(info)}`)
|
||||||
|
}
|
||||||
|
get().getMyApp(info)
|
||||||
|
},
|
||||||
|
setAutoUpdate: async (info: AppInfo, autoUpdate: boolean) => {
|
||||||
|
const res = await fetch(`${BASE_URL}/apps/${appId(info)}/auto-update`, {
|
||||||
|
method: autoUpdate ? 'PUT' : 'DELETE',
|
||||||
|
})
|
||||||
|
if (res.status !== HTTP_STATUS.OK) {
|
||||||
|
throw new Error(`Failed to change auto update: ${appId(info)}`)
|
||||||
|
}
|
||||||
|
get().getMyApp(info)
|
||||||
|
},
|
||||||
|
|
||||||
|
// searchApps: async (query: string, onlyMyApps = true) => {
|
||||||
|
// if (onlyMyApps) {
|
||||||
|
// const searchResults = get().myApps.filter((app) =>
|
||||||
|
// app.name.toLowerCase().includes(query.toLowerCase())
|
||||||
|
// || app.publisher.toLowerCase().includes(query.toLowerCase())
|
||||||
|
// || app.metadata?.name?.toLowerCase()?.includes(query.toLowerCase())
|
||||||
|
// )
|
||||||
|
// set(() => ({ searchResults }))
|
||||||
|
// return searchResults
|
||||||
|
// } else {
|
||||||
|
// const res = await fetch(`${BASE_URL}/apps/search/${encodeURIComponent(query)}`)
|
||||||
|
// const searchResults = await res.json() as AppInfo[]
|
||||||
|
// set(() => ({ searchResults }))
|
||||||
|
// return searchResults
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
|
||||||
|
get,
|
||||||
|
set,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: 'app_store', // unique name
|
||||||
|
storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
export default useAppsStore
|
101
kinode/packages/app_store/ui/src/types/Apps.ts
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
export interface MyApps {
|
||||||
|
downloaded: AppInfo[]
|
||||||
|
installed: AppInfo[]
|
||||||
|
local: AppInfo[]
|
||||||
|
system: AppInfo[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppListing {
|
||||||
|
owner?: string
|
||||||
|
package: string
|
||||||
|
publisher: string
|
||||||
|
metadata_hash: string
|
||||||
|
metadata?: OnchainPackageMetadata
|
||||||
|
installed: boolean
|
||||||
|
state?: PackageState
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Erc721Properties {
|
||||||
|
package_name: string;
|
||||||
|
publisher: string;
|
||||||
|
current_version: string;
|
||||||
|
mirrors: string[];
|
||||||
|
code_hashes: Record<string, string>;
|
||||||
|
license?: string;
|
||||||
|
screenshots?: string[];
|
||||||
|
wit_version?: [number, number, number];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OnchainPackageMetadata {
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
image?: string;
|
||||||
|
external_url?: string;
|
||||||
|
animation_url?: string;
|
||||||
|
properties: Erc721Properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PackageState {
|
||||||
|
mirrored_from: string;
|
||||||
|
our_version: string;
|
||||||
|
installed: boolean;
|
||||||
|
verified: boolean;
|
||||||
|
caps_approved: boolean;
|
||||||
|
manifest_hash?: string;
|
||||||
|
mirroring: boolean;
|
||||||
|
auto_update: boolean;
|
||||||
|
// source_zip?: Uint8Array, // bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppInfo extends AppListing {
|
||||||
|
permissions?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PackageManifest {
|
||||||
|
process_name: string
|
||||||
|
process_wasm_path: string
|
||||||
|
on_exit: string
|
||||||
|
request_networking: boolean
|
||||||
|
request_capabilities: string[]
|
||||||
|
grant_capabilities: string[]
|
||||||
|
public: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"installed": false,
|
||||||
|
"metadata": null,
|
||||||
|
"metadata_hash": "0xf244e4e227494c6a0716597f0c405284eb53f7916427d48ceb03a24ed5b52b5d",
|
||||||
|
"owner": "0x7Bf904E36715B650Fb1F99113cb4A2B2FfE22392",
|
||||||
|
"package": "sdapi",
|
||||||
|
"publisher": "mothu-et-doria.os",
|
||||||
|
"state": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"installed": false,
|
||||||
|
|
||||||
|
"metadata_hash": "0xe43f616b39f2511f2c3c29c801a0993de5a74ab1fc4382ff7c68aad50f0242f3",
|
||||||
|
"owner": "0xDe12193c037F768fDC0Db0B77B7E70de723b95E7",
|
||||||
|
"package": "chat",
|
||||||
|
"publisher": "mythicengineer.os",
|
||||||
|
"state": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"installed": false,
|
||||||
|
"metadata": null,
|
||||||
|
"metadata_hash": "0x4385b4b9ddddcc25ce99d6ae1542b1362c0e7f41abf1385cd9eda4d39ced6e39",
|
||||||
|
"owner": "0x7213aa2A6581b37506C035b387b4Bf2Fb93E2f88",
|
||||||
|
"package": "chat_template",
|
||||||
|
"publisher": "odinsbadeye.os",
|
||||||
|
"state": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"installed": false,
|
||||||
|
"metadata": null,
|
||||||
|
"metadata_hash": "0x0f4c02462407d88fb43a0e24df7e36b7be4a09f2fc27bb690e5b76c8d21088ef",
|
||||||
|
"owner": "0x958946dEcCfe3546fE7F3f98eb07c100E472F09D",
|
||||||
|
"package": "kino_files",
|
||||||
|
"publisher": "gloriainexcelsisdeo.os",
|
||||||
|
"state": null
|
||||||
|
}
|
||||||
|
]
|
7
kinode/packages/app_store/ui/src/types/Page.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { ethers } from "ethers";
|
||||||
|
import { PackageStore } from "../abis/types";
|
||||||
|
|
||||||
|
export interface PageProps {
|
||||||
|
provider?: ethers.providers.Web3Provider;
|
||||||
|
packageAbi: PackageStore
|
||||||
|
}
|
24
kinode/packages/app_store/ui/src/utils/app.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { AppInfo } from "../types/Apps";
|
||||||
|
|
||||||
|
export const appId = (app: AppInfo) => `${app.package}:${app.publisher}`
|
||||||
|
|
||||||
|
export const getAppName = (app: AppInfo) => app.metadata?.name || appId(app)
|
||||||
|
|
||||||
|
export enum AppType {
|
||||||
|
Downloaded = 'downloaded',
|
||||||
|
Installed = 'installed',
|
||||||
|
Local = 'local',
|
||||||
|
System = 'system',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAppType = (app: AppInfo) => {
|
||||||
|
if (app.publisher === 'sys') {
|
||||||
|
return AppType.System
|
||||||
|
} else if (app.state?.our_version && !app.state?.capsApproved) {
|
||||||
|
return AppType.Downloaded
|
||||||
|
} else if (!app.metadata) {
|
||||||
|
return AppType.Local
|
||||||
|
} else {
|
||||||
|
return AppType.Installed
|
||||||
|
}
|
||||||
|
}
|
88
kinode/packages/app_store/ui/src/utils/chain.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { SEPOLIA_OPT_HEX, OPTIMISM_OPT_HEX } from "../constants/chain";
|
||||||
|
const CHAIN_NOT_FOUND = "4902"
|
||||||
|
|
||||||
|
export interface Chain {
|
||||||
|
chainId: string, // Replace with the correct chainId for Sepolia
|
||||||
|
chainName: string,
|
||||||
|
nativeCurrency: {
|
||||||
|
name: string,
|
||||||
|
symbol: string,
|
||||||
|
decimals: number
|
||||||
|
},
|
||||||
|
rpcUrls: string[],
|
||||||
|
blockExplorerUrls: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CHAIN_DETAILS: { [key: string]: Chain } = {
|
||||||
|
[SEPOLIA_OPT_HEX]: {
|
||||||
|
chainId: SEPOLIA_OPT_HEX,
|
||||||
|
chainName: 'Sepolia',
|
||||||
|
nativeCurrency: {
|
||||||
|
name: 'Ether',
|
||||||
|
symbol: 'ETH',
|
||||||
|
decimals: 18
|
||||||
|
},
|
||||||
|
rpcUrls: ['https://rpc.sepolia.org'],
|
||||||
|
blockExplorerUrls: ['https://sepolia.etherscan.io']
|
||||||
|
},
|
||||||
|
[OPTIMISM_OPT_HEX]: {
|
||||||
|
chainId: OPTIMISM_OPT_HEX,
|
||||||
|
chainName: 'Optimism',
|
||||||
|
nativeCurrency: {
|
||||||
|
name: 'Ether',
|
||||||
|
symbol: 'ETH',
|
||||||
|
decimals: 18
|
||||||
|
},
|
||||||
|
rpcUrls: ['https://mainnet.optimism.io'],
|
||||||
|
blockExplorerUrls: ['https://optimistic.etherscan.io']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getNetworkName = (networkId: string) => {
|
||||||
|
switch (networkId) {
|
||||||
|
case '1':
|
||||||
|
case '0x1':
|
||||||
|
return 'Ethereum'; // Ethereum Mainnet
|
||||||
|
case '10':
|
||||||
|
case 'a':
|
||||||
|
case '0xa':
|
||||||
|
return 'Optimism'; // Optimism
|
||||||
|
case '42161':
|
||||||
|
return 'Arbitrum'; // Arbitrum One
|
||||||
|
case '11155111':
|
||||||
|
case 'aa36a7':
|
||||||
|
case '0xaa36a7':
|
||||||
|
return 'Sepolia'; // Sepolia Testnet
|
||||||
|
default:
|
||||||
|
return 'Unknown';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setChain = async (chainId: string) => {
|
||||||
|
let networkId = await (window.ethereum as any)?.request({ method: 'net_version' }).catch(() => '1') // eslint-disable-line
|
||||||
|
networkId = '0x' + (typeof networkId === 'string' ? networkId.replace(/^0x/, '') : networkId.toString(16))
|
||||||
|
|
||||||
|
if (!CHAIN_DETAILS[chainId]) {
|
||||||
|
console.error(`Invalid chain ID: ${chainId}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chainId !== networkId) {
|
||||||
|
try {
|
||||||
|
await (window.ethereum as any)?.request({ // eslint-disable-line
|
||||||
|
method: "wallet_switchEthereumChain",
|
||||||
|
params: [{ chainId }]
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if (String(err).includes(CHAIN_NOT_FOUND)) {
|
||||||
|
await (window.ethereum as any)?.request({ // eslint-disable-line
|
||||||
|
method: 'wallet_addEthereumChain',
|
||||||
|
params: [CHAIN_DETAILS[chainId]]
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
window.alert(`You must enable the ${getNetworkName(chainId)} network in your wallet.`)
|
||||||
|
throw new Error(`User cancelled connection to ${chainId}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
kinode/packages/app_store/ui/src/utils/dnsWire.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
export function toDNSWireFormat(domain: string) {
|
||||||
|
const parts = domain.split('.');
|
||||||
|
const result = new Uint8Array(domain.length + parts.length);
|
||||||
|
let idx = 0;
|
||||||
|
|
||||||
|
for (const part of parts) {
|
||||||
|
const len = part.length;
|
||||||
|
result[idx] = len; // write length byte
|
||||||
|
idx++;
|
||||||
|
for (let j = 0; j < len; j++) {
|
||||||
|
result[idx] = part.charCodeAt(j); // write ASCII bytes of the label
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// result[idx] = 0; // TODO do you need null byte at the end?
|
||||||
|
|
||||||
|
return `0x${Array.from(result).map(byte => byte.toString(16).padStart(2, '0')).join('')}`;
|
||||||
|
}
|
4
kinode/packages/app_store/ui/src/utils/metamask.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { initializeConnector } from '@web3-react/core'
|
||||||
|
import { MetaMask } from '@web3-react/metamask'
|
||||||
|
|
||||||
|
export const [metaMask, hooks] = initializeConnector<MetaMask>((actions) => new MetaMask({ actions }))
|
1
kinode/packages/app_store/ui/src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
27
kinode/packages/app_store/ui/tsconfig.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
// "strict": true,
|
||||||
|
// "noUnusedLocals": true,
|
||||||
|
// "noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"../src",
|
||||||
|
],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
10
kinode/packages/app_store/ui/tsconfig.node.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
68
kinode/packages/app_store/ui/vite.config.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
/*
|
||||||
|
If you are developing a UI outside of a Kinode project,
|
||||||
|
comment out the following 2 lines:
|
||||||
|
*/
|
||||||
|
// import manifest from '../pkg/manifest.json'
|
||||||
|
// import metadata from '../pkg/metadata.json'
|
||||||
|
|
||||||
|
/*
|
||||||
|
IMPORTANT:
|
||||||
|
This must match the process name from pkg/manifest.json + pkg/metadata.json
|
||||||
|
The format is "/" + "process_name:package_name:publisher_node"
|
||||||
|
*/
|
||||||
|
const BASE_URL = `/main:app_store:sys`;
|
||||||
|
// const BASE_URL = `/${manifest[0].process_name}:${metadata.package}:${metadata.publisher}`;
|
||||||
|
|
||||||
|
// This is the proxy URL, it must match the node you are developing against
|
||||||
|
const PROXY_URL = (process.env.VITE_NODE_URL || 'http://127.0.0.1:8080').replace('localhost', '127.0.0.1');
|
||||||
|
|
||||||
|
console.log('process.env.VITE_NODE_URL', process.env.VITE_NODE_URL, PROXY_URL);
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
base: BASE_URL,
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
external: ['/our.js']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
open: true,
|
||||||
|
proxy: {
|
||||||
|
'/our': {
|
||||||
|
target: PROXY_URL,
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
[`${BASE_URL}/our.js`]: {
|
||||||
|
target: PROXY_URL,
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (path) => path.replace(BASE_URL, ''),
|
||||||
|
},
|
||||||
|
// This route will match all other HTTP requests to the backend
|
||||||
|
[`^${BASE_URL}/(?!(@vite/client|src/.*|node_modules/.*|@react-refresh|$))`]: {
|
||||||
|
target: PROXY_URL,
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
// '/example': {
|
||||||
|
// target: PROXY_URL,
|
||||||
|
// changeOrigin: true,
|
||||||
|
// rewrite: (path) => path.replace(BASE_URL, ''),
|
||||||
|
// // This is only for debugging purposes
|
||||||
|
// configure: (proxy, _options) => {
|
||||||
|
// proxy.on('error', (err, _req, _res) => {
|
||||||
|
// console.log('proxy error', err);
|
||||||
|
// });
|
||||||
|
// proxy.on('proxyReq', (proxyReq, req, _res) => {
|
||||||
|
// console.log('Sending Request to the Target:', req.method, req.url);
|
||||||
|
// });
|
||||||
|
// proxy.on('proxyRes', (proxyRes, req, _res) => {
|
||||||
|
// console.log('Received Response from the Target:', proxyRes.statusCode, req.url);
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
5404
kinode/packages/app_store/ui/yarn.lock
Normal file
Before Width: | Height: | Size: 3.5 KiB |
@ -31,7 +31,6 @@
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 2em;
|
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
background-color: var(--gray-button);
|
background-color: var(--gray-button);
|
||||||
color: var(--text-light);
|
color: var(--text-light);
|
||||||
@ -39,7 +38,6 @@
|
|||||||
background-size: cover;
|
background-size: cover;
|
||||||
height: calc(100vh - 4em);
|
height: calc(100vh - 4em);
|
||||||
width: calc(100vw - 4em);
|
width: calc(100vw - 4em);
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body,
|
body,
|
||||||
@ -203,6 +201,7 @@
|
|||||||
#home-page {
|
#home-page {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
margin: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#uq-name {
|
#uq-name {
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
#![feature(let_chains)]
|
#![feature(let_chains)]
|
||||||
use kinode_process_lib::{
|
use kinode_process_lib::{
|
||||||
await_message, call_init, http::bind_http_static_path, http::HttpServerError, println, Address,
|
await_message, call_init,
|
||||||
Message, ProcessId,
|
http::{
|
||||||
|
bind_http_path, bind_http_static_path, send_response, HttpServerError, HttpServerRequest,
|
||||||
|
StatusCode,
|
||||||
|
},
|
||||||
|
println, Address, Message, ProcessId,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -21,6 +25,14 @@ enum HomepageRequest {
|
|||||||
Remove,
|
Remove,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct HomepageApp {
|
||||||
|
package_name: String,
|
||||||
|
path: String,
|
||||||
|
label: String,
|
||||||
|
base64_icon: String,
|
||||||
|
}
|
||||||
|
|
||||||
wit_bindgen::generate!({
|
wit_bindgen::generate!({
|
||||||
path: "wit",
|
path: "wit",
|
||||||
world: "process",
|
world: "process",
|
||||||
@ -38,7 +50,7 @@ const APP_TEMPLATE: &str = r#"
|
|||||||
<h6>${label}</h6>
|
<h6>${label}</h6>
|
||||||
</a>"#;
|
</a>"#;
|
||||||
|
|
||||||
call_init!(main);
|
call_init!(init);
|
||||||
|
|
||||||
/// bind to root path on http_server (we have special dispensation to do so!)
|
/// bind to root path on http_server (we have special dispensation to do so!)
|
||||||
fn bind_index(our: &str, apps: &HashMap<ProcessId, String>) {
|
fn bind_index(our: &str, apps: &HashMap<ProcessId, String>) {
|
||||||
@ -64,9 +76,96 @@ fn bind_index(our: &str, apps: &HashMap<ProcessId, String>) {
|
|||||||
.expect("failed to bind to /");
|
.expect("failed to bind to /");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main(our: Address) {
|
// // Copied in from process_lib serve_ui. see https://github.com/kinode-dao/process_lib/blob/main/src/http.rs
|
||||||
let mut apps: HashMap<ProcessId, String> = HashMap::new();
|
// fn static_serve_dir(
|
||||||
|
// our: &Address,
|
||||||
|
// directory: &str,
|
||||||
|
// authenticated: bool,
|
||||||
|
// local_only: bool,
|
||||||
|
// paths: Vec<&str>,
|
||||||
|
// ) -> anyhow::Result<()> {
|
||||||
|
// serve_index_html(our, directory, authenticated, local_only, paths)?;
|
||||||
|
|
||||||
|
// let initial_path = format!("{}/pkg/{}", our.package_id(), directory);
|
||||||
|
// println!("initial path: {}", initial_path);
|
||||||
|
|
||||||
|
// let mut queue = VecDeque::new();
|
||||||
|
// queue.push_back(initial_path.clone());
|
||||||
|
|
||||||
|
// while let Some(path) = queue.pop_front() {
|
||||||
|
// let Ok(directory_response) = KiRequest::to(("our", "vfs", "distro", "sys"))
|
||||||
|
// .body(serde_json::to_vec(&VfsRequest {
|
||||||
|
// path,
|
||||||
|
// action: VfsAction::ReadDir,
|
||||||
|
// })?)
|
||||||
|
// .send_and_await_response(5)?
|
||||||
|
// else {
|
||||||
|
// return Err(anyhow::anyhow!(
|
||||||
|
// "serve_ui: no response for path: {}",
|
||||||
|
// initial_path
|
||||||
|
// ));
|
||||||
|
// };
|
||||||
|
|
||||||
|
// let directory_body = serde_json::from_slice::<VfsResponse>(directory_response.body())?;
|
||||||
|
|
||||||
|
// // Determine if it's a file or a directory and handle appropriately
|
||||||
|
// match directory_body {
|
||||||
|
// VfsResponse::ReadDir(directory_info) => {
|
||||||
|
// for entry in directory_info {
|
||||||
|
// match entry.file_type {
|
||||||
|
// // If it's a file, serve it statically
|
||||||
|
// FileType::File => {
|
||||||
|
// KiRequest::to(("our", "vfs", "distro", "sys"))
|
||||||
|
// .body(serde_json::to_vec(&VfsRequest {
|
||||||
|
// path: entry.path.clone(),
|
||||||
|
// action: VfsAction::Read,
|
||||||
|
// })?)
|
||||||
|
// .send_and_await_response(5)??;
|
||||||
|
|
||||||
|
// let Some(blob) = get_blob() else {
|
||||||
|
// return Err(anyhow::anyhow!(
|
||||||
|
// "serve_ui: no blob for {}",
|
||||||
|
// entry.path
|
||||||
|
// ));
|
||||||
|
// };
|
||||||
|
|
||||||
|
// let content_type = get_mime_type(&entry.path);
|
||||||
|
|
||||||
|
// println!("binding {}", entry.path.replace(&initial_path, ""));
|
||||||
|
|
||||||
|
// bind_http_static_path(
|
||||||
|
// entry.path.replace(&initial_path, ""),
|
||||||
|
// authenticated, // Must be authenticated
|
||||||
|
// local_only, // Is not local-only
|
||||||
|
// Some(content_type),
|
||||||
|
// blob.bytes,
|
||||||
|
// )?;
|
||||||
|
// }
|
||||||
|
// FileType::Directory => {
|
||||||
|
// // Push the directory onto the queue
|
||||||
|
// queue.push_back(entry.path);
|
||||||
|
// }
|
||||||
|
// _ => {}
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// _ => {
|
||||||
|
// return Err(anyhow::anyhow!(
|
||||||
|
// "serve_ui: unexpected response for path: {:?}",
|
||||||
|
// directory_body
|
||||||
|
// ))
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Ok(())
|
||||||
|
// }
|
||||||
|
|
||||||
|
fn init(our: Address) {
|
||||||
|
let mut apps: HashMap<ProcessId, String> = HashMap::new();
|
||||||
|
let mut app_data: HashMap<ProcessId, HomepageApp> = HashMap::new();
|
||||||
|
|
||||||
|
// static_serve_dir(&our, "index.html", true, false, vec!["/"]);
|
||||||
bind_index(&our.node, &apps);
|
bind_index(&our.node, &apps);
|
||||||
|
|
||||||
bind_http_static_path(
|
bind_http_static_path(
|
||||||
@ -89,6 +188,8 @@ fn main(our: Address) {
|
|||||||
)
|
)
|
||||||
.expect("failed to bind to /our.js");
|
.expect("failed to bind to /our.js");
|
||||||
|
|
||||||
|
bind_http_path("/apps", true, true).expect("failed to bind /apps");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let Ok(ref message) = await_message() else {
|
let Ok(ref message) = await_message() else {
|
||||||
// we never send requests, so this will never happen
|
// we never send requests, so this will never happen
|
||||||
@ -108,6 +209,15 @@ fn main(our: Address) {
|
|||||||
if let Ok(request) = serde_json::from_slice::<HomepageRequest>(message.body()) {
|
if let Ok(request) = serde_json::from_slice::<HomepageRequest>(message.body()) {
|
||||||
match request {
|
match request {
|
||||||
HomepageRequest::Add { label, icon, path } => {
|
HomepageRequest::Add { label, icon, path } => {
|
||||||
|
app_data.insert(
|
||||||
|
message.source().process.clone(),
|
||||||
|
HomepageApp {
|
||||||
|
package_name: message.source().clone().package().to_string(),
|
||||||
|
path: path.clone(),
|
||||||
|
label: label.clone(),
|
||||||
|
base64_icon: icon.clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
apps.insert(
|
apps.insert(
|
||||||
message.source().process.clone(),
|
message.source().process.clone(),
|
||||||
APP_TEMPLATE
|
APP_TEMPLATE
|
||||||
@ -137,6 +247,36 @@ fn main(our: Address) {
|
|||||||
bind_index(&our.node, &apps);
|
bind_index(&our.node, &apps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if let Ok(request) = serde_json::from_slice::<HttpServerRequest>(message.body())
|
||||||
|
{
|
||||||
|
match request {
|
||||||
|
HttpServerRequest::Http(incoming) => {
|
||||||
|
let path = incoming.bound_path(None);
|
||||||
|
println!("on path: {}", path);
|
||||||
|
if path == "/apps" {
|
||||||
|
send_response(
|
||||||
|
StatusCode::OK,
|
||||||
|
Some(HashMap::from([(
|
||||||
|
"Content-Type".to_string(),
|
||||||
|
"application/json".to_string(),
|
||||||
|
)])),
|
||||||
|
app_data
|
||||||
|
.values()
|
||||||
|
.map(|app| serde_json::to_string(app).unwrap())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n")
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
send_response(
|
||||||
|
StatusCode::OK,
|
||||||
|
Some(HashMap::new()),
|
||||||
|
"hello".as_bytes().to_vec(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 2.9 KiB |
@ -90,7 +90,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
:root {
|
/* :root {
|
||||||
--k-red: #a30101;
|
--k-red: #a30101;
|
||||||
--k-darkred: #4d0c0c;
|
--k-darkred: #4d0c0c;
|
||||||
--k-lightred: #dd0207;
|
--k-lightred: #dd0207;
|
||||||
@ -173,15 +173,6 @@
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
#signup-page {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
padding: 2em;
|
|
||||||
max-width: calc(100vw - 4em);
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
label {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
}
|
||||||
@ -291,7 +282,7 @@
|
|||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
} */
|
||||||
</style>
|
</style>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
23
kinode/src/register-ui/.gitignore
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
src/abis/types/*
|
1
kinode/src/register-ui/.nvmrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
v18.18.0
|
10
kinode/src/register-ui/.sample.env
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# local
|
||||||
|
REACT_APP_INVITE_GET=
|
||||||
|
REACT_APP_BUILD_USER_OP_POST=
|
||||||
|
REACT_APP_BROADCAST_USER_OP_POST=
|
||||||
|
|
||||||
|
REACT_APP_SEPOLIA_RPC_URL=
|
||||||
|
REACT_APP_OPTIMISM_RPC_URL=
|
||||||
|
REACT_APP_MAINNET_RPC_URL=
|
||||||
|
DANGEROUSLY_DISABLE_HOST_CHECK=true # needed for local development
|
||||||
|
|
18
kinode/src/register-ui/README.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Register
|
||||||
|
This app is compiled and put into the root directory of every Kinode node for login and registration. It handles all on-chain KNS registration flows
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
1. Run `yarn` to install dependencies
|
||||||
|
1. Run `yarn run tc` to generate ABIs
|
||||||
|
1. Start a kinode locally on port 8080 (default)
|
||||||
|
1. Run `yarn start` to serve the UI at http://localhost:3000 (proxies requests to local kinode)
|
||||||
|
|
||||||
|
If you would like to proxy requests to a kinode that is not at http://localhost:8080, change the `proxy` field in `package.json`.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
1. Run `yarn` to install dependencies
|
||||||
|
1. Run `yarn run tc` to generate ABIs
|
||||||
|
1. Run `yarn build` to generate the `./build` folder
|
||||||
|
1. Overwrite `kinode/kinode/src/register-ui/build` with `./build`
|
19
kinode/src/register-ui/add-inline-tags.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const indexPath = path.join(__dirname, 'build', 'index.html');
|
||||||
|
|
||||||
|
fs.readFile(indexPath, 'utf8', (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let modifiedHtml = data
|
||||||
|
.replace(/<script src="(.*?)"><\/script>/g, '<script src="$1" inline></script>')
|
||||||
|
.replace(/<link href="(.*?)" rel="stylesheet">/g, '<link href="$1" rel="stylesheet" inline>');
|
||||||
|
|
||||||
|
fs.writeFile(indexPath, modifiedHtml, 'utf8', (err) => {
|
||||||
|
if (err) return console.log(err);
|
||||||
|
});
|
||||||
|
});
|
@ -1,15 +1,17 @@
|
|||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"main.css": "/static/css/main.a386e724.css",
|
"main.css": "/static/css/main.6c087b1c.css",
|
||||||
"main.js": "/static/js/main.332b908d.js",
|
"main.js": "/static/js/main.be5cbd4a.js",
|
||||||
|
"static/media/OpenSans-CondBold.ttf": "/static/media/OpenSans-CondBold.6293057f8484b6c0da03.ttf",
|
||||||
|
"static/media/BarlowCondensed-Black.ttf": "/static/media/BarlowCondensed-Black.3ba02bbdeb04e17f34bf.ttf",
|
||||||
|
"static/media/Futura-Heavy.ttf": "/static/media/Futura-Heavy.af72c25a6945b0f48abb.ttf",
|
||||||
"static/media/unknown.png": "/static/media/unknown.880d04d4611a45ab1001.png",
|
"static/media/unknown.png": "/static/media/unknown.880d04d4611a45ab1001.png",
|
||||||
"static/media/background.jpg": "/static/media/background.01d2427cfc21fb685016.jpg",
|
"static/media/background.jpg": "/static/media/background.01d2427cfc21fb685016.jpg",
|
||||||
"static/media/kinode.svg": "/static/media/kinode.86d0c1a6a4a3ca3be41616b5989d6925.svg",
|
|
||||||
"index.html": "/index.html",
|
"index.html": "/index.html",
|
||||||
"static/media/logo.svg": "/static/media/logo.45dcb752ac5b825f5e3b9299d2210f0a.svg"
|
"static/media/kinode.svg": "/static/media/kinode.6b178bc9164b31d90099844a82d04497.svg"
|
||||||
},
|
},
|
||||||
"entrypoints": [
|
"entrypoints": [
|
||||||
"static/css/main.a386e724.css",
|
"static/css/main.6c087b1c.css",
|
||||||
"static/js/main.332b908d.js"
|
"static/js/main.be5cbd4a.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -1 +1 @@
|
|||||||
<!doctype html><html lang="en"><head><title>Welcome - Kinode</title><meta charset="utf-8"/><meta http-equiv="pragma" content="no-cache"/><meta http-equiv="cache-control" content="no-cache"/><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Barlow+Condensed:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet"><link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzc5IiBoZWlnaHQ9IjUxNCIgdmlld0JveD0iMCAwIDc3OSA1MTQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8c3R5bGU+CiAgICAgICAgQG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyaykgewogICAgICAgICAgICBzdmcgeyBmaWxsOiB3aGl0ZTsgfQogICAgICAgIH0KICAgICAgICBAbWVkaWEgKHByZWZlcnMtY29sb3Itc2NoZW1lOiBsaWdodCkgewogICAgICAgICAgICBzdmcgeyBmaWxsOiBibGFjazsgfQogICAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8cGF0aCBkPSJNNzUzLjA5MiA1LjkxOTMyQzc1Ni41NTcgNS4wOTk3NiA3NTUuOTYyIC0wLjAwMDEyMjA3IDc1Mi40MDEgLTAuMDAwMTIyMDdINDI2LjAwMUM0MjQuNzU1IC0wLjAwMDEyMjA3IDQyMy42MzkgMC43NzAyNyA0MjMuMTk3IDEuOTM1MzVMMjM2Ljk2OCA0OTIuNkMyMzUuNzI5IDQ5NS44NjUgMjQwLjEyMyA0OTguMjU1IDI0Mi4xOTEgNDk1LjQ0MUw1NjkuMzU3IDUwLjExMzJDNTY5Ljc3OCA0OS41MzkyIDU3MC4zOTEgNDkuMTMzOSA1NzEuMDg0IDQ4Ljk3TDc1My4wOTIgNS45MTkzMloiLz4KICAgIDxwYXRoIGQ9Ik0xMS45NjY1IDQwLjIyODhDOS4xMDk0OSAzOC43NzcgMTAuMjEzNSAzNC40NTgzIDEzLjQxNjcgMzQuNTU1N0w0MDQuMjczIDQ2LjQzNjdDNDA2LjMzNCA0Ni40OTkzIDQwNy43MTkgNDguNTc0OSA0MDYuOTg2IDUwLjUwMjNMMzQ3LjQzOCAyMDYuOTgxQzM0Ni44MDQgMjA4LjY0NyAzNDQuODY1IDIwOS4zOTYgMzQzLjI3NSAyMDguNTg4TDExLjk2NjUgNDAuMjI4OFoiLz4KPC9zdmc+Cg=="><meta httpequiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1.00001,viewport-fit=cover"/><script defer="defer" src="/static/js/main.332b908d.js"></script><link href="/static/css/main.a386e724.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
<!doctype html><html lang="en"><head><title>Welcome - Kinode</title><meta charset="utf-8"/><meta http-equiv="pragma" content="no-cache"/><meta http-equiv="cache-control" content="no-cache"/><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Barlow+Condensed:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet"><link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzc5IiBoZWlnaHQ9IjUxNCIgdmlld0JveD0iMCAwIDc3OSA1MTQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8c3R5bGU+CiAgICAgICAgQG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyaykgewogICAgICAgICAgICBzdmcgeyBmaWxsOiB3aGl0ZTsgfQogICAgICAgIH0KICAgICAgICBAbWVkaWEgKHByZWZlcnMtY29sb3Itc2NoZW1lOiBsaWdodCkgewogICAgICAgICAgICBzdmcgeyBmaWxsOiBibGFjazsgfQogICAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8cGF0aCBkPSJNNzUzLjA5MiA1LjkxOTMyQzc1Ni41NTcgNS4wOTk3NiA3NTUuOTYyIC0wLjAwMDEyMjA3IDc1Mi40MDEgLTAuMDAwMTIyMDdINDI2LjAwMUM0MjQuNzU1IC0wLjAwMDEyMjA3IDQyMy42MzkgMC43NzAyNyA0MjMuMTk3IDEuOTM1MzVMMjM2Ljk2OCA0OTIuNkMyMzUuNzI5IDQ5NS44NjUgMjQwLjEyMyA0OTguMjU1IDI0Mi4xOTEgNDk1LjQ0MUw1NjkuMzU3IDUwLjExMzJDNTY5Ljc3OCA0OS41MzkyIDU3MC4zOTEgNDkuMTMzOSA1NzEuMDg0IDQ4Ljk3TDc1My4wOTIgNS45MTkzMloiLz4KICAgIDxwYXRoIGQ9Ik0xMS45NjY1IDQwLjIyODhDOS4xMDk0OSAzOC43NzcgMTAuMjEzNSAzNC40NTgzIDEzLjQxNjcgMzQuNTU1N0w0MDQuMjczIDQ2LjQzNjdDNDA2LjMzNCA0Ni40OTkzIDQwNy43MTkgNDguNTc0OSA0MDYuOTg2IDUwLjUwMjNMMzQ3LjQzOCAyMDYuOTgxQzM0Ni44MDQgMjA4LjY0NyAzNDQuODY1IDIwOS4zOTYgMzQzLjI3NSAyMDguNTg4TDExLjk2NjUgNDAuMjI4OFoiLz4KPC9zdmc+Cg=="><meta httpequiv="X-UA-Compatible" content="IE=edge"/><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1.00001,viewport-fit=cover"/><script defer="defer" src="/static/js/main.be5cbd4a.js"></script><link href="/static/css/main.6c087b1c.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
2
kinode/src/register-ui/build/static/js/main.be5cbd4a.js
Normal file
@ -1,3 +1,9 @@
|
|||||||
|
/*!
|
||||||
|
Copyright (c) 2018 Jed Watson.
|
||||||
|
Licensed under the MIT License (MIT), see
|
||||||
|
http://jedwatson.github.io/classnames
|
||||||
|
*/
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Copyright (c) 2015 Jed Watson.
|
Copyright (c) 2015 Jed Watson.
|
||||||
Based on code that is Copyright 2013-2015, Facebook, Inc.
|
Based on code that is Copyright 2013-2015, Facebook, Inc.
|
||||||
@ -25,8 +31,6 @@
|
|||||||
|
|
||||||
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
|
||||||
|
|
||||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @license React
|
* @license React
|
||||||
* react-dom.production.min.js
|
* react-dom.production.min.js
|
||||||
@ -88,7 +92,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @remix-run/router v1.10.0
|
* @remix-run/router v1.15.3
|
||||||
*
|
*
|
||||||
* Copyright (c) Remix Software Inc.
|
* Copyright (c) Remix Software Inc.
|
||||||
*
|
*
|
||||||
@ -99,7 +103,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* React Router DOM v6.17.0
|
* React Router DOM v6.22.3
|
||||||
*
|
*
|
||||||
* Copyright (c) Remix Software Inc.
|
* Copyright (c) Remix Software Inc.
|
||||||
*
|
*
|
||||||
@ -110,7 +114,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* React Router v6.17.0
|
* React Router v6.22.3
|
||||||
*
|
*
|
||||||
* Copyright (c) Remix Software Inc.
|
* Copyright (c) Remix Software Inc.
|
||||||
*
|
*
|
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="779" height="514" viewBox="0 0 779 514" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M753.092 5.91932C756.557 5.09976 755.962 -0.00012207 752.401 -0.00012207H426.001C424.755 -0.00012207 423.639 0.77027 423.197 1.93535L236.968 492.6C235.729 495.865 240.123 498.255 242.191 495.441L569.357 50.1132C569.778 49.5392 570.391 49.1339 571.084 48.97L753.092 5.91932Z" fill="#FFF5D9"/>
|
||||||
|
<path d="M11.9665 40.2288C9.10949 38.777 10.2135 34.4583 13.4167 34.5557L404.273 46.4367C406.334 46.4993 407.719 48.5749 406.986 50.5023L347.438 206.981C346.804 208.647 344.865 209.396 343.275 208.588L11.9665 40.2288Z" fill="#FFF5D9"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 644 B |
6
kinode/src/register-ui/build_all.sh
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
source ~/.nvm/nvm.sh
|
||||||
|
nvm use
|
||||||
|
npm install
|
||||||
|
npm run tc
|
||||||
|
npm run build
|
22995
kinode/src/register-ui/package-lock.json
generated
Normal file
73
kinode/src/register-ui/package.json
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"name": "register",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"proxy": "http://localhost:8080",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
|
"@ethersproject/hash": "^5.7.0",
|
||||||
|
"@typechain/ethers-v5": "^11.1.1",
|
||||||
|
"@types/node": "^16.18.50",
|
||||||
|
"@types/react": "^18.2.21",
|
||||||
|
"@types/react-dom": "^18.2.7",
|
||||||
|
"@web3-react/coinbase-wallet": "^8.2.3",
|
||||||
|
"@web3-react/core": "^8.2.2",
|
||||||
|
"@web3-react/gnosis-safe": "^8.2.4",
|
||||||
|
"@web3-react/injected-connector": "^6.0.7",
|
||||||
|
"@web3-react/metamask": "^8.2.3",
|
||||||
|
"@web3-react/network": "^8.2.3",
|
||||||
|
"@web3-react/types": "^8.2.2",
|
||||||
|
"@web3-react/walletconnect": "^8.2.3",
|
||||||
|
"@web3-react/walletconnect-connector": "^6.2.13",
|
||||||
|
"@web3-react/walletconnect-v2": "^8.5.1",
|
||||||
|
"buffer": "^6.0.3",
|
||||||
|
"classnames": "^2.5.1",
|
||||||
|
"eslint-config-react-app": "^7.0.1",
|
||||||
|
"eth-ens-namehash": "^2.0.8",
|
||||||
|
"ethers": "^5.7.2",
|
||||||
|
"idna-uts46-hx": "^2.3.1",
|
||||||
|
"is-valid-domain": "^0.1.6",
|
||||||
|
"jazzicon": "^1.5.0",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-icons": "^5.0.1",
|
||||||
|
"react-modal": "^3.16.1",
|
||||||
|
"react-router-dom": "^6.16.0",
|
||||||
|
"react-scripts": "5.0.1",
|
||||||
|
"tailwindcss": "^3.4.1",
|
||||||
|
"typechain": "^8.3.1",
|
||||||
|
"typescript": "^4.9.5"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "npm run tc && GENERATE_SOURCEMAP=false react-scripts build",
|
||||||
|
"build:copy": "npm run build",
|
||||||
|
"inline": "node ./add-inline-tags.js && cd build && inline-source ./index.html > ./inline-index.html && cd ..",
|
||||||
|
"build-inline": "npm run build && npm run inline",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject",
|
||||||
|
"tc": "typechain --target ethers-v5 --out-dir src/abis/types/ \"./src/abis/**/*.json\""
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react-modal": "^3.16.2",
|
||||||
|
"inline-source-cli": "^2.0.0"
|
||||||
|
}
|
||||||
|
}
|
23
kinode/src/register-ui/public/index.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Welcome - Kinode</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="pragma" content="no-cache" />
|
||||||
|
<meta http-equiv="cache-control" content="no-cache" />
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Barlow+Condensed:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet">
|
||||||
|
<link rel="icon"
|
||||||
|
href="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzc5IiBoZWlnaHQ9IjUxNCIgdmlld0JveD0iMCAwIDc3OSA1MTQiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgICA8c3R5bGU+CiAgICAgICAgQG1lZGlhIChwcmVmZXJzLWNvbG9yLXNjaGVtZTogZGFyaykgewogICAgICAgICAgICBzdmcgeyBmaWxsOiB3aGl0ZTsgfQogICAgICAgIH0KICAgICAgICBAbWVkaWEgKHByZWZlcnMtY29sb3Itc2NoZW1lOiBsaWdodCkgewogICAgICAgICAgICBzdmcgeyBmaWxsOiBibGFjazsgfQogICAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8cGF0aCBkPSJNNzUzLjA5MiA1LjkxOTMyQzc1Ni41NTcgNS4wOTk3NiA3NTUuOTYyIC0wLjAwMDEyMjA3IDc1Mi40MDEgLTAuMDAwMTIyMDdINDI2LjAwMUM0MjQuNzU1IC0wLjAwMDEyMjA3IDQyMy42MzkgMC43NzAyNyA0MjMuMTk3IDEuOTM1MzVMMjM2Ljk2OCA0OTIuNkMyMzUuNzI5IDQ5NS44NjUgMjQwLjEyMyA0OTguMjU1IDI0Mi4xOTEgNDk1LjQ0MUw1NjkuMzU3IDUwLjExMzJDNTY5Ljc3OCA0OS41MzkyIDU3MC4zOTEgNDkuMTMzOSA1NzEuMDg0IDQ4Ljk3TDc1My4wOTIgNS45MTkzMloiLz4KICAgIDxwYXRoIGQ9Ik0xMS45NjY1IDQwLjIyODhDOS4xMDk0OSAzOC43NzcgMTAuMjEzNSAzNC40NTgzIDEzLjQxNjcgMzQuNTU1N0w0MDQuMjczIDQ2LjQzNjdDNDA2LjMzNCA0Ni40OTkzIDQwNy43MTkgNDguNTc0OSA0MDYuOTg2IDUwLjUwMjNMMzQ3LjQzOCAyMDYuOTgxQzM0Ni44MDQgMjA4LjY0NyAzNDQuODY1IDIwOS4zOTYgMzQzLjI3NSAyMDguNTg4TDExLjk2NjUgNDAuMjI4OFoiLz4KPC9zdmc+Cg==">
|
||||||
|
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1.00001, viewport-fit=cover"
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
25
kinode/src/register-ui/public/manifest.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"short_name": "React App",
|
||||||
|
"name": "Create React App Sample",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
3
kinode/src/register-ui/public/robots.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
252
kinode/src/register-ui/src/App.tsx
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
import { useState, useEffect, useMemo } from "react";
|
||||||
|
import { Navigate, BrowserRouter as Router, Route, Routes, useParams } from 'react-router-dom';
|
||||||
|
import { hooks } from "./connectors/metamask";
|
||||||
|
import {
|
||||||
|
KNS_REGISTRY_ADDRESSES,
|
||||||
|
DOT_OS_ADDRESSES,
|
||||||
|
ENS_REGISTRY_ADDRESSES,
|
||||||
|
NAMEWRAPPER_ADDRESSES,
|
||||||
|
KNS_ENS_ENTRY_ADDRESSES,
|
||||||
|
KNS_ENS_EXIT_ADDRESSES,
|
||||||
|
} from "./constants/addresses";
|
||||||
|
import { ChainId } from "./constants/chainId";
|
||||||
|
import {
|
||||||
|
KNSRegistryResolver,
|
||||||
|
KNSRegistryResolver__factory,
|
||||||
|
DotOsRegistrar,
|
||||||
|
DotOsRegistrar__factory,
|
||||||
|
KNSEnsEntry,
|
||||||
|
KNSEnsEntry__factory,
|
||||||
|
KNSEnsExit,
|
||||||
|
KNSEnsExit__factory,
|
||||||
|
NameWrapper,
|
||||||
|
NameWrapper__factory,
|
||||||
|
ENSRegistry,
|
||||||
|
ENSRegistry__factory
|
||||||
|
} from "./abis/types";
|
||||||
|
import { ethers } from "ethers";
|
||||||
|
import ConnectWallet from "./components/ConnectWallet";
|
||||||
|
import RegisterEthName from "./pages/RegisterEthName";
|
||||||
|
import RegisterOsName from "./pages/RegisterKnsName";
|
||||||
|
import ClaimOsInvite from "./pages/ClaimKnsInvite";
|
||||||
|
import SetPassword from "./pages/SetPassword";
|
||||||
|
import Login from './pages/Login'
|
||||||
|
import Reset from './pages/ResetKnsName'
|
||||||
|
import KinodeHome from "./pages/KinodeHome"
|
||||||
|
import ResetNode from "./pages/ResetNode";
|
||||||
|
import ImportKeyfile from "./pages/ImportKeyfile";
|
||||||
|
import { UnencryptedIdentity } from "./lib/types";
|
||||||
|
|
||||||
|
const {
|
||||||
|
useProvider,
|
||||||
|
} = hooks;
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const provider = useProvider();
|
||||||
|
const params = useParams()
|
||||||
|
|
||||||
|
const [pw, setPw] = useState<string>('');
|
||||||
|
const [key, setKey] = useState<string>('');
|
||||||
|
const [keyFileName, setKeyFileName] = useState<string>('');
|
||||||
|
const [reset, setReset] = useState<boolean>(false);
|
||||||
|
const [direct, setDirect] = useState<boolean>(false);
|
||||||
|
const [knsName, setOsName] = useState<string>('');
|
||||||
|
const [appSizeOnLoad, setAppSizeOnLoad] = useState<number>(0);
|
||||||
|
const [networkingKey, setNetworkingKey] = useState<string>('');
|
||||||
|
const [ipAddress, setIpAddress] = useState<number>(0);
|
||||||
|
const [port, setPort] = useState<number>(0);
|
||||||
|
const [routers, setRouters] = useState<string[]>([]);
|
||||||
|
const [nodeChainId, setNodeChainId] = useState('')
|
||||||
|
|
||||||
|
const [navigateToLogin, setNavigateToLogin] = useState<boolean>(false)
|
||||||
|
const [initialVisit, setInitialVisit] = useState<boolean>(!params?.initial)
|
||||||
|
|
||||||
|
const [connectOpen, setConnectOpen] = useState<boolean>(false);
|
||||||
|
const openConnect = () => setConnectOpen(true)
|
||||||
|
const closeConnect = () => setConnectOpen(false)
|
||||||
|
|
||||||
|
const rpcUrl = useMemo(() => provider?.network?.chainId === ChainId.SEPOLIA ? process.env.REACT_APP_SEPOLIA_RPC_URL : process.env.REACT_APP_OPTIMISM_RPC_URL, [provider])
|
||||||
|
|
||||||
|
const [dotOs, setDotOs] = useState<DotOsRegistrar>(
|
||||||
|
DotOsRegistrar__factory.connect(
|
||||||
|
provider?.network?.chainId === ChainId.SEPOLIA ? DOT_OS_ADDRESSES[ChainId.SEPOLIA] : DOT_OS_ADDRESSES[ChainId.OPTIMISM],
|
||||||
|
new ethers.providers.JsonRpcProvider(rpcUrl))
|
||||||
|
);
|
||||||
|
|
||||||
|
const [kns, setKns] = useState<KNSRegistryResolver>(
|
||||||
|
KNSRegistryResolver__factory.connect(
|
||||||
|
provider?.network?.chainId === ChainId.SEPOLIA ? KNS_REGISTRY_ADDRESSES[ChainId.SEPOLIA] : KNS_REGISTRY_ADDRESSES[ChainId.OPTIMISM],
|
||||||
|
new ethers.providers.JsonRpcProvider(rpcUrl))
|
||||||
|
);
|
||||||
|
|
||||||
|
const [knsEnsEntry, setKnsEnsEntry] = useState<KNSEnsEntry>(
|
||||||
|
KNSEnsEntry__factory.connect(
|
||||||
|
provider?.network?.chainId === ChainId.SEPOLIA ? KNS_ENS_ENTRY_ADDRESSES[ChainId.SEPOLIA] : KNS_ENS_ENTRY_ADDRESSES[ChainId.MAINNET],
|
||||||
|
// set rpc url based on chain id
|
||||||
|
new ethers.providers.JsonRpcProvider(provider?.network?.chainId === ChainId.SEPOLIA ? process.env.REACT_APP_SEPOLIA_RPC_URL : process.env.REACT_APP_MAINNET_RPC_URL))
|
||||||
|
);
|
||||||
|
|
||||||
|
const [knsEnsExit, setKnsEnsExit] = useState<KNSEnsExit>(
|
||||||
|
KNSEnsExit__factory.connect(
|
||||||
|
provider?.network?.chainId === ChainId.SEPOLIA ? KNS_ENS_EXIT_ADDRESSES[ChainId.SEPOLIA] : KNS_ENS_EXIT_ADDRESSES[ChainId.OPTIMISM],
|
||||||
|
new ethers.providers.JsonRpcProvider(rpcUrl))
|
||||||
|
);
|
||||||
|
|
||||||
|
const [nameWrapper, setNameWrapper] = useState<NameWrapper>(
|
||||||
|
NameWrapper__factory.connect(
|
||||||
|
provider?.network?.chainId === ChainId.SEPOLIA ? NAMEWRAPPER_ADDRESSES[ChainId.SEPOLIA] : NAMEWRAPPER_ADDRESSES[ChainId.MAINNET],
|
||||||
|
new ethers.providers.JsonRpcProvider(rpcUrl))
|
||||||
|
);
|
||||||
|
|
||||||
|
const [ensRegistry, setEnsRegistry] = useState<ENSRegistry>(
|
||||||
|
ENSRegistry__factory.connect(
|
||||||
|
provider?.network?.chainId === ChainId.SEPOLIA ? ENS_REGISTRY_ADDRESSES[ChainId.SEPOLIA] : ENS_REGISTRY_ADDRESSES[ChainId.MAINNET],
|
||||||
|
new ethers.providers.JsonRpcProvider(rpcUrl))
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => setAppSizeOnLoad(
|
||||||
|
(window.performance.getEntriesByType('navigation') as any)[0].transferSize
|
||||||
|
), []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
const infoResponse = await fetch('/info', { method: 'GET' })
|
||||||
|
|
||||||
|
if (infoResponse.status > 399) {
|
||||||
|
console.log('no info, unbooted')
|
||||||
|
} else {
|
||||||
|
const info: UnencryptedIdentity = await infoResponse.json()
|
||||||
|
|
||||||
|
if (initialVisit) {
|
||||||
|
setOsName(info.name)
|
||||||
|
setRouters(info.allowed_routers)
|
||||||
|
setNavigateToLogin(true)
|
||||||
|
setInitialVisit(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
console.log('no info, unbooted')
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const currentChainResponse = await fetch('/current-chain', { method: 'GET' })
|
||||||
|
|
||||||
|
if (currentChainResponse.status < 400) {
|
||||||
|
const nodeChainId = await currentChainResponse.json()
|
||||||
|
setNodeChainId(nodeChainId.toLowerCase())
|
||||||
|
console.log('Node Chain ID:', nodeChainId)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
console.log('error getting current chain')
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
}, []) // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
useEffect(() => setNavigateToLogin(false), [initialVisit])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
provider?.getNetwork().then(network => {
|
||||||
|
if (network.chainId === ChainId.SEPOLIA) {
|
||||||
|
setDotOs(DotOsRegistrar__factory.connect(
|
||||||
|
DOT_OS_ADDRESSES[ChainId.SEPOLIA],
|
||||||
|
provider!.getSigner()
|
||||||
|
))
|
||||||
|
setKns(KNSRegistryResolver__factory.connect(
|
||||||
|
KNS_REGISTRY_ADDRESSES[ChainId.SEPOLIA],
|
||||||
|
provider!.getSigner()
|
||||||
|
))
|
||||||
|
setKnsEnsEntry(KNSEnsEntry__factory.connect(
|
||||||
|
KNS_ENS_ENTRY_ADDRESSES[ChainId.SEPOLIA],
|
||||||
|
provider!.getSigner()
|
||||||
|
))
|
||||||
|
setKnsEnsExit(KNSEnsExit__factory.connect(
|
||||||
|
KNS_ENS_EXIT_ADDRESSES[ChainId.SEPOLIA],
|
||||||
|
provider!.getSigner()
|
||||||
|
))
|
||||||
|
setNameWrapper(NameWrapper__factory.connect(
|
||||||
|
NAMEWRAPPER_ADDRESSES[ChainId.SEPOLIA],
|
||||||
|
provider!.getSigner()
|
||||||
|
))
|
||||||
|
setEnsRegistry(ENSRegistry__factory.connect(
|
||||||
|
ENS_REGISTRY_ADDRESSES[ChainId.SEPOLIA],
|
||||||
|
provider!.getSigner()
|
||||||
|
))
|
||||||
|
|
||||||
|
} else if (network.chainId === ChainId.OPTIMISM || network.chainId === ChainId.MAINNET) {
|
||||||
|
setDotOs(DotOsRegistrar__factory.connect(
|
||||||
|
DOT_OS_ADDRESSES[ChainId.OPTIMISM],
|
||||||
|
provider!.getSigner())
|
||||||
|
)
|
||||||
|
setKns(KNSRegistryResolver__factory.connect(
|
||||||
|
KNS_REGISTRY_ADDRESSES[ChainId.OPTIMISM],
|
||||||
|
provider!.getSigner())
|
||||||
|
)
|
||||||
|
setKnsEnsExit(KNSEnsExit__factory.connect(
|
||||||
|
KNS_ENS_EXIT_ADDRESSES[ChainId.OPTIMISM],
|
||||||
|
provider!.getSigner()
|
||||||
|
))
|
||||||
|
setKnsEnsEntry(KNSEnsEntry__factory.connect(
|
||||||
|
KNS_ENS_ENTRY_ADDRESSES[ChainId.MAINNET],
|
||||||
|
provider!.getSigner()
|
||||||
|
))
|
||||||
|
setNameWrapper(NameWrapper__factory.connect(
|
||||||
|
NAMEWRAPPER_ADDRESSES[ChainId.MAINNET],
|
||||||
|
new ethers.providers.JsonRpcProvider(process.env.REACT_APP_MAINNET_RPC_URL)
|
||||||
|
))
|
||||||
|
setEnsRegistry(ENSRegistry__factory.connect(
|
||||||
|
ENS_REGISTRY_ADDRESSES[ChainId.MAINNET],
|
||||||
|
new ethers.providers.JsonRpcProvider(process.env.REACT_APP_MAINNET_RPC_URL)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [provider])
|
||||||
|
|
||||||
|
const knsEnsEntryNetwork = ChainId.SEPOLIA;
|
||||||
|
const knsEnsExitNetwork = ChainId.SEPOLIA;
|
||||||
|
|
||||||
|
// just pass all the props each time since components won't mind extras
|
||||||
|
const props = {
|
||||||
|
direct, setDirect,
|
||||||
|
key,
|
||||||
|
keyFileName, setKeyFileName,
|
||||||
|
reset, setReset,
|
||||||
|
pw, setPw,
|
||||||
|
knsName, setOsName,
|
||||||
|
dotOs, kns,
|
||||||
|
knsEnsEntryNetwork, knsEnsExitNetwork,
|
||||||
|
knsEnsEntry, knsEnsExit,
|
||||||
|
nameWrapper, ensRegistry,
|
||||||
|
connectOpen, openConnect, closeConnect,
|
||||||
|
provider, appSizeOnLoad,
|
||||||
|
networkingKey, setNetworkingKey,
|
||||||
|
ipAddress, setIpAddress,
|
||||||
|
port, setPort,
|
||||||
|
routers, setRouters,
|
||||||
|
nodeChainId,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ConnectWallet {...props} />
|
||||||
|
<Router>
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={navigateToLogin
|
||||||
|
? <Navigate to="/login" replace />
|
||||||
|
: <KinodeHome {...props} />
|
||||||
|
} />
|
||||||
|
<Route path="/claim-invite" element={<ClaimOsInvite {...props} />} />
|
||||||
|
<Route path="/register-name" element={<RegisterOsName {...props} />} />
|
||||||
|
<Route path="/register-eth-name" element={<RegisterEthName {...props} />} />
|
||||||
|
<Route path="/set-password" element={<SetPassword {...props} />} />
|
||||||
|
<Route path="/reset" element={<Reset {...props} />} />
|
||||||
|
<Route path="/reset-node" element={<ResetNode {...props} />} />
|
||||||
|
<Route path="/import-keyfile" element={<ImportKeyfile {...props} />} />
|
||||||
|
<Route path="/login" element={<Login {...props} />} />
|
||||||
|
</Routes>
|
||||||
|
</Router>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
7741
kinode/src/register-ui/src/abis/DotOsRegistrar.json
Normal file
1336
kinode/src/register-ui/src/abis/DotOsRegistrar.ts
Normal file
1
kinode/src/register-ui/src/abis/ENSRegistry.json
Normal file
1
kinode/src/register-ui/src/abis/KNSEnsEntry.json
Normal file
1
kinode/src/register-ui/src/abis/KNSEnsExit.json
Normal file
11870
kinode/src/register-ui/src/abis/KNSRegistryResolver.json
Normal file
1
kinode/src/register-ui/src/abis/NameWrapper.json
Normal file
BIN
kinode/src/register-ui/src/assets/arbitrum.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
kinode/src/register-ui/src/assets/background.jpg
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
kinode/src/register-ui/src/assets/eth.png
Normal file
After Width: | Height: | Size: 1.7 KiB |