mirror of
https://github.com/irislib/iris-messenger.git
synced 2024-09-19 09:43:29 +00:00
show everything from public space in explorer
This commit is contained in:
parent
61ba1657a9
commit
546aa1dbc4
@ -5,7 +5,7 @@
|
||||
"scripts": {
|
||||
"build": "preact build --no-prerender",
|
||||
"serve": "sirv build --port 8080 --cors --single",
|
||||
"dev": "preact watch",
|
||||
"dev": "echo 'dev server will start at http://localhost:8080';preact watch",
|
||||
"lint": "eslint src",
|
||||
"test": "jest"
|
||||
},
|
||||
|
@ -9,7 +9,7 @@ const ELECTRON_GUN_URL = 'http://localhost:8767/gun';
|
||||
let maxConnectedPeers = Helpers.isElectron ? 2 : 1;
|
||||
const DEFAULT_PEERS = {};
|
||||
|
||||
if (window.location.hostname.endsWith('herokuapp.com') || window.location.host === 'localhost:5000') {
|
||||
if (window.location.hostname.endsWith('herokuapp.com') || window.location.host === 'localhost:4944') {
|
||||
DEFAULT_PEERS[window.location.origin + '/gun'] = {};
|
||||
} else {
|
||||
DEFAULT_PEERS['https://gun-rs.iris.to/gun'] = {};
|
||||
|
251
src/js/components/ExplorerNode.js
Normal file
251
src/js/components/ExplorerNode.js
Normal file
@ -0,0 +1,251 @@
|
||||
import BaseComponent from "../BaseComponent";
|
||||
import Session from "../Session";
|
||||
import Gun from "gun";
|
||||
import {html} from "htm/preact";
|
||||
|
||||
const hashRegex = /^(?:[A-Za-z0-9+/]{4}){10}(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)+$/;
|
||||
const pubKeyRegex = /^[A-Za-z0-9\-\_]{40,50}\.[A-Za-z0-9\_\-]{40,50}$/;
|
||||
const SHOW_CHILDREN_COUNT = 50;
|
||||
|
||||
const chevronDown = html`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-chevron-down" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
const chevronRight = html`
|
||||
<svg width="12" height="12" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
class ExplorerNode extends BaseComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.children = {};
|
||||
this.state = {children: {}, shownChildrenCount: SHOW_CHILDREN_COUNT};
|
||||
}
|
||||
|
||||
getNode() {
|
||||
if (this.props.path.length > 1) {
|
||||
const path = this.props.path.split('/');
|
||||
return path.slice(1).reduce((sum, current) => (current && sum.get(decodeURIComponent(current))) || sum, this.props.gun);
|
||||
}
|
||||
return this.props.gun;
|
||||
}
|
||||
|
||||
shouldComponentUpdate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.isMine = this.props.path.indexOf(`public/~${ Session.getPubKey()}`) === 0;
|
||||
this.isGroup = this.props.path.indexOf('group') === 0;
|
||||
|
||||
this.children = {};
|
||||
if (this.props.children && typeof this.props.children === "object") {
|
||||
this.children = Object.assign(this.children, this.props.children);
|
||||
}
|
||||
this.setState({children: {}, shownChildrenCount: SHOW_CHILDREN_COUNT});
|
||||
|
||||
const cb = this.sub(
|
||||
async (v, k, c, e, from) => {
|
||||
if (k === '_') { return; }
|
||||
let encryption;
|
||||
if (typeof v === 'string' && v.indexOf('SEA{') === 0) {
|
||||
try {
|
||||
const myKey = Session.getKey();
|
||||
let dec = await Gun.SEA.decrypt(v, myKey);
|
||||
if (dec === undefined) {
|
||||
if (!this.mySecret) {
|
||||
this.mySecret = await Gun.SEA.secret(myKey.epub, myKey);
|
||||
dec = await Gun.SEA.decrypt(v, this.mySecret);
|
||||
}
|
||||
}
|
||||
if (dec !== undefined) {
|
||||
v = dec;
|
||||
encryption = 'Decrypted';
|
||||
} else {
|
||||
encryption = 'Encrypted';
|
||||
}
|
||||
} catch(e) {
|
||||
null;
|
||||
}
|
||||
}
|
||||
const prev = this.children[k] || {};
|
||||
this.children[k] = Object.assign(prev, { value: v, encryption, from });
|
||||
this.setState({children: this.children});
|
||||
}
|
||||
);
|
||||
|
||||
if (this.isGroup) {
|
||||
const path = this.props.path.split('/').slice(2).join('/');
|
||||
this.props.gun.map(path, cb); // TODO: make State.group() provide the normal gun api
|
||||
} else {
|
||||
this.getNode().map().on(cb);
|
||||
}
|
||||
}
|
||||
|
||||
onChildObjectClick(e, k) {
|
||||
e.preventDefault();
|
||||
this.children[k].open = !this.children[k].open;
|
||||
this.setState({children: this.children});
|
||||
}
|
||||
|
||||
onShowMoreClick(e, k) {
|
||||
e.preventDefault();
|
||||
this.children[k].showMore = !this.children[k].showMore;
|
||||
this.setState({children: this.children});
|
||||
}
|
||||
|
||||
renderChildObject(k) {
|
||||
const path = `${this.props.path }/${ encodeURIComponent(k)}`;
|
||||
return html`
|
||||
<div class="explorer-row" style="padding-left: ${this.props.indent}em">
|
||||
<span onClick=${e => this.onChildObjectClick(e, k)}>${this.state.children[k].open ? chevronDown : chevronRight}</span>
|
||||
<a href="/explorer/${encodeURIComponent(path)}">
|
||||
<b>
|
||||
${typeof k === 'string' && k.substr(1).match(pubKeyRegex) ? html`<iris-text user=${k.substr(1)} path="profile/name"/>` : k}
|
||||
</b>
|
||||
</a>
|
||||
</div>
|
||||
${this.state.children[k].open ? html`<${ExplorerNode} gun=${this.props.gun} indent=${this.props.indent + 1} key=${path} path=${path} isGroup=${this.props.isGroup}/>` : ''}
|
||||
`;
|
||||
}
|
||||
|
||||
renderChildValue(k, v) {
|
||||
let s;
|
||||
const encryption = this.children[k].encryption;
|
||||
const from = this.children[k].from;
|
||||
const decrypted = encryption === 'Decrypted';
|
||||
const lnk = (href, text, cls) => html`<a class=${cls === undefined ? "mar-left5" : cls} href=${href}>${text}</a>`;
|
||||
const keyLinks = html`
|
||||
${typeof k === 'string' && k.match(hashRegex) ? lnk(`/post/${encodeURIComponent(k)}`, '#') : ''}
|
||||
${typeof k === 'string' && k.match(pubKeyRegex) ? lnk(`/explorer/public%2F~${encodeURIComponent(encodeURIComponent(k))}`, html`<iris-text user=${k} path="profile/name"/>`) : ''}
|
||||
`;
|
||||
if (encryption) {
|
||||
if (!decrypted) {
|
||||
s = html`<i>Encrypted value</i>`;
|
||||
} else {
|
||||
s = JSON.stringify(v);
|
||||
}
|
||||
} else {
|
||||
const pub = Session.getPubKey();
|
||||
const isMine = this.props.path.indexOf(`public/~${ pub}`) === 0;
|
||||
const path = isMine && (`${this.props.path }/${ encodeURIComponent(k)}`).replace(`public/~${ pub }/`, '');
|
||||
if (typeof v === 'string' && v.indexOf('data:image') === 0) {
|
||||
s = isMine ? html`<iris-img user=${pub} path=${path}/>` : html`<img src=${v}/>`;
|
||||
} else {
|
||||
let stringified = JSON.stringify(v);
|
||||
let showToggle;
|
||||
if (stringified.length > 100) {
|
||||
showToggle = true;
|
||||
if (!this.state.children[k].showMore) {
|
||||
stringified = stringified.slice(0, 100);
|
||||
}
|
||||
}
|
||||
|
||||
const valueLinks = html`
|
||||
${typeof v === 'string' && v.match(hashRegex) ? lnk(`/post/${encodeURIComponent(v)}`, '#') : ''}
|
||||
${typeof v === 'string' && v.match(pubKeyRegex) ? lnk(`/explorer/public%2F~${encodeURIComponent(encodeURIComponent(v))}`, html`<iris-text user=${v} path="profile/name"/>`) : ''}
|
||||
${typeof from === 'string' ? html`<small> from ${lnk(`/explorer/public%2F~${encodeURIComponent(encodeURIComponent(from))}`, html`<iris-text user=${from} path="profile/name"/>`, '')}</small>` : ''}
|
||||
`;
|
||||
|
||||
s = isMine ? html`
|
||||
<iris-text placeholder="empty" user=${pub} path=${path} editable=${true} json=${true}/>
|
||||
${valueLinks}
|
||||
` :
|
||||
html`
|
||||
<span class=${typeof v === 'string' ? '' : 'iris-non-string'}>
|
||||
${stringified}
|
||||
${showToggle ? html`
|
||||
<a onClick=${e => this.onShowMoreClick(e, k)} href="">${this.state.children[k].showMore ? 'less' : 'more'}</a>
|
||||
` : ''}
|
||||
${valueLinks}
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
}
|
||||
return html`
|
||||
<div class="explorer-row" style="padding-left: ${this.props.indent}em">
|
||||
<b class="val">${k} ${keyLinks}</b>:
|
||||
${encryption ? html`
|
||||
<span class="tooltip"><span class="tooltiptext">${encryption} value</span>
|
||||
${decrypted ? '🔓' : ''}
|
||||
</span>
|
||||
` : ''} ${s}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
onExpandClicked() {
|
||||
const expandAll = !this.state.expandAll;
|
||||
Object.keys(this.children).forEach(k => {
|
||||
this.children[k].open = expandAll;
|
||||
});
|
||||
this.setState({expandAll, children: this.children});
|
||||
}
|
||||
|
||||
onNewItemSubmit(e) {
|
||||
e.preventDefault();
|
||||
if (this.state.newItemName) {
|
||||
this.getNode().get(this.state.newItemName.trim()).put(this.state.showNewItem === 'object' ? {a:null} : '');
|
||||
this.setState({showNewItem: false, newItemName: ''});
|
||||
}
|
||||
}
|
||||
|
||||
onNewItemNameInput(e) {
|
||||
this.setState({newItemName: e.target.value.trimStart().replace(' ', ' ')});
|
||||
}
|
||||
|
||||
showNewItemClicked(type) {
|
||||
this.setState({showNewItem:type});
|
||||
setTimeout(() => document.querySelector('#newItemNameInput').focus(), 0);
|
||||
}
|
||||
|
||||
render() {
|
||||
const children = Object.keys(this.state.children).sort();
|
||||
|
||||
const renderChildren = children => {
|
||||
return children.map(k => {
|
||||
const v = this.state.children[k].value;
|
||||
if (typeof v === 'object' && v && v['_']) {
|
||||
return this.renderChildObject(k, v);
|
||||
}
|
||||
return this.renderChildValue(k, v);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
const showMoreBtn = children.length > this.state.shownChildrenCount;
|
||||
return html`
|
||||
${this.props.indent === 0 ? html`
|
||||
<div class="explorer-row" style="padding-left: ${this.props.indent}em">
|
||||
${this.props.showTools ? html`
|
||||
<p class="explorer-tools">
|
||||
<a onClick=${() => this.onExpandClicked()}>${this.state.expandAll ? 'Close all' : 'Expand all'}</a>
|
||||
<a onClick=${() => this.showNewItemClicked('object')}>New object</a>
|
||||
<a onClick=${() => this.showNewItemClicked('value')}>New value</a>
|
||||
${children.length} items
|
||||
</p>
|
||||
`: ''}
|
||||
${this.state.showNewItem ? html`
|
||||
<p>
|
||||
<form onSubmit=${(e) => this.onNewItemSubmit(e)}>
|
||||
<input id="newItemNameInput" type="text" onInput=${e => this.onNewItemNameInput(e)} value=${this.state.newItemName} placeholder="New ${this.state.showNewItem} name"/>
|
||||
<button type="submit">Create</button>
|
||||
<button onClick=${() => this.setState({showNewItem: false})}>Cancel</button>
|
||||
</form>
|
||||
</p>
|
||||
` : ''}
|
||||
</div>
|
||||
`: ''}
|
||||
${renderChildren(children.slice(0, this.state.shownChildrenCount))}
|
||||
${showMoreBtn ? html`
|
||||
<a style="padding-left: ${this.props.indent + 1}em" href="" onClick=${e => {e.preventDefault();this.setState({shownChildrenCount: this.state.shownChildrenCount + SHOW_CHILDREN_COUNT})}}>More (${children.length - this.state.shownChildrenCount})</a>
|
||||
` : ''}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export default ExplorerNode;
|
@ -1,14 +1,7 @@
|
||||
import { html } from 'htm/preact';
|
||||
import State from '../State.js';
|
||||
import Session from '../Session.js';
|
||||
import View from './View.js';
|
||||
import Gun from 'gun';
|
||||
import BaseComponent from "../BaseComponent";
|
||||
|
||||
const hashRegex = /^(?:[A-Za-z0-9+/]{4}){10}(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)+$/;
|
||||
const pubKeyRegex = /^[A-Za-z0-9\-\_]{40,50}\.[A-Za-z0-9\_\-]{40,50}$/;
|
||||
|
||||
const SHOW_CHILDREN_COUNT = 50;
|
||||
import ExplorerNode from '../components/ExplorerNode';
|
||||
|
||||
const chevronDown = html`
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-chevron-down" viewBox="0 0 16 16">
|
||||
@ -67,16 +60,7 @@ class Explorer extends View {
|
||||
<div class="explorer-row">
|
||||
${chevronDown} Public (synced with peers)
|
||||
</div>
|
||||
<div class="explorer-row" style="padding-left: 1em">
|
||||
${chevronDown} Users
|
||||
</div>
|
||||
<div class="explorer-row" style="padding-left: 2em">
|
||||
${chevronDown} <a href="/explorer/public%2F~${encodeURIComponent(Session.getPubKey())}">${Session.getPubKey()}</a>
|
||||
</div>
|
||||
<${ExplorerNode} indent=${3} gun=${State.public} key='public/~${Session.getPubKey()}' path='public/~${Session.getPubKey()}'/>
|
||||
<div class="explorer-row" style="padding-left: 1em">
|
||||
${chevronRight} <a href="/explorer/public%2F%23">#</a> (content-addressed values, such as public posts)
|
||||
</div>
|
||||
<${ExplorerNode} indent=${1} gun=${State.public} key='public' path='public' children=${{'#':{value:{_:1}}}}/>
|
||||
<br/><br/>
|
||||
<div class="explorer-row">
|
||||
${chevronDown} Local (only stored on your device)
|
||||
@ -96,225 +80,4 @@ class Explorer extends View {
|
||||
}
|
||||
}
|
||||
|
||||
class ExplorerNode extends BaseComponent {
|
||||
constructor() {
|
||||
super();
|
||||
this.children = {};
|
||||
this.state = {children: {}, shownChildrenCount: SHOW_CHILDREN_COUNT};
|
||||
}
|
||||
|
||||
getNode() {
|
||||
if (this.props.path.length > 1) {
|
||||
const path = this.props.path.split('/');
|
||||
return path.slice(1).reduce((sum, current) => (current && sum.get(decodeURIComponent(current))) || sum, this.props.gun);
|
||||
}
|
||||
return this.props.gun;
|
||||
}
|
||||
|
||||
shouldComponentUpdate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.isMine = this.props.path.indexOf(`public/~${ Session.getPubKey()}`) === 0;
|
||||
this.isGroup = this.props.path.indexOf('group') === 0;
|
||||
|
||||
this.children = {};
|
||||
this.setState({children: {}, shownChildrenCount: SHOW_CHILDREN_COUNT});
|
||||
|
||||
const cb = this.sub(
|
||||
async (v, k, c, e, from) => {
|
||||
if (k === '_') { return; }
|
||||
let encryption;
|
||||
if (typeof v === 'string' && v.indexOf('SEA{') === 0) {
|
||||
try {
|
||||
const myKey = Session.getKey();
|
||||
let dec = await Gun.SEA.decrypt(v, myKey);
|
||||
if (dec === undefined) {
|
||||
if (!this.mySecret) {
|
||||
this.mySecret = await Gun.SEA.secret(myKey.epub, myKey);
|
||||
dec = await Gun.SEA.decrypt(v, this.mySecret);
|
||||
}
|
||||
}
|
||||
if (dec !== undefined) {
|
||||
v = dec;
|
||||
encryption = 'Decrypted';
|
||||
} else {
|
||||
encryption = 'Encrypted';
|
||||
}
|
||||
} catch(e) {
|
||||
null;
|
||||
}
|
||||
}
|
||||
const prev = this.children[k] || {};
|
||||
this.children[k] = Object.assign(prev, { value: v, encryption, from });
|
||||
this.setState({children: this.children});
|
||||
}
|
||||
);
|
||||
|
||||
if (this.isGroup) {
|
||||
const path = this.props.path.split('/').slice(2).join('/');
|
||||
this.props.gun.map(path, cb); // TODO: make State.group() provide the normal gun api
|
||||
} else {
|
||||
this.getNode().map().on(cb);
|
||||
}
|
||||
}
|
||||
|
||||
onChildObjectClick(e, k) {
|
||||
e.preventDefault();
|
||||
this.children[k].open = !this.children[k].open;
|
||||
this.setState({children: this.children});
|
||||
}
|
||||
|
||||
onShowMoreClick(e, k) {
|
||||
e.preventDefault();
|
||||
this.children[k].showMore = !this.children[k].showMore;
|
||||
this.setState({children: this.children});
|
||||
}
|
||||
|
||||
renderChildObject(k) {
|
||||
const path = `${this.props.path }/${ encodeURIComponent(k)}`;
|
||||
return html`
|
||||
<div class="explorer-row" style="padding-left: ${this.props.indent}em">
|
||||
<span onClick=${e => this.onChildObjectClick(e, k)}>${this.state.children[k].open ? chevronDown : chevronRight}</span>
|
||||
<a href="/explorer/${encodeURIComponent(path)}"><b>${k}</b></a>
|
||||
</div>
|
||||
${this.state.children[k].open ? html`<${ExplorerNode} gun=${this.props.gun} indent=${this.props.indent + 1} key=${path} path=${path} isGroup=${this.props.isGroup}/>` : ''}
|
||||
`;
|
||||
}
|
||||
|
||||
renderChildValue(k, v) {
|
||||
let s;
|
||||
const encryption = this.children[k].encryption;
|
||||
const from = this.children[k].from;
|
||||
const decrypted = encryption === 'Decrypted';
|
||||
const lnk = (href, text, cls) => html`<a class=${cls === undefined ? "mar-left5" : cls} href=${href}>${text}</a>`;
|
||||
const keyLinks = html`
|
||||
${typeof k === 'string' && k.match(hashRegex) ? lnk(`/post/${encodeURIComponent(k)}`, '#') : ''}
|
||||
${typeof k === 'string' && k.match(pubKeyRegex) ? lnk(`/explorer/public%2F~${encodeURIComponent(encodeURIComponent(k))}`, html`<iris-text user=${k} path="profile/name"/>`) : ''}
|
||||
`;
|
||||
if (encryption) {
|
||||
if (!decrypted) {
|
||||
s = html`<i>Encrypted value</i>`;
|
||||
} else {
|
||||
s = JSON.stringify(v);
|
||||
}
|
||||
} else {
|
||||
const pub = Session.getPubKey();
|
||||
const isMine = this.props.path.indexOf(`public/~${ pub}`) === 0;
|
||||
const path = isMine && (`${this.props.path }/${ encodeURIComponent(k)}`).replace(`public/~${ pub }/`, '');
|
||||
if (typeof v === 'string' && v.indexOf('data:image') === 0) {
|
||||
s = isMine ? html`<iris-img user=${pub} path=${path}/>` : html`<img src=${v}/>`;
|
||||
} else {
|
||||
let stringified = JSON.stringify(v);
|
||||
let showToggle;
|
||||
if (stringified.length > 100) {
|
||||
showToggle = true;
|
||||
if (!this.state.children[k].showMore) {
|
||||
stringified = stringified.slice(0, 100);
|
||||
}
|
||||
}
|
||||
|
||||
const valueLinks = html`
|
||||
${typeof v === 'string' && v.match(hashRegex) ? lnk(`/post/${encodeURIComponent(v)}`, '#') : ''}
|
||||
${typeof v === 'string' && v.match(pubKeyRegex) ? lnk(`/explorer/public%2F~${encodeURIComponent(encodeURIComponent(v))}`, html`<iris-text user=${v} path="profile/name"/>`) : ''}
|
||||
${typeof from === 'string' ? html`<small> from ${lnk(`/explorer/public%2F~${encodeURIComponent(encodeURIComponent(from))}`, html`<iris-text user=${from} path="profile/name"/>`, '')}</small>` : ''}
|
||||
`;
|
||||
|
||||
s = isMine ? html`
|
||||
<iris-text placeholder="empty" user=${pub} path=${path} editable=${true} json=${true}/>
|
||||
${valueLinks}
|
||||
` :
|
||||
html`
|
||||
<span class=${typeof v === 'string' ? '' : 'iris-non-string'}>
|
||||
${stringified}
|
||||
${showToggle ? html`
|
||||
<a onClick=${e => this.onShowMoreClick(e, k)} href="">${this.state.children[k].showMore ? 'less' : 'more'}</a>
|
||||
` : ''}
|
||||
${valueLinks}
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
}
|
||||
return html`
|
||||
<div class="explorer-row" style="padding-left: ${this.props.indent}em">
|
||||
<b class="val">${k} ${keyLinks}</b>:
|
||||
${encryption ? html`
|
||||
<span class="tooltip"><span class="tooltiptext">${encryption} value</span>
|
||||
${decrypted ? '🔓' : ''}
|
||||
</span>
|
||||
` : ''} ${s}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
onExpandClicked() {
|
||||
const expandAll = !this.state.expandAll;
|
||||
Object.keys(this.children).forEach(k => {
|
||||
this.children[k].open = expandAll;
|
||||
});
|
||||
this.setState({expandAll, children: this.children});
|
||||
}
|
||||
|
||||
onNewItemSubmit(e) {
|
||||
e.preventDefault();
|
||||
if (this.state.newItemName) {
|
||||
this.getNode().get(this.state.newItemName.trim()).put(this.state.showNewItem === 'object' ? {a:null} : '');
|
||||
this.setState({showNewItem: false, newItemName: ''});
|
||||
}
|
||||
}
|
||||
|
||||
onNewItemNameInput(e) {
|
||||
this.setState({newItemName: e.target.value.trimStart().replace(' ', ' ')});
|
||||
}
|
||||
|
||||
showNewItemClicked(type) {
|
||||
this.setState({showNewItem:type});
|
||||
setTimeout(() => document.querySelector('#newItemNameInput').focus(), 0);
|
||||
}
|
||||
|
||||
render() {
|
||||
const children = Object.keys(this.state.children).sort();
|
||||
|
||||
const renderChildren = children => {
|
||||
return children.map(k => {
|
||||
const v = this.state.children[k].value;
|
||||
if (typeof v === 'object' && v && v['_']) {
|
||||
return this.renderChildObject(k, v);
|
||||
}
|
||||
return this.renderChildValue(k, v);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
const showMoreBtn = children.length > this.state.shownChildrenCount;
|
||||
return html`
|
||||
${this.props.indent === 0 ? html`
|
||||
<div class="explorer-row" style="padding-left: ${this.props.indent}em">
|
||||
${this.props.showTools ? html`
|
||||
<p class="explorer-tools">
|
||||
<a onClick=${() => this.onExpandClicked()}>${this.state.expandAll ? 'Close all' : 'Expand all'}</a>
|
||||
<a onClick=${() => this.showNewItemClicked('object')}>New object</a>
|
||||
<a onClick=${() => this.showNewItemClicked('value')}>New value</a>
|
||||
</p>
|
||||
`: ''}
|
||||
${this.state.showNewItem ? html`
|
||||
<p>
|
||||
<form onSubmit=${(e) => this.onNewItemSubmit(e)}>
|
||||
<input id="newItemNameInput" type="text" onInput=${e => this.onNewItemNameInput(e)} value=${this.state.newItemName} placeholder="New ${this.state.showNewItem} name"/>
|
||||
<button type="submit">Create</button>
|
||||
<button onClick=${() => this.setState({showNewItem: false})}>Cancel</button>
|
||||
</form>
|
||||
</p>
|
||||
` : ''}
|
||||
</div>
|
||||
`: ''}
|
||||
${renderChildren(children.slice(0, this.state.shownChildrenCount))}
|
||||
${showMoreBtn ? html`
|
||||
<a style="padding-left: ${this.props.indent + 1}em" href="" onClick=${e => {e.preventDefault();this.setState({shownChildrenCount: this.state.shownChildrenCount + SHOW_CHILDREN_COUNT})}}>More (${children.length - this.state.shownChildrenCount})</a>
|
||||
` : ''}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
export default Explorer;
|
||||
|
Loading…
Reference in New Issue
Block a user