Move VM to root

This commit is contained in:
Evgeny Kuzyakov 2023-02-16 09:59:08 -08:00
parent 6f90e95caa
commit 5b68433497
79 changed files with 120 additions and 3319 deletions

View File

@ -1,3 +1,3 @@
{
"presets": ["@babel/env", "@babel/preset-react"]
"presets": ["@babel/preset-env", "@babel/preset-react"]
}

1
.gitignore vendored
View File

@ -10,6 +10,7 @@
# production
/build
/dist
# misc
.DS_Store

View File

@ -1,84 +0,0 @@
# Browser
A framework for reusable components to render and modify SocialDB by Near Social.
## Setup & Development
Initialize repo:
```
yarn
```
Start development version:
```
yarn start
```
## Widget example
Profile view
```jsx
let accountId = props.accountId || "eugenethedream";
let profile = socialGetr(`${accountId}/profile`);
(
<div>
<img src={profile.image.url}/>
<span>{profile.name}</span> <span>(@{accountId})</span>
</div>
);
```
Profile editor
```jsx
let accountId = context.accountId;
if (!accountId) {
return "Please sign in with NEAR wallet";
}
const profile = socialGetr(`${accountId}/profile`);
if (profile === null) {
return "Loading";
}
initState({
name: profile.name,
url: profile.image.url,
});
const data = {
profile: {
name: state.name,
image: {
url: state.url,
},
},
};
return (
<div>
<div>account = {accountId}</div>
<div>
Name:
<input type="text" value={state.name} />
</div>
<div>
Image URL:
<input type="text" value={state.url} />
</div>
<div>Preview</div>
<div>
<img src={state.url} alt="profile image" /> {state.name}
</div>
<div>
<CommitButton data={data}>Save profile</CommitButton>
</div>
</div>
);
```

View File

@ -4,11 +4,12 @@ const path = require("path");
module.exports = () => {
return {
output: {
path: path.resolve(__dirname, "../dist"),
publicPath: "./",
filename: "[name].[contenthash].bundle.js",
},
// output: {
// path: path.resolve(__dirname, "../dist"),
// publicPath: "./",
// // filename: "[name].[contenthash].bundle.js",
// filename: "index.js",
// },
devtool: false,
module: {
rules: [
@ -61,9 +62,9 @@ module.exports = () => {
optimization: {
minimize: true,
minimizer: [new CssMinimizerPlugin(), "..."],
runtimeChunk: {
name: "runtime",
},
// runtimeChunk: {
// name: "runtime",
// },
},
performance: {
hints: false,

View File

@ -1,35 +1,86 @@
{
"name": "frontend",
"version": "0.5.0",
"homepage": "/",
"private": true,
"name": "near-social-vm",
"version": "0.1.0",
"description": "Near Social VM",
"main": "dist/index.js",
"files": [
"dist"
],
"dependencies": {
"@monaco-editor/react": "^4.4.6",
"@near-wallet-selector/modal-ui": "^7.4.0",
"@braintree/sanitize-url": "6.0.0",
"@near-wallet-selector/core": "^7.4.0",
"@near-wallet-selector/here-wallet": "^7.4.0",
"@near-wallet-selector/meteor-wallet": "^7.4.0",
"@near-wallet-selector/my-near-wallet": "^7.4.0",
"@near-wallet-selector/near-wallet": "^7.4.0",
"@near-wallet-selector/neth": "^7.4.0",
"@near-wallet-selector/sender": "^7.4.0",
"acorn": "^8.8.0",
"acorn-jsx": "^5.3.2",
"big.js": "^6.1.1",
"bn.js": "^5.1.1",
"bootstrap": "^5.2.1",
"bootstrap-icons": "^1.9.0",
"collections": "^5.1.12",
"error-polyfill": "^0.1.2",
"deep-equal": "^2.2.0",
"elliptic": "^6.5.4",
"idb": "^7.1.1",
"local-storage": "^2.0.0",
"mdast-util-find-and-replace": "^2.0.0",
"near-api-js": "^0.45.1",
"near-social-vm": "file:src/near-social-vm",
"prettier": "^2.7.1",
"react": "^18.2.0",
"react-bootstrap": "^2.5.0",
"react-dom": "^18.2.0",
"react-router-dom": "^5.2.0",
"styled-components": "^5.3.6"
"react-bootstrap-typeahead": "^6.0.0",
"react-error-boundary": "^3.1.4",
"react-files": "^3.0.0-alpha.3",
"react-infinite-scroller": "^1.2.6",
"react-markdown": "^7.1.0",
"react-singleton-hook": "^3.1.1",
"react-syntax-highlighter": "^15.5.0",
"react-uuid": "^1.0.2",
"remark-gfm": "^3.0.1",
"styled-components": "^5.3.6",
"tweetnacl": "^1.0.3"
},
"devDependencies": {
"@babel/core": "^7.20.12",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"buffer": "^6.0.3",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^9.0.1",
"cross-env": "^7.0.3",
"crypto-browserify": "^3.12.0",
"css-loader": "^6.2.0",
"css-minimizer-webpack-plugin": "^3.0.2",
"html-webpack-plugin": "^5.3.2",
"https-browserify": "^1.0.0",
"mini-css-extract-plugin": "^2.2.2",
"node-sass": "^7.0.3",
"os-browserify": "^0.3.0",
"postcss-loader": "^7.0.1",
"process": "^0.11.10",
"sass-loader": "^13.1.0",
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
"style-loader": "^3.2.1",
"url": "^0.11.0",
"webpack": "^5.52.0",
"webpack-bundle-analyzer": "^4.4.2",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.1.0",
"webpack-manifest-plugin": "^5.0.0",
"webpack-merge": "^5.8.0",
"webpack-node-externals": "^3.0.0"
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"scripts": {
"serve": "webpack serve",
"webpack": "webpack",
"dev": "npm run serve -- --env mode=development",
"prod": "npm run webpack -- --env mode=production",
"prod:analyze": "npm run prod -- --env presets=analyze",
"build": "npm run prod",
"start": "npm run dev"
"build": "npm run prod"
},
"eslintConfig": {
"extends": [
@ -48,40 +99,5 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@babel/cli": "^7.15.4",
"@babel/core": "^7.15.5",
"@babel/preset-env": "^7.15.4",
"@babel/preset-react": "^7.14.5",
"assert": "^2.0.0",
"babel-loader": "^8.2.2",
"browserify-zlib": "^0.2.0",
"buffer": "^6.0.3",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^9.0.1",
"cross-env": "^7.0.3",
"crypto-browserify": "^3.12.0",
"css-loader": "^6.2.0",
"css-minimizer-webpack-plugin": "^3.0.2",
"html-webpack-plugin": "^5.3.2",
"https-browserify": "^1.0.0",
"mini-css-extract-plugin": "^2.2.2",
"node-sass": "^7.0.3",
"os-browserify": "^0.3.0",
"path-browserify": "^1.0.1",
"postcss-loader": "^7.0.1",
"process": "^0.11.10",
"sass-loader": "^13.1.0",
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
"style-loader": "^3.2.1",
"url": "^0.11.0",
"webpack": "^5.52.0",
"webpack-bundle-analyzer": "^4.4.2",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.1.0",
"webpack-manifest-plugin": "^5.0.0",
"webpack-merge": "^5.8.0"
}
}

View File

View File

@ -1 +0,0 @@
near.social

View File

@ -1 +0,0 @@
/* /index.html 200

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,33 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
name="description"
content="Social data protocol built on NEAR"
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@NearSocial_">
<meta name="twitter:image" content="https://near.social/assets/logo.png">
<meta property="og:image" content="https://near.social/assets/logo.png">
<meta property="og:type" content="website">
<meta property="og:title" content="Near Social" />
<meta property="og:description" content="Social data protocol built on NEAR" />
<link rel="apple-touch-icon" href="favicon.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="manifest.json" />
<title>Near Social</title>
<script defer data-domain="near.social" src="https://plausible.io/js/script.hash.js"></script>
<script>window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }; window.analytics = function() { window.plausible(...arguments) }</script>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

View File

@ -1,15 +0,0 @@
{
"short_name": "Near Social",
"name": "Near Social",
"icons": [
{
"src": "favicon.png",
"sizes": "1024x1024",
"type": "image/png"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#333333",
"background_color": "#ffffff"
}

View File

@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@ -1,143 +0,0 @@
import React, { useCallback, useEffect, useState } from "react";
import "error-polyfill";
import "bootstrap-icons/font/bootstrap-icons.css";
import "@near-wallet-selector/modal-ui/styles.css";
import "bootstrap/dist/js/bootstrap.bundle";
import "App.scss";
import { HashRouter as Router, Link, Route, Switch } from "react-router-dom";
import EditorPage from "./pages/EditorPage";
import ViewPage from "./pages/ViewPage";
import { setupModal } from "@near-wallet-selector/modal-ui";
import EmbedPage from "./pages/EmbedPage";
import { useAccount, useInitNear, useNear, utils } from "near-social-vm";
import Big from "big.js";
import { NavigationWrapper } from "./components/navigation/NavigationWrapper";
import { NetworkId, Widgets } from "./data/widgets";
export const refreshAllowanceObj = {};
function App(props) {
console.log(useState);
const [connected, setConnected] = useState(false);
const [signedIn, setSignedIn] = useState(false);
const [signedAccountId, setSignedAccountId] = useState(null);
const [availableStorage, setAvailableStorage] = useState(null);
const [walletModal, setWalletModal] = useState(null);
const [widgetSrc, setWidgetSrc] = useState(null);
const { initNear } = useInitNear();
const near = useNear();
const account = useAccount();
const accountId = account.accountId;
const location = window.location;
useEffect(() => {
initNear &&
initNear({
networkId: NetworkId,
});
}, [initNear]);
useEffect(() => {
if (
!location.search.includes("?account_id") &&
!location.search.includes("&account_id") &&
(location.search || location.href.includes("/?#"))
) {
window.history.replaceState({}, "/", "/" + location.hash);
}
}, [location]);
useEffect(() => {
if (!near) {
return;
}
near.selector.then((selector) => {
setWalletModal(
setupModal(selector, { contractId: near.config.contractName })
);
});
}, [near]);
const requestSignIn = useCallback(
(e) => {
e && e.preventDefault();
walletModal.show();
return false;
},
[walletModal]
);
const logOut = useCallback(async () => {
if (!near) {
return;
}
const wallet = await (await near.selector).wallet();
wallet.signOut();
near.accountId = null;
setSignedIn(false);
setSignedAccountId(null);
}, [near]);
const refreshAllowance = useCallback(async () => {
alert(
"You're out of access key allowance. Need sign in again to refresh it"
);
await logOut();
requestSignIn();
}, [logOut, requestSignIn]);
refreshAllowanceObj.refreshAllowance = refreshAllowance;
useEffect(() => {
if (!near) {
return;
}
setSignedIn(!!accountId);
setSignedAccountId(accountId);
setConnected(true);
}, [near, accountId]);
useEffect(() => {
setAvailableStorage(
account.storageBalance
? Big(account.storageBalance.available).div(utils.StorageCostPerByte)
: Big(0)
);
}, [account]);
const passProps = {
refreshAllowance: () => refreshAllowance(),
setWidgetSrc,
signedAccountId,
signedIn,
connected,
availableStorage,
widgetSrc,
logOut,
requestSignIn,
widgets: Widgets,
};
return (
<div className="App">
<Router basename={process.env.PUBLIC_URL}>
<Switch>
<Route path={"/embed/:widgetSrc*"}>
<EmbedPage {...passProps} />
</Route>
<Route path={"/edit/:widgetSrc*"}>
<NavigationWrapper {...passProps} />
<EditorPage {...passProps} />
</Route>
<Route path={"/:widgetSrc*"}>
<NavigationWrapper {...passProps} />
<ViewPage {...passProps} />
</Route>
</Switch>
</Router>
</div>
);
}
export default App;

View File

@ -1,45 +0,0 @@
@import "bootstrap";
body, html {
-webkit-font-smoothing: antialiased;
}
.pointer {
cursor: pointer;
}
.btn-success, .btn-primary {
.text-muted {
color: #fff !important;
};
}
.outline-none {
outline: 0 !important;
}
a {
text-decoration: none;
&:hover {
text-decoration: underline;
}
&.btn {
text-decoration: none;
}
}
.sidebar-items {
&> div {
border-right: 1px solid #e5e5e5;
text-align: center;
min-height: 4rem;
max-height: 4rem;
display: flex;
}
&> div:not(.apps) {
min-width: 4rem;
align-items: center !important;
justify-content: center !important;
}
}

View File

@ -1,62 +0,0 @@
import React, { useState } from "react";
import Modal from "react-bootstrap/Modal";
export default function OpenModal(props) {
const onHide = props.onHide;
const onOpen = props.onOpen;
const onNew = props.onNew;
const show = props.show;
const [widgetSrc, setWidgetSrc] = useState("");
return (
<Modal centered scrollable show={show} onHide={onHide}>
<Modal.Header closeButton>
<Modal.Title>Open widget</Modal.Title>
</Modal.Header>
<Modal.Body>
<label htmlFor="widget-src-input" className="form-label">
Widget name <span className="text-muted">(or path)</span>
</label>
<input
className="form-control"
id="widget-src-input"
type="text"
value={widgetSrc}
onChange={(e) =>
setWidgetSrc(e.target.value.replaceAll(/[^a-zA-Z0-9_.\-\/]/g, ""))
}
/>
</Modal.Body>
<Modal.Footer>
<button
className="btn btn-success"
disabled={!widgetSrc}
onClick={(e) => {
e.preventDefault();
onOpen(widgetSrc);
setWidgetSrc("");
onHide();
}}
>
Open
</button>
<button
className="btn btn-outline-success"
disabled={widgetSrc && widgetSrc.indexOf("/") !== -1}
onClick={(e) => {
e.preventDefault();
onNew(widgetSrc);
setWidgetSrc("");
onHide();
}}
>
Create New
</button>
<button className="btn btn-secondary" onClick={onHide}>
Close
</button>
</Modal.Footer>
</Modal>
);
}

View File

@ -1,49 +0,0 @@
import React, { useState } from "react";
import Modal from "react-bootstrap/Modal";
export default function RenameModal(props) {
const onHide = props.onHide;
const name = props.name;
const onRename = props.onRename;
const show = props.show;
const [newName, setNewName] = useState(name);
return (
<Modal centered scrollable show={show} onHide={onHide}>
<Modal.Header closeButton>
<Modal.Title>Rename</Modal.Title>
</Modal.Header>
<Modal.Body>
<label htmlFor="rename-input" className="form-label">
New name
</label>
<input
className="form-control"
id="rename-input"
type="text"
value={newName}
onChange={(e) =>
setNewName(e.target.value.replaceAll(/[^a-zA-Z0-9_.\-]/g, ""))
}
/>
</Modal.Body>
<Modal.Footer>
<button
className="btn btn-success"
disabled={!newName || newName === name}
onClick={(e) => {
e.preventDefault();
onRename(newName);
onHide();
}}
>
Confirm
</button>
<button className="btn btn-secondary" onClick={onHide}>
Close
</button>
</Modal.Footer>
</Modal>
);
}

View File

@ -1,13 +0,0 @@
import React from "react";
import { Button } from "./Button";
import styled from "styled-components";
const StyledButton = styled(Button)`
background-color: var(--blue-light-9);
border-color: var(--blue-light-9);
color: white;
`;
export function BlueButton(props) {
return <StyledButton {...props}>{props.children}</StyledButton>;
}

View File

@ -1,26 +0,0 @@
import React from "react";
import styled from "styled-components";
const StyledButton = styled.button`
border-radius: 8px;
border-style: solid;
border-width: 1px;
border-color: transparent;
padding: 8px 16px;
font-weight: var(--font-weight-bold);
display: inline-block;
height: 40px;
`;
export function Button(props) {
return (
<StyledButton
className={props.className}
onClick={props.onClick}
title={props.title}
disabled={props.disabled}
>
{props.children}
</StyledButton>
);
}

View File

@ -1,13 +0,0 @@
import React from "react";
import { Button } from "./Button";
import styled from "styled-components";
const StyledButton = styled(Button)`
background-color: var(--slate-dark-6);
border-color: var(--slate-dark-8);
color: white;
`;
export function GrayBorderButton(props) {
return <StyledButton {...props}>{props.children}</StyledButton>;
}

View File

@ -1,29 +0,0 @@
import React from "react";
export function ArrowUpRight() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="arrow-up-right"
>
<path
d="M17.25 15.25V6.75H8.75"
stroke="#9BA1A6"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M17 7L6.75 17.25"
stroke="#9BA1A6"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@ -1,28 +0,0 @@
import React from "react";
export function Book() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19.25 5.75C19.25 5.19772 18.8023 4.75 18.25 4.75H14C12.8954 4.75 12 5.64543 12 6.75V19.25L12.8284 18.4216C13.5786 17.6714 14.596 17.25 15.6569 17.25H18.25C18.8023 17.25 19.25 16.8023 19.25 16.25V5.75Z"
stroke="#9BA1A6"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M4.75 5.75C4.75 5.19772 5.19772 4.75 5.75 4.75H10C11.1046 4.75 12 5.64543 12 6.75V19.25L11.1716 18.4216C10.4214 17.6714 9.40401 17.25 8.34315 17.25H5.75C5.19772 17.25 4.75 16.8023 4.75 16.25V5.75Z"
stroke="#9BA1A6"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@ -1,28 +0,0 @@
import React from "react";
export function Close() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17.25 6.75L6.75 17.25"
stroke="#9BA1A6"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M6.75 6.75L17.25 17.25"
stroke="#9BA1A6"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@ -1,28 +0,0 @@
import React from "react";
export function Code() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M4.75 6.75C4.75 5.64543 5.64543 4.75 6.75 4.75H17.25C18.3546 4.75 19.25 5.64543 19.25 6.75V17.25C19.25 18.3546 18.3546 19.25 17.25 19.25H6.75C5.64543 19.25 4.75 18.3546 4.75 17.25V6.75Z"
stroke="#9BA1A6"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M8.75 10.75L11.25 13L8.75 15.25"
stroke="#9BA1A6"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@ -1,42 +0,0 @@
import React from "react";
export function Fork() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.25 7C9.25 8.24264 8.24264 9.25 7 9.25C5.75736 9.25 4.75 8.24264 4.75 7C4.75 5.75736 5.75736 4.75 7 4.75C8.24264 4.75 9.25 5.75736 9.25 7Z"
stroke="#9BA1A6"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M19.25 7C19.25 8.24264 18.2426 9.25 17 9.25C15.7574 9.25 14.75 8.24264 14.75 7C14.75 5.75736 15.7574 4.75 17 4.75C18.2426 4.75 19.25 5.75736 19.25 7Z"
stroke="#9BA1A6"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M14.25 17C14.25 18.2426 13.2426 19.25 12 19.25C10.7574 19.25 9.75 18.2426 9.75 17C9.75 15.7574 10.7574 14.75 12 14.75C13.2426 14.75 14.25 15.7574 14.25 17Z"
stroke="#9BA1A6"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M6.75 9.5V10.25C6.75 11.3546 7.64543 12.25 8.75 12.25H12M17.25 9.5V10.25C17.25 11.3546 16.3546 12.25 15.25 12.25H12M12 12.25V14.5"
stroke="#9BA1A6"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@ -1,28 +0,0 @@
import React from "react";
export function Home() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.75024 19.2502H17.2502C18.3548 19.2502 19.2502 18.3548 19.2502 17.2502V9.75025L12.0002 4.75024L4.75024 9.75025V17.2502C4.75024 18.3548 5.64568 19.2502 6.75024 19.2502Z"
stroke="#9BA1A6"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M9.74963 15.7493C9.74963 14.6447 10.6451 13.7493 11.7496 13.7493H12.2496C13.3542 13.7493 14.2496 14.6447 14.2496 15.7493V19.2493H9.74963V15.7493Z"
stroke="#9BA1A6"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@ -1,35 +0,0 @@
import React from "react";
export function LogOut() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15.75 8.75L19.25 12L15.75 15.25"
stroke="#697177"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M19 12H10.75"
stroke="#697177"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M15.25 4.75H6.75C5.64543 4.75 4.75 5.64543 4.75 6.75V17.25C4.75 18.3546 5.64543 19.25 6.75 19.25H15.25"
stroke="#697177"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@ -1,28 +0,0 @@
import React from "react";
export function NearSocialLogo() {
return (
<svg
width="29"
height="20"
viewBox="0 0 29 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55396 17.509L2 9.99996L9.55396 2.49097"
stroke="#3D7FFF"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M19.536 2.49097L27 9.99996L19.536 17.509"
stroke="#3D7FFF"
strokeWidth="3"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@ -1,24 +0,0 @@
import React from "react";
export function Pretend() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="none"
viewBox="0 0 16 16"
>
<path
strokeWidth="0.3px"
fill="#697177"
d="M1.5 1a.5.5 0 0 0-.5.5v3a.5.5 0 0 1-1 0v-3A1.5 1.5 0 0 1 1.5 0h3a.5.5 0 0 1 0 1h-3zM11 .5a.5.5 0 0 1 .5-.5h3A1.5 1.5 0 0 1 16 1.5v3a.5.5 0 0 1-1 0v-3a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 1-.5-.5zM.5 11a.5.5 0 0 1 .5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 1 0 1h-3A1.5 1.5 0 0 1 0 14.5v-3a.5.5 0 0 1 .5-.5zm15 0a.5.5 0 0 1 .5.5v3a1.5 1.5 0 0 1-1.5 1.5h-3a.5.5 0 0 1 0-1h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 1 .5-.5z"
/>
<path
strokeWidth="0.3px"
fill="#697177"
d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm8-9a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"
/>
</svg>
);
}

View File

@ -1,19 +0,0 @@
import React from "react";
export function StopPretending() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
fill="none"
viewBox="0 0 16 16"
>
<path
strokeWidth="0.3px"
fill="#697177"
d="M13.879 10.414a2.501 2.501 0 0 0-3.465 3.465l3.465-3.465Zm.707.707-3.465 3.465a2.501 2.501 0 0 0 3.465-3.465Zm-4.56-1.096a3.5 3.5 0 1 1 4.949 4.95 3.5 3.5 0 0 1-4.95-4.95ZM11 5a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm-9 8c0 1 1 1 1 1h5.256A4.493 4.493 0 0 1 8 12.5a4.49 4.49 0 0 1 1.544-3.393C9.077 9.038 8.564 9 8 9c-5 0-6 3-6 4Z"
/>
</svg>
);
}

View File

@ -1,28 +0,0 @@
import React from "react";
export function User() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15.25 8C15.25 9.79493 13.7949 11.25 12 11.25C10.2051 11.25 8.75 9.79493 8.75 8C8.75 6.20507 10.2051 4.75 12 4.75C13.7949 4.75 15.25 6.20507 15.25 8Z"
stroke="#697177"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M6.84751 19.25H17.1525C18.2944 19.25 19.174 18.2681 18.6408 17.2584C17.8563 15.7731 16.068 14 12 14C7.93201 14 6.14367 15.7731 5.35924 17.2584C4.82597 18.2681 5.70559 19.25 6.84751 19.25Z"
stroke="#697177"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@ -1,35 +0,0 @@
import React from "react";
export function UserCircle() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19.25 12C19.25 16.0041 16.0041 19.25 12 19.25C7.99594 19.25 4.75 16.0041 4.75 12C4.75 7.99594 7.99594 4.75 12 4.75C16.0041 4.75 19.25 7.99594 19.25 12Z"
stroke="#9BA1A6"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M14.25 10C14.25 11.2426 13.2426 12.25 12 12.25C10.7574 12.25 9.75 11.2426 9.75 10C9.75 8.75736 10.7574 7.75 12 7.75C13.2426 7.75 14.25 8.75736 14.25 10Z"
stroke="#9BA1A6"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M7.19745 17C8.34392 15.625 10.0698 14.75 12 14.75C13.9302 14.75 15.6561 15.625 16.8025 17"
stroke="#9BA1A6"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@ -1,19 +0,0 @@
import React from "react";
export function Withdraw() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeWidth="0.3px"
d="M14.9634 10.8766C15.2234 11.1366 15.2234 11.5567 14.9634 11.8167L12.4701 14.3067C12.2101 14.5701 11.79 14.5701 11.5301 14.3067L9.03676 11.8167C8.77679 11.5567 8.77679 11.1366 9.03676 10.8766C9.30016 10.6133 9.72011 10.6133 9.98008 10.8766L11.3333 12.2267V6.66667C11.3333 5.1933 12.5266 4 14 4H17.3333C18.8067 4 20 5.1933 20 6.66667V17.3333C20 18.8067 18.8067 20 17.3333 20H6.66667C5.1933 20 4 18.8067 4 17.3333V6.66667C4 5.1933 5.1933 4 6.66667 4H9.33333C9.7 4 10 4.3 10 4.66667C10 5.03333 9.7 5.33333 9.33333 5.33333H6.66667C5.93006 5.33333 5.33333 5.93006 5.33333 6.66667V17.3333C5.33333 18.0699 5.93006 18.6667 6.66667 18.6667H17.3333C18.0699 18.6667 18.6667 18.0699 18.6667 17.3333V6.66667C18.6667 5.93006 18.0699 5.33333 17.3333 5.33333H14C13.2634 5.33333 12.6667 5.93006 12.6667 6.66667V12.2267L14.0199 10.8766C14.28 10.6134 14.7 10.6134 14.9634 10.8766Z"
fill="#697177"
/>
</svg>
);
}

File diff suppressed because one or more lines are too long

View File

@ -1,51 +0,0 @@
import React from "react";
import styled from "styled-components";
import { NavLink } from "react-router-dom";
const StyledNavigationButton = styled.div`
a {
color: var(--slate-dark-11);
font-size: 16px;
padding: 10px;
border-radius: 8px;
font-weight: var(--font-weight-bold);
height: 40px;
display: flex;
align-items: center;
justify-content: center;
&:hover,
&.active {
color: white;
text-decoration: none;
background-color: var(--slate-dark-6);
}
}
&.disabled {
opacity: 0.5;
}
`;
export function NavigationButton(props) {
return (
<StyledNavigationButton className={props.disabled ? "disabled" : ""}>
{props.route ? (
<NavLink
onClick={(e) => {
if (props.disabled) {
e.preventDefault();
}
}}
to={props.route}
exact={true}
>
{props.children}
</NavLink>
) : (
<a href={props.href} target="_blank" rel="noopener noreferrer">
{props.children}
</a>
)}
</StyledNavigationButton>
);
}

View File

@ -1,21 +0,0 @@
import React, { useState, useEffect } from "react";
import { DesktopNavigation } from "./desktop/DesktopNavigation";
import { MobileNavigation } from "./mobile/MobileNavigation";
export function NavigationWrapper(props) {
const [matches, setMatches] = useState(
window.matchMedia("(min-width: 992px)").matches
);
useEffect(() => {
window
.matchMedia("(min-width: 992px)")
.addEventListener("change", (e) => setMatches(e.matches));
}, []);
return (
<>
{matches && <DesktopNavigation {...props} />}
{!matches && <MobileNavigation {...props} />}
</>
);
}

View File

@ -1,43 +0,0 @@
import React from "react";
import styled from "styled-components";
import { Widget } from "near-social-vm";
const StyledNotificationWidget = styled.div`
margin: 0 15px;
background-color: var(--slate-dark-5);
height: 40px;
width: 40px;
border-radius: 50%;
> div,
a {
width: 100%;
height: 100%;
}
a {
color: var(--slate-dark-11) !important;
display: flex;
align-items: center;
justify-content: center;
i {
font-size: 18px !important;
}
}
:hover {
a,
i {
color: white;
}
}
`;
export function NotificationWidget({ notificationButtonSrc }) {
return (
<StyledNotificationWidget className="nav-notification-widget">
<Widget src={notificationButtonSrc} />
</StyledNotificationWidget>
);
}

View File

@ -1,60 +0,0 @@
import React, { useState } from "react";
import Modal from "react-bootstrap/Modal";
import { Widget, useAccount } from "near-social-vm";
export default function PretendModal(props) {
const account = useAccount();
const onHide = props.onHide;
const show = props.show;
const [accountId, setAccountId] = useState("");
return (
<Modal centered show={show} onHide={onHide}>
<Modal.Header closeButton>
<Modal.Title>Pretend to be another account</Modal.Title>
</Modal.Header>
<Modal.Body>
<div>
<label htmlFor="widget-src-input" className="form-label">
Pretend to be account ID:
</label>
<input
className="form-control"
id="widget-src-input"
type="text"
value={accountId}
onChange={(e) =>
setAccountId(
e.target.value.toLowerCase().replaceAll(/[^a-z0-9_.\-]/g, "")
)
}
/>
</div>
<div className="mt-2">
<Widget
src={props.widgets.profileInlineBlock}
props={{ accountId }}
/>
</div>
</Modal.Body>
<Modal.Footer>
<button
className="btn btn-success"
disabled={!accountId || !account.startPretending}
onClick={(e) => {
e.preventDefault();
account.startPretending(accountId);
setAccountId("");
onHide();
}}
>
Pretend
</button>
<button className="btn btn-secondary" onClick={onHide}>
Cancel
</button>
</Modal.Footer>
</Modal>
);
}

View File

@ -1,10 +0,0 @@
import React from "react";
import { GrayBorderButton } from "../common/buttons/GrayBorderButton";
export function SignInButton(props) {
return (
<GrayBorderButton className="nav-sign-in-btn" onClick={props.onSignIn}>
Sign In
</GrayBorderButton>
);
}

View File

@ -1,91 +0,0 @@
import React from "react";
import styled from "styled-components";
import { Link } from "react-router-dom";
import { Logotype } from "../Logotype";
import { NavigationButton } from "../NavigationButton";
import { ArrowUpRight } from "../../icons/ArrowUpRight";
import { SignInButton } from "../SignInButton";
import { UserDropdown } from "./UserDropdown";
import { DevActionsDropdown } from "./DevActionsDropdown";
import { NotificationWidget } from "../NotificationWidget";
const StyledNavigation = styled.div`
position: sticky;
top: 0;
left: 0;
right: 0;
width: 100%;
background-color: var(--slate-dark-1);
z-index: 1000;
padding: 12px 0;
.user-section {
margin-left: auto;
> button {
font-size: 14px;
}
}
.container {
display: flex;
align-items: center;
.navigation-section {
margin-left: 50px;
display: flex;
> div {
> a {
margin-right: 20px;
}
}
}
.user-section {
display: flex;
align-items: center;
.nav-create-btn {
margin-left: 10px;
}
}
.arrow-up-right {
margin-left: 4px;
}
}
`;
export function DesktopNavigation(props) {
return (
<StyledNavigation>
<div className="container">
<Link to="/" className="logo-link">
<Logotype />
</Link>
<div className="navigation-section">
<NavigationButton route="/">Home</NavigationButton>
<NavigationButton route="/edit">Create</NavigationButton>
<NavigationButton href="https://thewiki.near.page/near.social_docs">
Documentation
<ArrowUpRight />
</NavigationButton>
</div>
<div className="user-section">
{!props.signedIn && (
<SignInButton onSignIn={() => props.requestSignIn()} />
)}
{props.signedIn && (
<>
<DevActionsDropdown {...props} />
<NotificationWidget
notificationButtonSrc={props.widgets.notificationButton}
/>
<UserDropdown {...props} />
</>
)}
</div>
</div>
</StyledNavigation>
);
}

View File

@ -1,133 +0,0 @@
import React from "react";
import styled from "styled-components";
import { Link } from "react-router-dom";
import { Fork } from "../../icons/Fork";
import { Code } from "../../icons/Code";
import { useAccount } from "near-social-vm";
const StyledDropdown = styled.div`
.dropdown-toggle {
display: flex;
align-items: center;
justify-content: center;
background-color: var(--slate-dark-5);
border-radius: 50px;
outline: none;
border: 0;
width: 40px;
height: 40px;
&:after {
display none;
}
.menu {
width: 18px;
height: 24px;
display: flex;
flex-direction: column;
justify-content: space-evenly;
div {
background-color: var(--slate-dark-11);;
height: 2px;
width: 100%;
border-radius: 30px;
}
}
:hover {
.menu {
div {
background-color: white;
}
}
}
}
ul {
background-color: var(--slate-dark-5);
width: 100%;
li {
padding: 0 6px;
}
button,
a {
color: var(--slate-dark-11);
display: flex;
align-items: center;
border-radius: 8px;
padding: 12px;
:hover,
:focus {
text-decoration: none;
background-color: var(--slate-dark-1);
color: white;
svg {
path {
stroke: white;
}
}
}
svg {
margin-right: 7px;
path {
stroke: var(--slate-dark-9);
}
}
}
}
`;
export function DevActionsDropdown(props) {
const account = useAccount();
if (props.widgetSrc?.edit || props.widgetSrc?.view) {
return (
<StyledDropdown className="dropdown">
<button
className="dropdown-toggle"
type="button"
id="dropdownMenu2222"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<div className="menu">
<div />
<div />
<div />
</div>
</button>
<ul className="dropdown-menu" aria-labelledby="dropdownMenu2222">
{props.widgetSrc?.edit && (
<li>
<Link to={`/edit/${props.widgetSrc?.edit}`}>
<Fork />
{props.widgetSrc.edit.startsWith(`${account.accountId}/widget/`)
? "Edit widget"
: "Fork widget"}
</Link>
</li>
)}
{props.widgetSrc?.view && (
<li>
<Link
to={`/${props.widgets.viewSource}?src=${props.widgetSrc?.view}`}
>
<Code />
View source
</Link>
</li>
)}
</ul>
</StyledDropdown>
);
} else {
return null;
}
}

View File

@ -1,200 +0,0 @@
import React, { useCallback } from "react";
import { Widget, useNear, useAccount } from "near-social-vm";
import styled from "styled-components";
import { User } from "../../icons/User";
import { LogOut } from "../../icons/LogOut";
import { Withdraw } from "../../icons/Withdraw";
import { NavLink } from "react-router-dom";
import PretendModal from "../PretendModal";
import { Pretend } from "../../icons/Pretend";
import { StopPretending } from "../../icons/StopPretending";
const StyledDropdown = styled.div`
button,
a {
font-weight: var(--font-weight-medium);
}
.dropdown-toggle {
display: flex;
align-items: center;
text-align: left;
background-color: var(--slate-dark-5);
border-radius: 50px;
outline: none;
border: 0;
&:after {
margin: 0 15px;
border-top-color: var(--slate-dark-11);
}
img {
border-radius: 50% !important;
}
.profile-info {
margin: 5px 10px;
line-height: normal;
max-width: 140px;
.profile-name,
.profile-username {
text-overflow: ellipsis;
overflow: hidden;
}
.profile-name {
color: var(--slate-dark-12);
}
.profile-username {
color: var(--slate-dark-11);
}
}
}
ul {
background-color: var(--slate-dark-5);
width: 100%;
li {
padding: 0 6px;
}
button,
a {
color: var(--slate-dark-11);
display: flex;
align-items: center;
border-radius: 8px;
padding: 12px;
:hover,
:focus {
text-decoration: none;
background-color: var(--slate-dark-1);
color: white;
svg {
path {
stroke: white;
}
}
}
svg {
margin-right: 7px;
min-width: 24px;
path {
stroke: var(--slate-dark-9);
}
}
}
}
`;
export function UserDropdown(props) {
const near = useNear();
const account = useAccount();
const withdrawStorage = useCallback(async () => {
await near.contract.storage_withdraw({}, undefined, "1");
}, [near]);
const [showPretendModal, setShowPretendModal] = React.useState(false);
return (
<>
<StyledDropdown className="dropdown">
<button
className="dropdown-toggle"
type="button"
id="dropdownMenu2222"
data-bs-toggle="dropdown"
aria-expanded="false"
>
<Widget
src={props.widgets.profileImage}
props={{
accountId: account.accountId,
className: "d-inline-block",
style: { width: "40px", height: "40px" },
}}
/>
<div className="profile-info">
{props.widgets.profileName && (
<div className="profile-name">
<Widget src={props.widgets.profileName} />
</div>
)}
<div className="profile-username">{account.accountId}</div>
</div>
</button>
<ul
className="dropdown-menu"
aria-labelledby="dropdownMenu2222"
style={{ minWidth: "fit-content" }}
>
<li>
<NavLink
className="dropdown-item"
type="button"
to={`/${props.widgets.profilePage}?accountId=${account.accountId}`}
>
<User />
My Profile
</NavLink>
</li>
<li>
<button
className="dropdown-item"
type="button"
onClick={() => withdrawStorage()}
>
<Withdraw />
Withdraw {props.availableStorage.div(1000).toFixed(2)}kb
</button>
</li>
{account.pretendAccountId ? (
<li>
<button
className="dropdown-item"
type="button"
disabled={!account.startPretending}
onClick={() => account.startPretending(undefined)}
>
<StopPretending />
Stop pretending
</button>
</li>
) : (
<li>
<button
className="dropdown-item"
type="button"
onClick={() => setShowPretendModal(true)}
>
<Pretend />
Pretend to be another account
</button>
</li>
)}
<li>
<button
className="dropdown-item"
type="button"
onClick={() => props.logOut()}
>
<LogOut />
Sign Out
</button>
</li>
</ul>
</StyledDropdown>
<PretendModal
show={showPretendModal}
onHide={() => setShowPretendModal(false)}
widgets={props.widgets}
/>
</>
);
}

View File

@ -1,256 +0,0 @@
import React from "react";
import styled from "styled-components";
import { Close } from "../../icons/Close";
import { Home } from "../../icons/Home";
import { Book } from "../../icons/Book";
import { Code } from "../../icons/Code";
import { LogOut } from "../../icons/LogOut";
import { Fork } from "../../icons/Fork";
import { UserCircle } from "../../icons/UserCircle";
import { Widget } from "near-social-vm";
import { NavigationButton } from "../NavigationButton";
import { SignInButton } from "../SignInButton";
import { Link } from "react-router-dom";
const StyledMenu = styled.div`
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
display: flex;
transition: 350ms;
transform: translateX(-100%);
&.show {
transform: translateX(0);
}
ul {
list-style-type: none;
margin: 0;
padding: 0;
}
.left-side {
flex: 80;
background-color: var(--slate-dark-1);
position: relative;
display: flex;
flex-direction: column;
padding: 25px;
overflow-x: auto;
.nav-sign-in-btn {
width: fit-content;
}
.profile-link {
max-width: 100%;
white-space: nowrap;
:hover {
text-decoration: none;
}
}
img {
border-radius: 50% !important;
}
.profile-name {
color: var(--slate-dark-12);
font-weight: var(--font-weight-bold);
margin-top: 10px;
}
.profile-username {
color: var(--slate-dark-11);
}
.profile-name,
.profile-username {
text-overflow: ellipsis;
overflow: hidden;
}
}
.top-links,
.bottom-links {
a,
button {
justify-content: flex-start;
padding: 28px 0;
display: flex;
align-items: center;
color: var(--slate-dark-11);
font-weight: var(--font-weight-bold);
svg {
margin-right: 12px;
}
&.active,
&:hover,
&:focus {
background-color: transparent;
color: white;
text-decoration: none;
svg {
path {
stroke: white;
}
}
}
}
}
.top-links {
margin-top: 40px;
}
.bottom-links {
margin-top: auto;
a,
button {
padding: 14px 0;
}
}
.log-out-button {
background: none;
border: none;
color: var(--slate-dark-11);
font-weight: var(--font-weight-bold);
padding: 28px 0;
svg {
path {
stroke: #9ba1a6;
}
}
}
.close-button {
background: none;
border: none;
position: absolute;
right: 16px;
top: 16px;
padding: 10px;
svg {
margin: 0;
}
}
.right-side {
flex: 20;
opacity: 0.8;
background-color: var(--slate-dark-1);
}
`;
export function Menu(props) {
return (
<StyledMenu className={props.showMenu ? "show" : ""}>
<div className="left-side">
{props.signedIn ? (
<Link
to={`/${props.widgets.profilePage}?accountId=${props.signedAccountId}`}
className="profile-link"
>
<Widget
src={props.widgets.profileImage}
props={{
accountId: props.signedAccountId,
className: "d-inline-block",
style: { width: "56px", height: "56px" },
}}
/>
{props.widgets.profileName && (
<div className="profile-name">
<Widget src={props.widgets.profileName} />
</div>
)}
<div className="profile-username">{props.signedAccountId}</div>
</Link>
) : (
<SignInButton
onSignIn={() => {
props.onCloseMenu();
props.requestSignIn();
}}
/>
)}
<ul className="top-links">
<li>
<NavigationButton route="/">
<Home />
Home
</NavigationButton>
</li>
<li>
<NavigationButton
disabled={!props.signedIn}
route={`/${props.widgets.profilePage}?accountId=${props.signedAccountId}`}
>
<UserCircle />
Profile
</NavigationButton>
</li>
<li>
<NavigationButton route="/edit">
<Code />
Create
</NavigationButton>
</li>
<li>
<NavigationButton href="https://thewiki.near.page/near.social_docs">
<Book />
Documentation
</NavigationButton>
</li>
</ul>
<ul className="bottom-links">
{props.widgetSrc?.edit && (
<li>
<Link to={`/edit/${props.widgetSrc?.edit}`}>
<Fork />
{props.widgetSrc.edit.startsWith(
`${props.signedAccountId}/widget/`
)
? "Edit widget"
: "Fork widget"}
</Link>
</li>
)}
{props.widgetSrc?.view && (
<li>
<Link
to={`/${props.widgets.viewSource}?src=${props.widgetSrc?.view}`}
>
<Code />
View source
</Link>
</li>
)}
{props.signedIn && (
<li>
<button onClick={() => props.logOut()} className="log-out-button">
<LogOut />
Sign Out
</button>
</li>
)}
</ul>
<button className="close-button" onClick={props.onCloseMenu}>
<Close />
</button>
</div>
<div className="right-side" onClick={props.onCloseMenu} />
</StyledMenu>
);
}

View File

@ -1,41 +0,0 @@
import React from "react";
import styled from "styled-components";
const StyledMobileMenuButton = styled.button`
background-color: transparent;
border: none;
display: flex;
align-items: center;
color: white;
font-weight: var(--font-weight-bold);
padding: 0;
.menu {
width: 18px;
height: 24px;
display: flex;
flex-direction: column;
justify-content: space-evenly;
margin-right: 10px;
div {
background-color: white;
height: 2px;
width: 100%;
border-radius: 30px;
}
}
`;
export function MobileMenuButton(props) {
return (
<StyledMobileMenuButton onClick={props.onClick}>
<div className="menu">
<div />
<div />
<div />
</div>
{props.currentPage}
</StyledMobileMenuButton>
);
}

View File

@ -1,52 +0,0 @@
import React, { useState, useEffect } from "react";
import { Navigation } from "./Navigation";
import { Menu } from "./Menu";
import { useLocation } from "react-router-dom";
import useScrollBlock from ".././../../hooks/useScrollBlock";
export function MobileNavigation(props) {
const [showMenu, setShowMenu] = useState(false);
const [currentPage, setCurrentPage] = useState("");
const location = useLocation();
const [blockScroll, allowScroll] = useScrollBlock();
useEffect(() => {
setShowMenu(false);
getCurrentPage();
allowScroll();
}, [location.pathname]);
const getCurrentPage = () => {
switch (location.pathname) {
case "/":
return setCurrentPage("Home");
case `/${props.widgets.profilePage}`:
return setCurrentPage("Profile");
case "/edit":
return setCurrentPage("Create");
default:
return setCurrentPage("");
}
};
return (
<>
<Navigation
{...props}
currentPage={currentPage}
onClickShowMenu={() => {
setShowMenu(true);
blockScroll();
}}
/>
<Menu
{...props}
showMenu={showMenu}
onCloseMenu={() => {
setShowMenu(false);
allowScroll();
}}
/>
</>
);
}

View File

@ -1,63 +0,0 @@
import React from "react";
import styled from "styled-components";
import { Link } from "react-router-dom";
import { MobileMenuButton } from "./MobileMenuButton";
import { NearSocialLogo } from "../../icons/NearSocialLogo";
import { NotificationWidget } from "../NotificationWidget";
import { SignInButton } from "../SignInButton";
const StyledNavigation = styled.div`
position: sticky;
top: 0;
left: 0;
right: 0;
width: 100%;
background-color: var(--slate-dark-1);
z-index: 1000;
padding: 16px 24px;
display: flex;
align-items: center;
justify-content: space-between;
.logo-link {
position: absolute;
left: 0;
right: 0;
margin: auto;
display: flex;
align-items: center;
justify-content: center;
width: fit-content;
}
.nav-notification-widget {
margin: 0;
}
.nav-sign-in-btn {
background: none;
border: none;
padding-right: 0;
}
`;
export function Navigation(props) {
return (
<StyledNavigation>
<MobileMenuButton
onClick={props.onClickShowMenu}
currentPage={props.currentPage}
/>
<Link to="/" className="logo-link">
<NearSocialLogo />
</Link>
{props.signedIn ? (
<NotificationWidget
notificationButtonSrc={props.widgets.notificationButton}
/>
) : (
<SignInButton onSignIn={() => props.requestSignIn()} />
)}
</StyledNavigation>
);
}

View File

@ -1,35 +0,0 @@
const TestnetDomains = {
"test.near.social": true,
"127.0.0.1": true,
};
export const NetworkId =
window.location.hostname in TestnetDomains ? "testnet" : "mainnet";
const TestnetWidgets = {
image: "eugenethedream/widget/Image",
default: "eugenethedream/widget/Welcome",
viewSource: "eugenethedream/widget/WidgetSource",
widgetMetadataEditor: "eugenethedream/widget/WidgetMetadataEditor",
widgetMetadata: "eugenethedream/widget/WidgetMetadata",
profileImage: "eugenethedream/widget/ProfileImage",
profilePage: "eugenethedream/widget/Profile",
profileName: "eugenethedream/widget/ProfileName",
notificationButton: "eugenethedream/widget/NotificationButton",
};
const MainnetWidgets = {
image: "mob.near/widget/Image",
default: "mob.near/widget/Homepage",
viewSource: "mob.near/widget/WidgetSource",
widgetMetadataEditor: "mob.near/widget/WidgetMetadataEditor",
widgetMetadata: "mob.near/widget/WidgetMetadata",
profileImage: "mob.near/widget/ProfileImage",
notificationButton: "mob.near/widget/NotificationButton",
profilePage: "mob.near/widget/ProfilePage",
profileName: "patrick.near/widget/ProfileName",
editorComponentSearch: "mob.near/widget/Editor.ComponentSearch",
profileInlineBlock: "mob.near/widget/Profile.InlineBlock",
};
export const Widgets =
NetworkId === "testnet" ? TestnetWidgets : MainnetWidgets;

View File

@ -1,8 +0,0 @@
import { useLocation } from "react-router-dom";
import React from "react";
export function useQuery() {
const { search } = useLocation();
return React.useMemo(() => new URLSearchParams(search), [search]);
}

View File

@ -1,51 +0,0 @@
import { useRef } from "react";
const safeDocument = typeof document !== "undefined" ? document : {};
/**
* Usage:
* const [blockScroll, allowScroll] = useScrollBlock();
*/
export default () => {
const scrollBlocked = useRef();
const html = safeDocument.documentElement;
const { body } = safeDocument;
const blockScroll = () => {
if (!body || !body.style || scrollBlocked.current) return;
const scrollBarWidth = window.innerWidth - html.clientWidth;
const bodyPaddingRight =
parseInt(
window.getComputedStyle(body).getPropertyValue("padding-right")
) || 0;
/**
* 1. Fixes a bug in iOS and desktop Safari whereby setting
* `overflow: hidden` on the html/body does not prevent scrolling.
* 2. Fixes a bug in desktop Safari where `overflowY` does not prevent
* scroll if an `overflow-x` style is also applied to the body.
*/
html.style.position = "relative"; /* [1] */
html.style.overflow = "hidden"; /* [2] */
body.style.position = "relative"; /* [1] */
body.style.overflow = "hidden"; /* [2] */
body.style.paddingRight = `${bodyPaddingRight + scrollBarWidth}px`;
scrollBlocked.current = true;
};
const allowScroll = () => {
if (!body || !body.style || !scrollBlocked.current) return;
html.style.position = "";
html.style.overflow = "";
body.style.position = "";
body.style.overflow = "";
body.style.paddingRight = "";
scrollBlocked.current = false;
};
return [blockScroll, allowScroll];
};

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -1,4 +0,0 @@
<svg width="331" height="203" viewBox="0 0 331 203" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M107.137 185L18 101.5L107.137 18" stroke="#3D7FFF" stroke-width="35" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M224.924 18L313 101.5L224.924 185" stroke="#3D7FFF" stroke-width="35" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 362 B

View File

@ -1,13 +0,0 @@
:root {
--slate-dark-1: #151718;
--slate-dark-5: #2B2F31;
--slate-dark-6: #313538;
--slate-dark-8: #4C5155;
--slate-dark-9: #697177;
--slate-dark-11: #9BA1A6;
--slate-dark-12: #ECEDEE;
--blue-light-9: #0091FF;
--font-weight-medium: 500;
--font-weight-bold: 600;
}

View File

@ -1,8 +1,18 @@
import React from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App";
import { useAccount, useAccountId } from "./lib/data/account";
import { useInitNear, useNear } from "./lib/data/near";
import { Widget } from "./lib/components/Widget";
import { useCache } from "./lib/data/cache";
import * as utils from "./lib/data/utils";
import { CommitButton } from "./lib/components/Commit";
const container = document.getElementById("root");
const root = createRoot(container);
root.render(<App />);
export {
Widget,
CommitButton,
useInitNear,
useNear,
useCache,
useAccount,
useAccountId,
utils,
};

View File

@ -1,3 +0,0 @@
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}

View File

@ -1,33 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
/dist
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
#IDE
.idea
target
neardev
# OS X
.DS_Store

View File

@ -1,13 +0,0 @@
const path = require("path");
const srcPath = path.resolve(__dirname, "../src");
const distPath = path.resolve(__dirname, "../dist");
const publicPath = path.resolve(__dirname, "../public");
const nodeModulesPath = path.resolve(__dirname, "../node_modules");
module.exports = {
srcPath,
distPath,
publicPath,
nodeModulesPath,
};

View File

@ -1,13 +0,0 @@
const { merge } = require("webpack-merge");
const loadPresets = (env = { presets: [] }) => {
const presets = env.presets || [];
/** @type {string[]} */
const mergedPresets = [].concat(...[presets]);
const mergedConfigs = mergedPresets.map((presetName) =>
require(`./webpack.${presetName}.js`)(env)
);
return merge({}, ...mergedConfigs);
};
module.exports = loadPresets;

View File

@ -1,6 +0,0 @@
const WebpackBundleAnalyzer =
require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
module.exports = () => ({
plugins: [new WebpackBundleAnalyzer()],
});

View File

@ -1,48 +0,0 @@
const path = require("path");
const { HotModuleReplacementPlugin } = require("webpack");
module.exports = () => ({
devtool: false,
module: {
rules: [
{
test: /\.(scss|css)$/,
use: [
{
// inject CSS to page
loader: "style-loader",
},
{
// translates CSS into CommonJS modules
loader: "css-loader",
},
{
// Run postcss actions
loader: "postcss-loader",
options: {
// `postcssOptions` is needed for postcss 8.x;
// if you use postcss 7.x skip the key
postcssOptions: {
// postcss plugins, can be exported to postcss.config.js
plugins: function () {
return [require("autoprefixer")];
},
},
},
},
{
// compiles Sass to CSS
loader: "sass-loader",
},
],
},
],
},
devServer: {
open: true,
static: path.resolve(__dirname, "../dist"),
port: 3000,
compress: true,
},
plugins: [new HotModuleReplacementPlugin()],
});

View File

@ -1,75 +0,0 @@
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const path = require("path");
module.exports = () => {
return {
// output: {
// path: path.resolve(__dirname, "../dist"),
// publicPath: "./",
// // filename: "[name].[contenthash].bundle.js",
// filename: "index.js",
// },
devtool: false,
module: {
rules: [
// {
// test: /\.(css)$/,
// use: [MiniCssExtractPlugin.loader, "css-loader"],
// // options: {
// // sourceMap: false,
// // },
// },
{
test: /\.(scss|css)$/,
use: [
{
// inject CSS to page
loader: "style-loader",
},
{
// translates CSS into CommonJS modules
loader: "css-loader",
},
{
// Run postcss actions
loader: "postcss-loader",
options: {
// `postcssOptions` is needed for postcss 8.x;
// if you use postcss 7.x skip the key
postcssOptions: {
// postcss plugins, can be exported to postcss.config.js
plugins: function () {
return [require("autoprefixer")];
},
},
},
},
{
// compiles Sass to CSS
loader: "sass-loader",
},
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
filename: "styles/[name].[contenthash].css",
chunkFilename: "[id].css",
}),
],
optimization: {
minimize: true,
minimizer: [new CssMinimizerPlugin(), "..."],
// runtimeChunk: {
// name: "runtime",
// },
},
performance: {
hints: false,
maxEntrypointSize: 512000,
maxAssetSize: 512000,
},
};
};

View File

@ -1,103 +0,0 @@
{
"name": "near-social-vm",
"version": "0.1.0",
"description": "Near Social VM",
"main": "dist/index.js",
"files": [
"dist"
],
"dependencies": {
"@braintree/sanitize-url": "6.0.0",
"@near-wallet-selector/core": "^7.4.0",
"@near-wallet-selector/here-wallet": "^7.4.0",
"@near-wallet-selector/meteor-wallet": "^7.4.0",
"@near-wallet-selector/my-near-wallet": "^7.4.0",
"@near-wallet-selector/near-wallet": "^7.4.0",
"@near-wallet-selector/neth": "^7.4.0",
"@near-wallet-selector/sender": "^7.4.0",
"acorn": "^8.8.0",
"acorn-jsx": "^5.3.2",
"big.js": "^6.1.1",
"bn.js": "^5.1.1",
"bootstrap": "^5.2.1",
"bootstrap-icons": "^1.9.0",
"collections": "^5.1.12",
"deep-equal": "^2.2.0",
"elliptic": "^6.5.4",
"idb": "^7.1.1",
"local-storage": "^2.0.0",
"mdast-util-find-and-replace": "^2.0.0",
"near-api-js": "^0.45.1",
"prettier": "^2.7.1",
"react-bootstrap": "^2.5.0",
"react-bootstrap-typeahead": "^6.0.0",
"react-error-boundary": "^3.1.4",
"react-files": "^3.0.0-alpha.3",
"react-infinite-scroller": "^1.2.6",
"react-markdown": "^7.1.0",
"react-singleton-hook": "^3.1.1",
"react-syntax-highlighter": "^15.5.0",
"react-uuid": "^1.0.2",
"remark-gfm": "^3.0.1",
"styled-components": "^5.3.6",
"tweetnacl": "^1.0.3"
},
"devDependencies": {
"@babel/core": "^7.20.12",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"buffer": "^6.0.3",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^9.0.1",
"cross-env": "^7.0.3",
"crypto-browserify": "^3.12.0",
"css-loader": "^6.2.0",
"css-minimizer-webpack-plugin": "^3.0.2",
"html-webpack-plugin": "^5.3.2",
"https-browserify": "^1.0.0",
"mini-css-extract-plugin": "^2.2.2",
"node-sass": "^7.0.3",
"os-browserify": "^0.3.0",
"postcss-loader": "^7.0.1",
"process": "^0.11.10",
"sass-loader": "^13.1.0",
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
"style-loader": "^3.2.1",
"url": "^0.11.0",
"webpack": "^5.52.0",
"webpack-bundle-analyzer": "^4.4.2",
"webpack-cli": "^4.8.0",
"webpack-dev-server": "^4.1.0",
"webpack-manifest-plugin": "^5.0.0",
"webpack-merge": "^5.8.0",
"webpack-node-externals": "^3.0.0"
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"scripts": {
"webpack": "webpack",
"prod": "npm run webpack -- --env mode=production",
"build": "npm run prod"
},
"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"
]
}
}

View File

@ -1,18 +0,0 @@
import React from "react";
import { useAccount, useAccountId } from "./lib/data/account";
import { useInitNear, useNear } from "./lib/data/near";
import { Widget } from "./lib/components/Widget";
import { useCache } from "./lib/data/cache";
import * as utils from "./lib/data/utils";
import { CommitButton } from "./lib/components/Commit";
export {
Widget,
CommitButton,
useInitNear,
useNear,
useCache,
useAccount,
useAccountId,
utils,
};

View File

@ -1,83 +0,0 @@
const webpack = require("webpack");
const paths = require("./config/paths");
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const { merge } = require("webpack-merge");
const loadPreset = require("./config/presets/loadPreset");
const loadConfig = (mode) => require(`./config/webpack.${mode}.js`)(mode);
const nodeExternals = require("webpack-node-externals");
module.exports = function (env) {
const { mode = "production" } = env || {};
return merge(
{
mode,
entry: `${paths.srcPath}/index.js`,
output: {
path: paths.distPath,
filename: "index.js",
libraryTarget: "umd",
},
externals: [
nodeExternals(),
{
react: {
commonjs: "react",
commonjs2: "react",
amd: "react",
root: "React",
},
"react-dom": {
commonjs: "react-dom",
commonjs2: "react-dom",
amd: "react-dom",
root: "ReactDOM",
},
},
],
module: {
rules: [
{
test: /\.m?js/,
resolve: {
fullySpecified: false,
},
},
{
test: /\.js$/,
use: ["babel-loader"],
exclude: path.resolve(__dirname, "node_modules"),
},
// Images: Copy image files to build folder
{ test: /\.(?:ico|gif|png|jpg|jpeg)$/i, type: "asset/resource" },
// Fonts and SVGs: Inline files
{ test: /\.(woff(2)?|eot|ttf|otf|svg|)$/, type: "asset/inline" },
],
},
resolve: {
modules: [paths.srcPath, "node_modules"],
extensions: [".js", ".jsx", ".json"],
fallback: {
crypto: require.resolve("crypto-browserify"),
stream: require.resolve("stream-browserify"),
},
},
target: "node",
plugins: [
// new webpack.EnvironmentPlugin({
// // Configure environment variables here.
// ENVIRONMENT: "browser",
// }),
new CleanWebpackPlugin(),
new webpack.ProgressPlugin(),
new webpack.ProvidePlugin({
process: "process/browser",
Buffer: [require.resolve("buffer/"), "Buffer"],
}),
],
},
loadConfig(mode),
loadPreset(env)
);
};

View File

@ -1,693 +0,0 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import ls from "local-storage";
import prettier from "prettier";
import parserBabel from "prettier/parser-babel";
import { useHistory, useParams } from "react-router-dom";
import Editor from "@monaco-editor/react";
import {
Widget,
useCache,
useNear,
CommitButton,
useAccountId,
} from "near-social-vm";
import { Nav, OverlayTrigger, Tooltip } from "react-bootstrap";
import RenameModal from "../components/Editor/RenameModal";
import OpenModal from "../components/Editor/OpenModal";
const StorageDomain = {
page: "editor",
};
const StorageType = {
Code: "code",
Files: "files",
};
const Filetype = {
Widget: "widget",
Module: "module",
};
const LsKey = "social.near:v01:";
const EditorLayoutKey = LsKey + "editorLayout:";
const WidgetPropsKey = LsKey + "widgetProps:";
const DefaultEditorCode = "return <div>Hello World</div>;";
const Tab = {
Editor: "Editor",
Props: "Props",
Metadata: "Metadata",
Widget: "Widget",
};
const Layout = {
Tabs: "Tabs",
Split: "Split",
};
export default function EditorPage(props) {
const { widgetSrc } = useParams();
const history = useHistory();
const setWidgetSrc = props.setWidgetSrc;
const [loading, setLoading] = useState(false);
const [code, setCode] = useState(undefined);
const [path, setPath] = useState(undefined);
const [files, setFiles] = useState(undefined);
const [lastPath, setLastPath] = useState(undefined);
const [showRenameModal, setShowRenameModal] = useState(false);
const [showOpenModal, setShowOpenModal] = useState(false);
const [renderCode, setRenderCode] = useState(code);
const [widgetProps, setWidgetProps] = useState(
ls.get(WidgetPropsKey) || "{}"
);
const [parsedWidgetProps, setParsedWidgetProps] = useState({});
const [propsError, setPropsError] = useState(null);
const [metadata, setMetadata] = useState(undefined);
const near = useNear();
const cache = useCache();
const accountId = useAccountId();
const [tab, setTab] = useState(Tab.Editor);
const [layout, setLayoutState] = useState(
ls.get(EditorLayoutKey) || Layout.Tabs
);
const setLayout = useCallback(
(layout) => {
ls.set(EditorLayoutKey, layout);
setLayoutState(layout);
},
[setLayoutState]
);
useEffect(() => {
setWidgetSrc({
edit: null,
view: widgetSrc,
});
}, [widgetSrc, setWidgetSrc]);
const updateCode = useCallback(
(path, code) => {
cache.localStorageSet(
StorageDomain,
{
path,
type: StorageType.Code,
},
{
code,
time: Date.now(),
}
);
setCode(code);
},
[cache, setCode]
);
useEffect(() => {
ls.set(WidgetPropsKey, widgetProps);
try {
const parsedWidgetProps = JSON.parse(widgetProps);
setParsedWidgetProps(parsedWidgetProps);
setPropsError(null);
} catch (e) {
setParsedWidgetProps({});
setPropsError(e.message);
}
}, [widgetProps]);
const removeFromFiles = useCallback(
(path) => {
path = JSON.stringify(path);
setFiles((files) =>
files.filter((file) => JSON.stringify(file) !== path)
);
setLastPath(path);
},
[setFiles, setLastPath]
);
const addToFiles = useCallback(
(path) => {
const jpath = JSON.stringify(path);
setFiles((files) => {
const newFiles = [...files];
if (!files.find((file) => JSON.stringify(file) === jpath)) {
newFiles.push(path);
}
return newFiles;
});
setLastPath(path);
},
[setFiles, setLastPath]
);
useEffect(() => {
if (files && lastPath) {
cache.localStorageSet(
StorageDomain,
{
type: StorageType.Files,
},
{ files, lastPath }
);
}
}, [files, lastPath, cache]);
const openFile = useCallback(
(path, code) => {
setPath(path);
addToFiles(path);
setMetadata(undefined);
setRenderCode(null);
if (code !== undefined) {
updateCode(path, code);
} else {
setLoading(true);
cache
.asyncLocalStorageGet(StorageDomain, {
path,
type: StorageType.Code,
})
.then(({ code }) => {
updateCode(path, code);
})
.finally(() => {
setLoading(false);
});
}
},
[updateCode, addToFiles]
);
const toPath = useCallback((type, nameOrPath) => {
const name =
nameOrPath.indexOf("/") >= 0
? nameOrPath.split("/").slice(2).join("/")
: nameOrPath;
return { type, name };
}, []);
const loadFile = useCallback(
(nameOrPath) => {
if (!near) {
return;
}
const widgetSrc =
nameOrPath.indexOf("/") >= 0
? nameOrPath
: `${accountId}/widget/${nameOrPath}`;
const c = () => {
const code = cache.socialGet(
near,
widgetSrc,
false,
undefined,
undefined,
c
);
if (code) {
const name = widgetSrc.split("/").slice(2).join("/");
openFile(toPath(Filetype.Widget, widgetSrc), code);
}
};
c();
},
[accountId, openFile, toPath, near, cache]
);
const generateNewName = useCallback(
(type) => {
for (let i = 0; ; i++) {
const name = `Draft-${i}`;
const path = toPath(type, name);
path.unnamed = true;
const jPath = JSON.stringify(path);
if (!files?.find((file) => JSON.stringify(file) === jPath)) {
return path;
}
}
},
[toPath, files]
);
const createFile = useCallback(
(type) => {
const path = generateNewName(type);
openFile(path, DefaultEditorCode);
},
[generateNewName, openFile]
);
const renameFile = useCallback(
(newName, code) => {
const newPath = toPath(path.type, newName);
const jNewPath = JSON.stringify(newPath);
const jPath = JSON.stringify(path);
setFiles((files) => {
const newFiles = files.filter(
(file) => JSON.stringify(file) !== jNewPath
);
const i = newFiles.findIndex((file) => JSON.stringify(file) === jPath);
if (i >= 0) {
newFiles[i] = newPath;
}
return newFiles;
});
setLastPath(newPath);
setPath(newPath);
updateCode(newPath, code);
},
[path, toPath, updateCode]
);
useEffect(() => {
cache
.asyncLocalStorageGet(StorageDomain, { type: StorageType.Files })
.then((value) => {
const { files, lastPath } = value || {};
setFiles(files || []);
setLastPath(lastPath);
});
}, [cache]);
useEffect(() => {
if (!near || !files) {
return;
}
if (widgetSrc) {
if (widgetSrc === "new") {
createFile(Filetype.Widget);
} else {
loadFile(widgetSrc);
}
analytics("edit", {
props: {
widget: widgetSrc,
},
});
history.replace(`/edit/`);
} else if (path === undefined) {
if (files.length === 0) {
createFile(Filetype.Widget);
} else {
openFile(lastPath, undefined);
}
}
}, [near, createFile, lastPath, files, path, widgetSrc, openFile, loadFile]);
const reformat = useCallback(
(path, code) => {
try {
const formattedCode = prettier.format(code, {
parser: "babel",
plugins: [parserBabel],
});
updateCode(path, formattedCode);
} catch (e) {
console.log(e);
}
},
[updateCode]
);
const reformatProps = useCallback(
(props) => {
try {
const formattedProps = JSON.stringify(JSON.parse(props), null, 2);
setWidgetProps(formattedProps);
} catch (e) {
console.log(e);
}
},
[setWidgetProps]
);
const layoutClass = layout === Layout.Split ? "col-lg-6" : "";
const onLayoutChange = useCallback(
(e) => {
const layout = e.target.value;
if (layout === Layout.Split && tab === Tab.Widget) {
setTab(Tab.Editor);
}
setLayout(layout);
},
[setLayout, tab, setTab]
);
const widgetName = path?.name;
const commitButton = (
<CommitButton
className="btn btn-primary"
disabled={!widgetName}
near={near}
data={{
widget: {
[widgetName]: {
"": code,
metadata,
},
},
}}
>
Save Widget
</CommitButton>
);
const widgetPath = `${accountId}/${path?.type}/${path?.name}`;
const jpath = JSON.stringify(path);
return (
<div className="container-fluid mt-1">
<RenameModal
key={`rename-modal-${jpath}`}
show={showRenameModal}
name={path?.name}
onRename={(newName) => renameFile(newName, code)}
onHide={() => setShowRenameModal(false)}
/>
<OpenModal
show={showOpenModal}
onOpen={(newName) => loadFile(newName)}
onNew={(newName) =>
newName
? openFile(toPath(Filetype.Widget, newName), DefaultEditorCode)
: createFile(Filetype.Widget)
}
onHide={() => setShowOpenModal(false)}
/>
<div className="mb-3">
<Nav
variant="pills mb-1"
activeKey={jpath}
onSelect={(key) => openFile(JSON.parse(key))}
>
{files?.map((p, idx) => {
const jp = JSON.stringify(p);
return (
<Nav.Item key={jp}>
<Nav.Link className="text-decoration-none" eventKey={jp}>
{p.name}
<button
className={`btn btn-sm border-0 py-0 px-1 ms-1 rounded-circle ${
jp === jpath
? "btn-outline-light"
: "btn-outline-secondary"
}`}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
removeFromFiles(p);
if (jp === jpath) {
if (files.length > 1) {
openFile(files[idx - 1] || files[idx + 1]);
} else {
createFile(Filetype.Widget);
}
}
}}
>
<i className="bi bi-x"></i>
</button>
</Nav.Link>
</Nav.Item>
);
})}
<Nav.Item>
<Nav.Link
className="text-decoration-none"
onClick={() => setShowOpenModal(true)}
>
<i className="bi bi-file-earmark-plus"></i> Add
</Nav.Link>
</Nav.Item>
</Nav>
{props.widgets.editorComponentSearch && (
<div>
<Widget
src={props.widgets.editorComponentSearch}
props={useMemo(
() => ({
extraButtons: ({ widgetName, widgetPath, onHide }) => (
<OverlayTrigger
placement="auto"
overlay={
<Tooltip>
Open "{widgetName}" component in the editor
</Tooltip>
}
>
<button
className="btn btn-outline-primary"
onClick={(e) => {
e.preventDefault();
loadFile(widgetPath);
onHide && onHide();
}}
>
Open
</button>
</OverlayTrigger>
),
}),
[loadFile]
)}
/>
</div>
)}
</div>
<div className="d-flex align-content-start">
<div className="me-2">
<div
className="btn-group-vertical"
role="group"
aria-label="Layout selection"
>
<input
type="radio"
className="btn-check"
name="layout-radio"
id="layout-tabs"
autoComplete="off"
checked={layout === Layout.Tabs}
onChange={onLayoutChange}
value={Layout.Tabs}
title={"Set layout to Tabs mode"}
/>
<label className="btn btn-outline-secondary" htmlFor="layout-tabs">
<i className="bi bi-square" />
</label>
<input
type="radio"
className="btn-check"
name="layout-radio"
id="layout-split"
autoComplete="off"
checked={layout === Layout.Split}
value={Layout.Split}
title={"Set layout to Split mode"}
onChange={onLayoutChange}
/>
<label className="btn btn-outline-secondary" htmlFor="layout-split">
<i className="bi bi-layout-split" />
</label>
</div>
</div>
<div className="flex-grow-1">
<div className="row">
<div className={layoutClass}>
<ul className={`nav nav-tabs mb-2`}>
<li className="nav-item">
<button
className={`nav-link ${tab === Tab.Editor ? "active" : ""}`}
aria-current="page"
onClick={() => setTab(Tab.Editor)}
>
Editor
</button>
</li>
<li className="nav-item">
<button
className={`nav-link ${tab === Tab.Props ? "active" : ""}`}
aria-current="page"
onClick={() => setTab(Tab.Props)}
>
Props
</button>
</li>
{props.widgets.widgetMetadataEditor && (
<li className="nav-item">
<button
className={`nav-link ${
tab === Tab.Metadata ? "active" : ""
}`}
aria-current="page"
onClick={() => setTab(Tab.Metadata)}
>
Metadata
</button>
</li>
)}
{layout === Layout.Tabs && (
<li className="nav-item">
<button
className={`nav-link ${
tab === Tab.Widget ? "active" : ""
}`}
aria-current="page"
onClick={() => {
setRenderCode(code);
setTab(Tab.Widget);
}}
>
Widget Preview
</button>
</li>
)}
</ul>
<div className={`${tab === Tab.Editor ? "" : "visually-hidden"}`}>
<div className="form-control mb-3" style={{ height: "70vh" }}>
<Editor
value={code}
path={widgetPath}
defaultLanguage="javascript"
onChange={(code) => updateCode(path, code)}
wrapperProps={{
onBlur: () => reformat(path, code),
}}
/>
</div>
<div className="mb-3 d-flex gap-2 flex-wrap">
<button
className="btn btn-success"
onClick={() => {
setRenderCode(code);
if (layout === Layout.Tabs) {
setTab(Tab.Widget);
}
}}
>
Render preview
</button>
{!path?.unnamed && commitButton}
<button
className={`btn ${
path?.unnamed ? "btn-primary" : "btn-secondary"
}`}
onClick={() => {
setShowRenameModal(true);
}}
>
Rename {path?.type}
</button>
{path && accountId && (
<a
className="btn btn-outline-primary"
href={`#/${widgetPath}`}
target="_blank"
rel="noopener noreferrer"
>
Open Component in a new tab
</a>
)}
</div>
</div>
<div className={`${tab === Tab.Props ? "" : "visually-hidden"}`}>
<div className="form-control" style={{ height: "70vh" }}>
<Editor
value={widgetProps}
defaultLanguage="json"
onChange={(props) => setWidgetProps(props)}
wrapperProps={{
onBlur: () => reformatProps(widgetProps),
}}
/>
</div>
<div className=" mb-3">^^ Props for debugging (in JSON)</div>
{propsError && (
<pre className="alert alert-danger">{propsError}</pre>
)}
</div>
<div
className={`${
tab === Tab.Metadata && props.widgets.widgetMetadataEditor
? ""
: "visually-hidden"
}`}
>
<div className="mb-3">
<Widget
src={props.widgets.widgetMetadataEditor}
key={`metadata-editor-${jpath}`}
props={useMemo(
() => ({
widgetPath,
onChange: setMetadata,
}),
[widgetPath]
)}
/>
</div>
<div className="mb-3">{commitButton}</div>
</div>
</div>
<div
className={`${
tab === Tab.Widget ||
(layout === Layout.Split && tab !== Tab.Metadata)
? layoutClass
: "visually-hidden"
}`}
>
<div className="container">
<div className="row">
<div className="d-inline-block position-relative overflow-hidden">
{renderCode ? (
<Widget
key={`preview-${jpath}`}
code={renderCode}
props={parsedWidgetProps}
/>
) : (
'Click "Render preview" button to render the widget'
)}
</div>
</div>
</div>
</div>
<div
className={`${
tab === Tab.Metadata ? layoutClass : "visually-hidden"
}`}
>
<div className="container">
<div className="row">
<div className="d-inline-block position-relative overflow-hidden">
<Widget
key={`metadata-${jpath}`}
src={props.widgets.widgetMetadata}
props={useMemo(
() => ({ metadata, accountId, widgetName }),
[metadata, accountId, widgetName]
)}
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@ -1,35 +0,0 @@
import React, { useEffect, useState } from "react";
import { Widget } from "near-social-vm";
import { useParams } from "react-router-dom";
import { useQuery } from "../hooks/useQuery";
export default function EmbedPage(props) {
const { widgetSrc } = useParams();
const query = useQuery();
const [widgetProps, setWidgetProps] = useState({});
const src = widgetSrc || props.widgets.default;
useEffect(() => {
setWidgetProps(
[...query.entries()].reduce((props, [key, value]) => {
props[key] = value;
return props;
}, {})
);
}, [query]);
useEffect(() => {
analytics("embed", {
props: {
widget: src,
},
});
}, [src]);
return (
<div className="d-inline-block position-relative overflow-hidden">
<Widget key={src} src={src} props={widgetProps} />{" "}
</div>
);
}

View File

@ -1,55 +0,0 @@
import React, { useEffect, useState } from "react";
import { Widget } from "near-social-vm";
import { useParams } from "react-router-dom";
import { useQuery } from "../hooks/useQuery";
export default function ViewPage(props) {
const { widgetSrc } = useParams();
const query = useQuery();
const [widgetProps, setWidgetProps] = useState({});
const src = widgetSrc || props.widgets.default;
const setWidgetSrc = props.setWidgetSrc;
const viewSourceWidget = props.widgets.viewSource;
useEffect(() => {
setWidgetProps(Object.fromEntries([...query.entries()]));
}, [query]);
useEffect(() => {
setTimeout(() => {
setWidgetSrc(
src === viewSourceWidget && query.get("src")
? {
edit: query.get("src"),
view: null,
}
: {
edit: src,
view: src,
}
);
analytics("view", {
props: {
widget: src,
},
});
}, 1);
}, [src, query, setWidgetSrc, viewSourceWidget]);
return (
<div className="container-xl">
<div className="row">
<div
className="d-inline-block position-relative overflow-hidden"
style={{
"--body-top-padding": "24px",
paddingTop: "var(--body-top-padding)",
}}
>
<Widget key={src} src={src} props={widgetProps} />{" "}
</div>
</div>
</div>
);
}

View File

@ -1,14 +1,11 @@
const webpack = require("webpack");
const paths = require("./config/paths");
const path = require("path");
const ManifestPlugin = require("webpack-manifest-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HTMLWebpackPlugin = require("html-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { merge } = require("webpack-merge");
const loadPreset = require("./config/presets/loadPreset");
const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
const loadConfig = (mode) => require(`./config/webpack.${mode}.js`)(mode);
const nodeExternals = require("webpack-node-externals");
module.exports = function (env) {
const { mode = "production" } = env || {};
@ -18,9 +15,26 @@ module.exports = function (env) {
entry: `${paths.srcPath}/index.js`,
output: {
path: paths.distPath,
filename: "[name].bundle.js",
publicPath: "/",
filename: "index.js",
libraryTarget: "umd",
},
externals: [
nodeExternals(),
{
react: {
commonjs: "react",
commonjs2: "react",
amd: "react",
root: "React",
},
"react-dom": {
commonjs: "react-dom",
commonjs2: "react-dom",
amd: "react-dom",
root: "ReactDOM",
},
},
],
module: {
rules: [
{
@ -45,56 +59,22 @@ module.exports = function (env) {
modules: [paths.srcPath, "node_modules"],
extensions: [".js", ".jsx", ".json"],
fallback: {
// fs: false,
// path: require.resolve("path-browserify"),
// http: require.resolve("stream-http"),
// https: require.resolve("https-browserify"),
// zlib: require.resolve("browserify-zlib"),
crypto: require.resolve("crypto-browserify"),
stream: require.resolve("stream-browserify"),
},
alias:
mode === "production"
? {}
: {
react: path.join(__dirname, "node_modules/react"),
"react-dom": path.join(__dirname, "node_modules/react-dom"),
"styled-components": path.join(
__dirname,
"node_modules/styled-components"
),
},
},
target: "node",
plugins: [
new webpack.EnvironmentPlugin({
// Configure environment variables here.
ENVIRONMENT: "browser",
}),
// new webpack.EnvironmentPlugin({
// // Configure environment variables here.
// ENVIRONMENT: "browser",
// }),
new CleanWebpackPlugin(),
// Copies files from target to destination folder
new CopyWebpackPlugin({
patterns: [
{
from: paths.publicPath,
to: "assets",
globOptions: {
ignore: ["*.DS_Store"],
},
noErrorOnMissing: true,
},
],
}),
new HTMLWebpackPlugin({
template: `${paths.publicPath}/index.html`,
favicon: `${paths.publicPath}/favicon.png`,
robots: `${paths.publicPath}/robots.txt`,
}),
new webpack.ProgressPlugin(),
new webpack.ProvidePlugin({
process: "process/browser",
Buffer: [require.resolve("buffer/"), "Buffer"],
}),
new ManifestPlugin.WebpackManifestPlugin(),
],
},
loadConfig(mode),