mirror of
https://github.com/anyproto/anytype-ts.git
synced 2025-06-08 05:57:02 +09:00
merge
This commit is contained in:
commit
b966d7cc4a
30 changed files with 705 additions and 88 deletions
|
@ -41,7 +41,7 @@ let csp = [
|
|||
"style-src 'unsafe-inline' http://localhost:* file://*",
|
||||
"font-src data: file://*",
|
||||
"connect-src http://localhost:* http://127.0.0.1:* ws://localhost:* https://sentry.anytype.io https://anytype.io https://api.amplitude.com/ devtools://devtools data:",
|
||||
"script-src-elem file://* http://localhost:* https://sentry.io devtools://devtools 'unsafe-inline'",
|
||||
"script-src-elem file: http://localhost:* https://sentry.io devtools://devtools 'unsafe-inline'",
|
||||
"frame-src chrome-extension://react-developer-tools"
|
||||
];
|
||||
let autoUpdate = false;
|
||||
|
@ -501,6 +501,12 @@ function menuInit () {
|
|||
setConfig({ debugMW: !config.debugMW });
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Threads', type: 'checkbox', checked: config.debugTH,
|
||||
click: () => {
|
||||
setConfig({ debugTH: !config.debugTH });
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Analytics', type: 'checkbox', checked: config.debugAN,
|
||||
click: () => {
|
||||
|
|
|
@ -2,7 +2,13 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
</plist>
|
4
package-lock.json
generated
4
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "anytype2",
|
||||
"version": "0.16.8",
|
||||
"version": "0.16.13-alpha",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "anytype2",
|
||||
"version": "0.16.8",
|
||||
"version": "0.16.13-alpha",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@sentry/browser": "^5.27.6",
|
||||
|
|
|
@ -13,5 +13,6 @@
|
|||
"dist/img/**/*",
|
||||
"dist/anytypeHelper.exe",
|
||||
"dist/anytypeHelper",
|
||||
"dist/*.node",
|
||||
"!node_modules/**/*"
|
||||
]
|
||||
|
|
11
package.json
11
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "anytype2",
|
||||
"version": "0.16.8",
|
||||
"version": "0.16.13-alpha",
|
||||
"description": "Anytype",
|
||||
"main": "electron.js",
|
||||
"scripts": {
|
||||
|
@ -175,7 +175,9 @@
|
|||
"dist/anytypeHelper",
|
||||
"dist/anytypeHelper.exe",
|
||||
"electron/icon*",
|
||||
"build"
|
||||
"build",
|
||||
"dist/*.node",
|
||||
"node_modules/keytar/build/Release/keytar.node"
|
||||
],
|
||||
"extraResources": [],
|
||||
"files": [
|
||||
|
@ -193,6 +195,7 @@
|
|||
"dist/img/**/*",
|
||||
"dist/anytypeHelper.exe",
|
||||
"dist/anytypeHelper",
|
||||
"dist/*.node",
|
||||
"!node_modules/**/*",
|
||||
"node_modules/about-window",
|
||||
"node_modules/async",
|
||||
|
@ -234,6 +237,7 @@
|
|||
"node_modules/lockfile",
|
||||
"node_modules/lodash",
|
||||
"node_modules/lodash.isequal",
|
||||
"node_modules/lru-cache",
|
||||
"node_modules/mime-db",
|
||||
"node_modules/minimatch",
|
||||
"node_modules/mkdirp",
|
||||
|
@ -259,7 +263,8 @@
|
|||
"node_modules/unused-filename",
|
||||
"node_modules/with-open-file",
|
||||
"node_modules/wrappy",
|
||||
"node_modules/write-file-atomic"
|
||||
"node_modules/write-file-atomic",
|
||||
"node_modules/yallist"
|
||||
],
|
||||
"dmg": {
|
||||
"sign": false
|
||||
|
|
6
src/img/icon/cafe.svg
Normal file
6
src/img/icon/cafe.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg width="18" height="16" viewBox="0 0 18 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="18" height="7" rx="3.5" fill="#2C2B27"/>
|
||||
<rect y="9" width="18" height="7" rx="3.5" fill="#2C2B27"/>
|
||||
<rect x="2" y="11" width="3" height="3" rx="1.5" fill="white"/>
|
||||
<rect x="2" y="2" width="3" height="3" rx="1.5" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 344 B |
|
@ -52,8 +52,8 @@
|
|||
"authInviteLogin": { "en": "Enter" },
|
||||
"authInviteEmpty": { "en": "Invite code is empty" },
|
||||
|
||||
"authSelectTitle": {
|
||||
"en": "Organize everything",
|
||||
"authSelectTitle": {
|
||||
"en": "Organize everything",
|
||||
"ru": "Организуй все"
|
||||
},
|
||||
"authSelectLabel": { "en": "With Anytype you can write notes and documents, manage tasks, share files and save important content from the web." },
|
||||
|
@ -294,6 +294,18 @@
|
|||
"filterConditionEmpty": { "en": "is empty" },
|
||||
"filterConditionNotEmpty": { "en": "is not empty" },
|
||||
|
||||
"inputWithFileTextUrl": { "en": "Paste a link" }
|
||||
"inputWithFileTextUrl": { "en": "Paste a link" },
|
||||
|
||||
}
|
||||
"syncStatus0": { "en": "Preparing..." },
|
||||
"syncStatus1": { "en": "No connection" },
|
||||
"syncStatus2": { "en": "Syncing..." },
|
||||
"syncStatus3": { "en": "Synced" },
|
||||
"syncStatus4": { "en": "Not syncing" },
|
||||
|
||||
"tooltip0": { "en": "Initializing sync" },
|
||||
"tooltip1": { "en": "Can’t connect to Anytype node" },
|
||||
"tooltip2": { "en": "Downloading or uploading data to some node" },
|
||||
"tooltip3": { "en": "Backed up on one node at least" },
|
||||
"tooltip4": { "en": "Failed to sync, trying again..." }
|
||||
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ $colorBlue: #3e58eb;
|
|||
$colorIce: #2aa7ee;
|
||||
$colorTeal: #0fc8ba;
|
||||
$colorLime: #5dd400;
|
||||
$colorGreen: #57c600;
|
||||
|
||||
$transitionFast: all 0.2s ease-in-out;
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
|
||||
.side.right { right: 6px; z-index: 1; }
|
||||
.side.right {
|
||||
.sync { margin-right: 8px; cursor: pointer; }
|
||||
.icon { margin: 0px; }
|
||||
}
|
||||
}
|
||||
|
@ -99,6 +100,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.popup {
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
width: 64px; height: 64px; border-radius: 100%; font-weight: 500; position: relative; overflow: hidden;
|
||||
text-transform: uppercase; font-size: 22px; color: #fff; text-align: center; line-height: 62px; background-color: $colorIce;
|
||||
}
|
||||
.icon.user.c18 { width: 18px; height: 18px; line-height: 18px; font-size: 11px; }
|
||||
.icon.user.c18 { width: 18px !important; height: 18px !important; line-height: 18px; font-size: 12px; }
|
||||
.icon.user.c40 { width: 40px; height: 40px; line-height: 40px; font-size: 18px; }
|
||||
.icon.user.c96 { width: 96px; height: 96px; line-height: 96px; font-size: 44px; font-weight: 500; }
|
||||
|
||||
|
|
15
src/scss/component/sync.scss
Normal file
15
src/scss/component/sync.scss
Normal file
|
@ -0,0 +1,15 @@
|
|||
@import "~scss/_vars";
|
||||
|
||||
.sync {
|
||||
background: #f3f2ec; border-radius: 4px; @include text-small; padding: 0px 6px; display: inline-block; vertical-align: middle;
|
||||
color: $colorDarkGrey; white-space: nowrap;
|
||||
}
|
||||
.sync {
|
||||
.bullet {
|
||||
width: 8px; height: 8px; border-radius: 100%; display: inline-block; vertical-align: middle; margin: -2px 4px 0px 0px;
|
||||
background: $colorLightGrey;
|
||||
}
|
||||
.bullet.green { background: $colorGreen; }
|
||||
.bullet.orange { background: $colorOrange; }
|
||||
.bullet.red { background: $colorRed; }
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
@import "~scss/_vars";
|
||||
|
||||
.menus {
|
||||
.menu.menuHelp { width: 224px; }
|
||||
.menu.menuHelp { width: 224px; position: fixed; top: auto !important; bottom: 38px !important; }
|
||||
.menu.menuHelp {
|
||||
.item:hover { background: #f3f2ec; }
|
||||
|
||||
|
|
41
src/scss/menu/thread.scss
Normal file
41
src/scss/menu/thread.scss
Normal file
|
@ -0,0 +1,41 @@
|
|||
@import "~scss/_vars";
|
||||
|
||||
.menus {
|
||||
.menu.menuThreadList,
|
||||
.menu.menuThreadStatus { width: 320px; position: fixed; }
|
||||
|
||||
.menu.menuThreadList,
|
||||
.menu.menuThreadStatus {
|
||||
.description { color: $colorDarkGrey; display: flex; }
|
||||
.item { line-height: 22px; }
|
||||
.side { width: 50%; }
|
||||
.side.right { text-align: right; white-space: nowrap; }
|
||||
|
||||
.icon { margin-right: 9px; }
|
||||
|
||||
.red { color: $colorRed; }
|
||||
.orange { color: $colorOrange; }
|
||||
.green { color: $colorGreen; }
|
||||
}
|
||||
|
||||
.menu.menuThreadStatus {
|
||||
.content { max-height: 250px; }
|
||||
.item { padding: 5px 16px; }
|
||||
}
|
||||
|
||||
.menu.menuThreadList { width: 272px; top: 42px !important; }
|
||||
.menu.menuThreadList {
|
||||
.content { padding: 0px; }
|
||||
.item { padding: 11px 16px; border-bottom: 1px solid #eae9e0;}
|
||||
.item:hover::before { opacity: 0.15; }
|
||||
.item:last-child { border: 0px; }
|
||||
|
||||
.item {
|
||||
.icon { position: absolute; left: 16px; top: 12px; }
|
||||
.icon.user { top: 13px; }
|
||||
.icon.cafe { left: 15px; width: 20px; height: 20px; background-size: 18px 16px; background-image: url('~img/icon/cafe.svg'); }
|
||||
.info { padding-left: 28px; }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -38,6 +38,7 @@ import 'scss/component/linkPreview.scss';
|
|||
import 'scss/component/drag.scss';
|
||||
import 'scss/component/pager.scss';
|
||||
import 'scss/component/pin.scss';
|
||||
import 'scss/component/sync.scss';
|
||||
|
||||
import 'scss/page/auth.scss';
|
||||
import 'scss/page/main/index.scss';
|
||||
|
@ -84,6 +85,7 @@ import 'scss/menu/smile.scss';
|
|||
import 'scss/menu/help.scss';
|
||||
import 'scss/menu/select.scss';
|
||||
import 'scss/menu/search.scss';
|
||||
import 'scss/menu/thread.scss';
|
||||
|
||||
import 'scss/menu/block/context.scss';
|
||||
import 'scss/menu/block/common.scss';
|
||||
|
@ -310,14 +312,10 @@ class App extends React.Component<Props, State> {
|
|||
|
||||
ipcRenderer.on('keytarGet', (e: any, key: string, value: string) => {
|
||||
if ((key == 'phrase') && accountId) {
|
||||
if (!value) {
|
||||
if (phrase) {
|
||||
value = phrase;
|
||||
ipcRenderer.send('keytarSet', 'phrase', phrase);
|
||||
Storage.delete('phrase');
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
if (phrase) {
|
||||
value = phrase;
|
||||
ipcRenderer.send('keytarSet', 'phrase', phrase);
|
||||
Storage.delete('phrase');
|
||||
};
|
||||
|
||||
authStore.phraseSet(value);
|
||||
|
|
|
@ -333,6 +333,7 @@ class EditorPage extends React.Component<Props, {}> {
|
|||
C.BlockClose(id, (message: any) => {
|
||||
blockStore.blocksClear(id);
|
||||
dbStore.relationsRemove(id);
|
||||
authStore.threadRemove(id);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { Icon, Smile } from 'ts/component';
|
||||
import { Icon, Smile, Sync } from 'ts/component';
|
||||
import { I, Util, SmileUtil, DataUtil, crumbs, focus } from 'ts/lib';
|
||||
import { commonStore, blockStore } from 'ts/store';
|
||||
import { observer } from 'mobx-react';
|
||||
|
@ -26,6 +26,7 @@ class HeaderMainEdit extends React.Component<Props, {}> {
|
|||
this.onNavigation = this.onNavigation.bind(this);
|
||||
this.onAdd = this.onAdd.bind(this);
|
||||
this.onRelation = this.onRelation.bind(this);
|
||||
this.onSync = this.onSync.bind(this);
|
||||
|
||||
this.onPathOver = this.onPathOver.bind(this);
|
||||
this.onPathOut = this.onPathOut.bind(this);
|
||||
|
@ -69,8 +70,9 @@ class HeaderMainEdit extends React.Component<Props, {}> {
|
|||
</div>
|
||||
|
||||
<div className="side right">
|
||||
<Sync id="button-sync" rootId={rootId} onClick={this.onSync} />
|
||||
<Icon id="button-header-relation" tooltip="Relations" menuId="blockRelationList" className="relation" onClick={this.onRelation} />
|
||||
<Icon id="button-header-more" tooltip="Menu" menuId="blockMore" className="more" onClick={this.onMore} />
|
||||
<Icon id="button-header-more" tooltip="Menu" className="more" onClick={this.onMore} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -140,6 +142,22 @@ class HeaderMainEdit extends React.Component<Props, {}> {
|
|||
});
|
||||
};
|
||||
|
||||
onSync (e: any) {
|
||||
const { rootId } = this.props;
|
||||
|
||||
commonStore.menuOpen('threadList', {
|
||||
type: I.MenuType.Vertical,
|
||||
element: '#button-sync',
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
vertical: I.MenuDirection.Bottom,
|
||||
horizontal: I.MenuDirection.Right,
|
||||
data: {
|
||||
rootId: rootId,
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onNavigation (e: any, expanded: boolean) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
|
|
@ -51,6 +51,7 @@ import Tooltip from './util/tooltip';
|
|||
import Drag from './util/drag';
|
||||
import Marker from './util/marker';
|
||||
import Pin from './util/pin';
|
||||
import Sync from './util/sync';
|
||||
|
||||
import Icon from './util/icon';
|
||||
import IconUser from './util/iconUser';
|
||||
|
@ -87,6 +88,7 @@ export {
|
|||
Input,
|
||||
InputWithFile,
|
||||
Pin,
|
||||
Sync,
|
||||
Checkbox,
|
||||
Textarea,
|
||||
Button,
|
||||
|
|
|
@ -10,6 +10,9 @@ import MenuSmile from './smile';
|
|||
import MenuSmileSkin from './smile/skin';
|
||||
import MenuSearch from './search';
|
||||
|
||||
import MenuThreadList from './thread/list';
|
||||
import MenuThreadStatus from './thread/status';
|
||||
|
||||
import MenuBlockContext from './block/context';
|
||||
import MenuBlockStyle from './block/style';
|
||||
import MenuBlockAdd from './block/add';
|
||||
|
@ -73,6 +76,9 @@ class Menu extends React.Component<Props, {}> {
|
|||
smile: MenuSmile,
|
||||
smileSkin: MenuSmileSkin,
|
||||
search: MenuSearch,
|
||||
|
||||
threadList: MenuThreadList,
|
||||
threadStatus: MenuThreadStatus,
|
||||
|
||||
blockContext: MenuBlockContext,
|
||||
blockAction: MenuBlockAction,
|
||||
|
|
152
src/ts/component/menu/thread/list.tsx
Normal file
152
src/ts/component/menu/thread/list.tsx
Normal file
|
@ -0,0 +1,152 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { Icon, IconUser } from 'ts/component';
|
||||
import { authStore, commonStore } from 'ts/store';
|
||||
import { observer } from 'mobx-react';
|
||||
import { I, DataUtil, translate, Util } from 'ts/lib';
|
||||
|
||||
interface Props extends I.Menu {};
|
||||
|
||||
const Constant = require('json/constant.json');
|
||||
const $ = require('jquery');
|
||||
|
||||
@observer
|
||||
class MenuThreadList extends React.Component<Props, {}> {
|
||||
|
||||
timeoutMenu: number = 0;
|
||||
timeoutClose: number = 0;
|
||||
|
||||
constructor (props: any) {
|
||||
super(props);
|
||||
|
||||
this.onMouseEnter = this.onMouseEnter.bind(this);
|
||||
};
|
||||
|
||||
render () {
|
||||
const { param } = this.props;
|
||||
const { data } = param;
|
||||
const { rootId } = data;
|
||||
const thread = authStore.threadGet(rootId);
|
||||
const { accounts, cafe } = thread;
|
||||
const { status } = cafe;
|
||||
|
||||
const Item = (item: any) => (
|
||||
<div
|
||||
id={'item-' + item.id}
|
||||
className="item"
|
||||
onMouseEnter={(e: any) => { this.onMouseEnter(item.id, false); }}
|
||||
>
|
||||
<IconUser className="c18" {...item} />
|
||||
<div className="info">
|
||||
<div className="name">{item.name}</div>
|
||||
<div className="description">
|
||||
<div className="side left">Last sync</div>
|
||||
<div className="side right">
|
||||
{Util.timeAgo(Math.max(item.lastPulled, item.lastEdited))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="items">
|
||||
<div
|
||||
id="item-cafe"
|
||||
className="item"
|
||||
onMouseOver={(e: any) => { this.onMouseEnter('cafe', true); }}
|
||||
>
|
||||
<Icon className="cafe" />
|
||||
<div className="info">
|
||||
<div className="name">Backup node</div>
|
||||
<div className={[ 'description', DataUtil.threadColor(status) ].join(' ')}>
|
||||
{translate('syncStatus' + status)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{accounts.map((item: I.ThreadAccount, i: number) => (
|
||||
<Item key={i} {...item} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
const obj = $('#menuThreadList');
|
||||
const clear = () => {
|
||||
window.clearTimeout(this.timeoutClose);
|
||||
window.clearTimeout(this.timeoutMenu);
|
||||
};
|
||||
const leave = () => {
|
||||
clear();
|
||||
this.timeoutClose = window.setTimeout(() => {
|
||||
clear();
|
||||
commonStore.menuClose(this.props.id);
|
||||
commonStore.menuClose('threadStatus');
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
obj.unbind('mouseenter').on('mouseenter', () => {
|
||||
clear();
|
||||
});
|
||||
obj.unbind('mouseleave').on('mouseleave', () => {
|
||||
const status = $('#menuThreadStatus');
|
||||
if (status.length) {
|
||||
status.unbind('mouseenter').on('mouseenter', () => {
|
||||
clear();
|
||||
});
|
||||
status.unbind('mouseleave').on('mouseleave', leave);
|
||||
};
|
||||
leave();
|
||||
});
|
||||
};
|
||||
|
||||
componentWillUnmount () {
|
||||
window.clearTimeout(this.timeoutClose);
|
||||
window.clearTimeout(this.timeoutMenu);
|
||||
};
|
||||
|
||||
onMouseEnter (id: string, isCafe: boolean) {
|
||||
if (!id) {
|
||||
return;
|
||||
};
|
||||
|
||||
const { param } = this.props;
|
||||
const { data } = param;
|
||||
const { menus } = commonStore;
|
||||
const menu = menus.find((it: I.Menu) => { return it.id == 'threadStatus'; });
|
||||
|
||||
if (menu && (menu.param.data.accountId == id)) {
|
||||
return;
|
||||
};
|
||||
|
||||
const node = $(ReactDOM.findDOMNode(this));
|
||||
const item = node.find('#item-' + id);
|
||||
if (!item.length) {
|
||||
return;
|
||||
};
|
||||
|
||||
const top = item.offset().top - $(window).scrollTop();
|
||||
|
||||
window.clearTimeout(this.timeoutMenu);
|
||||
this.timeoutMenu = window.setTimeout(() => {
|
||||
commonStore.menuOpen('threadStatus', {
|
||||
element: '#item-' + id,
|
||||
type: I.MenuType.Vertical,
|
||||
vertical: I.MenuDirection.Bottom,
|
||||
horizontal: I.MenuDirection.Right,
|
||||
offsetX: 272,
|
||||
offsetY: 0,
|
||||
fixedY: top,
|
||||
data: {
|
||||
...data,
|
||||
accountId: id,
|
||||
isCafe: isCafe,
|
||||
},
|
||||
});
|
||||
}, Constant.delay.menu);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
export default MenuThreadList;
|
101
src/ts/component/menu/thread/status.tsx
Normal file
101
src/ts/component/menu/thread/status.tsx
Normal file
|
@ -0,0 +1,101 @@
|
|||
import * as React from 'react';
|
||||
import { authStore } from 'ts/store';
|
||||
import { I, Util } from 'ts/lib';
|
||||
|
||||
interface Props extends I.Menu {};
|
||||
|
||||
class MenuThreadStatus extends React.Component<Props, {}> {
|
||||
|
||||
render () {
|
||||
const { param } = this.props;
|
||||
const { data } = param;
|
||||
const { rootId, isCafe, accountId } = data;
|
||||
const thread = authStore.threadGet(rootId);
|
||||
const { cafe, accounts } = thread;
|
||||
const { status, files } = cafe;
|
||||
const account = accounts.find((it: I.ThreadAccount) => { return it.id == accountId; });
|
||||
|
||||
const Item = (item: any) => (
|
||||
<div className="item">
|
||||
<div className="name">{item.name}</div>
|
||||
{item.fields.map((field: any, i: number) => {
|
||||
if (!field.key) {
|
||||
return null;
|
||||
};
|
||||
|
||||
let content = null;
|
||||
if (field.value.toString()) {
|
||||
content = (
|
||||
<React.Fragment>
|
||||
<div className="side left">{field.key}</div>
|
||||
<div className="side right">{field.value}</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
} else {
|
||||
content = field.key;
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={i} className="description">
|
||||
{content}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
|
||||
let cafeStatus = [];
|
||||
if (cafe.lastPushSucceed) {
|
||||
cafeStatus = [
|
||||
{ key: 'This object is backed up', value: '' },
|
||||
{ key: 'Updates requested', value: cafe.lastPulled ? Util.timeAgo(cafe.lastPulled) : 'No interaction' }
|
||||
];
|
||||
} else {
|
||||
cafeStatus = [
|
||||
{ key: 'Some changes are not backed up', value: '' },
|
||||
{ key: 'Updates requested', value: cafe.lastPulled ? Util.timeAgo(cafe.lastPulled) : 'No interaction' }
|
||||
];
|
||||
};
|
||||
|
||||
const fileStatus = [
|
||||
{ key: 'Uploading', value: files.pinning + files.failed },
|
||||
{ key: 'Waiting for upload', value: files.failed },
|
||||
{ key: 'Stored', value: files.pinned },
|
||||
];
|
||||
|
||||
return isCafe ? (
|
||||
<div className="items">
|
||||
<Item name="Status" fields={cafeStatus} />
|
||||
<Item name="Files" fields={fileStatus} />
|
||||
</div>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<div className="section">
|
||||
<div className="name">My devices direct interaction</div>
|
||||
<div className="items">
|
||||
{account.devices.map((item: any, i: number) => {
|
||||
const fields = [
|
||||
// {
|
||||
// key: 'Direct interation status:',
|
||||
// value: (item.online ? 'Online' : 'Offline'),
|
||||
// },
|
||||
{
|
||||
key: 'Updates requested',
|
||||
value: (item.lastPulled ? Util.timeAgo(item.lastPulled) : 'No interaction'),
|
||||
},
|
||||
{
|
||||
key: 'Last edits received',
|
||||
value: (item.lastEdited ? Util.timeAgo(item.lastEdited) : 'No changes'),
|
||||
},
|
||||
];
|
||||
return <Item key={i} {...item} fields={fields} />;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
export default MenuThreadStatus;
|
|
@ -1,9 +1,9 @@
|
|||
import * as React from 'react';
|
||||
import { RouteComponentProps } from 'react-router';
|
||||
import { Frame, Icon, Cover, Error, Title, IconUser, HeaderAuth as Header, FooterAuth as Footer } from 'ts/component';
|
||||
import { Frame, Icon, Cover, Error, Title, IconUser, HeaderAuth as Header, FooterAuth as Footer, Loader } from 'ts/component';
|
||||
import { commonStore, authStore } from 'ts/store';
|
||||
import { observer } from 'mobx-react';
|
||||
import { Storage, I, C, Util, translate } from 'ts/lib';
|
||||
import { I, C, Util, translate } from 'ts/lib';
|
||||
|
||||
interface Props extends RouteComponentProps<any> {};
|
||||
|
||||
|
@ -29,7 +29,7 @@ class PageAccountSelect extends React.Component<Props, State> {
|
|||
const { cover } = commonStore;
|
||||
const { error } = this.state;
|
||||
const { accounts } = authStore;
|
||||
|
||||
|
||||
const Item = (item: any) => (
|
||||
<div className="item" onClick={(e) => { this.onSelect(item as I.Account); }}>
|
||||
<IconUser {...item} />
|
||||
|
@ -44,18 +44,22 @@ class PageAccountSelect extends React.Component<Props, State> {
|
|||
<Footer />
|
||||
|
||||
<Frame>
|
||||
<Title text={translate('authAccountSelectTitle')} />
|
||||
<Error text={error} />
|
||||
|
||||
<div className="list">
|
||||
{accounts.map((item: I.Account, i: number) => (
|
||||
<Item key={i} {...item} />
|
||||
))}
|
||||
<div className="item add dn" onMouseDown={this.onAdd}>
|
||||
<Icon className="plus" />
|
||||
<div className="name">{translate('authAccountSelectAdd')}</div>
|
||||
</div>
|
||||
</div>
|
||||
{!accounts.length ? <Loader /> : (
|
||||
<React.Fragment>
|
||||
<Title text={translate('authAccountSelectTitle')} />
|
||||
<Error text={error} />
|
||||
|
||||
<div className="list">
|
||||
{accounts.map((item: I.Account, i: number) => (
|
||||
<Item key={i} {...item} />
|
||||
))}
|
||||
<div className="item add dn" onMouseDown={this.onAdd}>
|
||||
<Icon className="plus" />
|
||||
<div className="name">{translate('authAccountSelectAdd')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Frame>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -173,7 +173,6 @@ class PopupShortcut extends React.Component<Props, State> {
|
|||
{ com: '↑ or Shift + Tab', name: 'Go to the previous option' },
|
||||
{ com: '←', name: 'Go to the left side of navigation. Link from page' },
|
||||
{ com: '→', name: 'Go to the right side of navigation. Link to page' },
|
||||
{ mac: '⌘ + ↑', com: 'Ctrl + ↑', name: 'Go to the start of the page' },
|
||||
{ com: 'Enter', name: 'Select option' },
|
||||
]
|
||||
},
|
||||
|
|
64
src/ts/component/util/sync.tsx
Normal file
64
src/ts/component/util/sync.tsx
Normal file
|
@ -0,0 +1,64 @@
|
|||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { I, Util, DataUtil, translate } from 'ts/lib';
|
||||
import { authStore } from 'ts/store';
|
||||
import { observer } from 'mobx-react';
|
||||
|
||||
interface Props {
|
||||
id?: string;
|
||||
className?: string;
|
||||
rootId: string;
|
||||
onClick: (e: any) => void;
|
||||
};
|
||||
|
||||
const $ = require('jquery');
|
||||
|
||||
@observer
|
||||
class Sync extends React.Component<Props, {}> {
|
||||
|
||||
public static defaultProps = {
|
||||
className: '',
|
||||
};
|
||||
|
||||
constructor (props: any) {
|
||||
super(props);
|
||||
|
||||
this.onMouseEnter = this.onMouseEnter.bind(this);
|
||||
this.onMouseLeave = this.onMouseLeave.bind(this);
|
||||
};
|
||||
|
||||
render () {
|
||||
const { id, className, rootId, onClick } = this.props;
|
||||
const thread = authStore.threadGet(rootId);
|
||||
const { summary } = thread;
|
||||
|
||||
if (!summary) {
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div id={id} className={[ 'sync', className ].join(' ')} onClick={onClick} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
<div className={[ 'bullet', DataUtil.threadColor(summary.status) ].join(' ')} />
|
||||
{translate('syncStatus' + summary.status)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
onMouseEnter (e: any) {
|
||||
const { rootId } = this.props;
|
||||
const node = $(ReactDOM.findDOMNode(this));
|
||||
const thread = authStore.threadGet(rootId);
|
||||
const { summary } = thread;
|
||||
|
||||
if (summary) {
|
||||
Util.tooltipShow(translate('tooltip' + summary.status), node, I.MenuDirection.Bottom);
|
||||
};
|
||||
};
|
||||
|
||||
onMouseLeave (e: any) {
|
||||
Util.tooltipHide();
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
export default Sync;
|
|
@ -1,19 +1,20 @@
|
|||
import { Account, Platform, DragItem, CoverType, CrumbsType, NavigationType, Option, HistoryVersion, LinkPreview } from './common';
|
||||
import { ThreadStatus, ThreadSummary, ThreadDevice, ThreadAccount, ThreadCafe, FilesStatus } from './thread';
|
||||
import { Progress, ProgressType, ProgressState } from './progress';
|
||||
import { PopupParam, Popup } from './popup';
|
||||
import { MenuType, MenuDirection, MenuParam, Menu, MenuItem } from './menu';
|
||||
import { ObjectLayout, ObjectType, ObjectTypePerObject, RelationType, Relation, SelectOption } from './object';
|
||||
|
||||
import { PageInfo, BlockType, BlockPosition, BlockSplitMode, BlockAlign, BlockComponent, Block } from './block';
|
||||
import {
|
||||
import {
|
||||
DateFormat,
|
||||
TimeFormat,
|
||||
ViewRelation,
|
||||
ViewComponent,
|
||||
ViewType,
|
||||
View,
|
||||
View,
|
||||
SortType,
|
||||
Sort,
|
||||
Sort,
|
||||
FilterOperator,
|
||||
FilterCondition,
|
||||
Filter,
|
||||
|
@ -43,17 +44,24 @@ export {
|
|||
HistoryVersion,
|
||||
LinkPreview,
|
||||
|
||||
ThreadStatus,
|
||||
ThreadSummary,
|
||||
ThreadDevice,
|
||||
ThreadAccount,
|
||||
ThreadCafe,
|
||||
FilesStatus,
|
||||
|
||||
Progress,
|
||||
ProgressType,
|
||||
ProgressState,
|
||||
|
||||
PopupParam,
|
||||
|
||||
PopupParam,
|
||||
Popup,
|
||||
|
||||
MenuType,
|
||||
MenuDirection,
|
||||
MenuParam,
|
||||
Menu,
|
||||
|
||||
MenuType,
|
||||
MenuDirection,
|
||||
MenuParam,
|
||||
Menu,
|
||||
MenuItem,
|
||||
|
||||
ObjectLayout,
|
||||
|
@ -63,14 +71,14 @@ export {
|
|||
SelectOption,
|
||||
|
||||
PageInfo,
|
||||
|
||||
|
||||
BlockType,
|
||||
BlockPosition,
|
||||
BlockSplitMode,
|
||||
BlockAlign,
|
||||
BlockComponent,
|
||||
Block,
|
||||
|
||||
|
||||
DateFormat,
|
||||
TimeFormat,
|
||||
ViewRelation,
|
||||
|
@ -79,46 +87,46 @@ export {
|
|||
View,
|
||||
RelationType,
|
||||
SortType,
|
||||
Sort,
|
||||
Sort,
|
||||
FilterOperator,
|
||||
FilterCondition,
|
||||
Filter,
|
||||
Cell,
|
||||
ContentDataview,
|
||||
BlockDataview,
|
||||
|
||||
PageType,
|
||||
|
||||
PageType,
|
||||
BlockPage,
|
||||
|
||||
LayoutStyle,
|
||||
ContentLayout,
|
||||
|
||||
LayoutStyle,
|
||||
ContentLayout,
|
||||
BlockLayout,
|
||||
|
||||
ContentIcon,
|
||||
|
||||
ContentIcon,
|
||||
BlockIcon,
|
||||
|
||||
|
||||
LinkStyle,
|
||||
ContentLink,
|
||||
ContentLink,
|
||||
BlockLink,
|
||||
|
||||
TextStyle,
|
||||
MarkType,
|
||||
TextRange,
|
||||
Mark,
|
||||
ContentText,
|
||||
|
||||
TextStyle,
|
||||
MarkType,
|
||||
TextRange,
|
||||
Mark,
|
||||
ContentText,
|
||||
BlockText,
|
||||
|
||||
DivStyle,
|
||||
|
||||
DivStyle,
|
||||
ContentDiv,
|
||||
BlockDiv,
|
||||
|
||||
FileType,
|
||||
FileState,
|
||||
File,
|
||||
|
||||
FileType,
|
||||
FileState,
|
||||
File,
|
||||
BlockFile,
|
||||
|
||||
|
||||
BookmarkType,
|
||||
ContentBookmark,
|
||||
ContentBookmark,
|
||||
BlockBookmark,
|
||||
|
||||
ContentRelation,
|
||||
|
|
48
src/ts/interface/thread.tsx
Normal file
48
src/ts/interface/thread.tsx
Normal file
|
@ -0,0 +1,48 @@
|
|||
export enum ThreadStatus {
|
||||
Unknown = 0,
|
||||
Offline = 1,
|
||||
Syncing = 2,
|
||||
Synced = 3,
|
||||
Failed = 4,
|
||||
};
|
||||
|
||||
export interface FilesStatus {
|
||||
pinning: number;
|
||||
pinned: number;
|
||||
failed: number;
|
||||
updated: number;
|
||||
}
|
||||
|
||||
export interface ThreadSummary {
|
||||
status: ThreadStatus;
|
||||
};
|
||||
|
||||
export interface ThreadDevice {
|
||||
name: string;
|
||||
online: boolean;
|
||||
lastPulled: number;
|
||||
lastEdited: number;
|
||||
};
|
||||
|
||||
export interface ThreadAccount {
|
||||
id: string;
|
||||
name: string;
|
||||
imageHash: string;
|
||||
online: false;
|
||||
lastPulled: number;
|
||||
lastEdited: number;
|
||||
devices: ThreadDevice[];
|
||||
};
|
||||
|
||||
export interface ThreadCafe {
|
||||
status: ThreadStatus;
|
||||
lastPulled: number;
|
||||
lastPushSucceed: boolean;
|
||||
files: FilesStatus;
|
||||
};
|
||||
|
||||
export interface ThreadObj {
|
||||
summary: ThreadSummary;
|
||||
cafe: ThreadCafe;
|
||||
accounts: ThreadAccount[];
|
||||
};
|
|
@ -150,6 +150,17 @@ class DataUtil {
|
|||
{ type: I.CoverType.Color, id: 'black' },
|
||||
];
|
||||
};
|
||||
|
||||
threadColor (s: I.ThreadStatus) {
|
||||
let c = '';
|
||||
switch (s) {
|
||||
case I.ThreadStatus.Failed:
|
||||
case I.ThreadStatus.Offline: c = 'red'; break;
|
||||
case I.ThreadStatus.Syncing: c = 'orange'; break;
|
||||
case I.ThreadStatus.Synced: c = 'green'; break;
|
||||
};
|
||||
return c;
|
||||
};
|
||||
|
||||
alignIcon (v: I.BlockAlign): string {
|
||||
let icon = '';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { authStore, commonStore, blockStore, dbStore } from 'ts/store';
|
||||
import { set } from 'mobx';
|
||||
import { Util, DataUtil, I, M, Decode, translate, analytics, Response, Mapper } from 'ts/lib';
|
||||
import { Util, DataUtil, I, M, Decode, translate, analytics, Response, Mapper, Storage } from 'ts/lib';
|
||||
import * as Sentry from '@sentry/browser';
|
||||
|
||||
const Service = require('lib/pb/protos/service/service_grpc_web_pb');
|
||||
|
@ -39,7 +39,6 @@ class Dispatcher {
|
|||
/// #else
|
||||
let serverAddr = remote.getGlobal('serverAddr');
|
||||
console.log('[Dispatcher] Server address: ', serverAddr);
|
||||
|
||||
this.service = new Service.ClientCommandsClient(serverAddr, null, null);
|
||||
this.listenEvents();
|
||||
/// #endif
|
||||
|
@ -74,6 +73,7 @@ class Dispatcher {
|
|||
let V = Events.Event.Message.ValueCase;
|
||||
|
||||
if (v == V.ACCOUNTSHOW) t = 'accountShow';
|
||||
if (v == V.THREADSTATUS) t = 'threadStatus';
|
||||
if (v == V.BLOCKADD) t = 'blockAdd';
|
||||
if (v == V.BLOCKDELETE) t = 'blockDelete';
|
||||
if (v == V.BLOCKSETFIELDS) t = 'blockSetFields';
|
||||
|
@ -113,7 +113,9 @@ class Dispatcher {
|
|||
const { config } = commonStore;
|
||||
const rootId = event.getContextid();
|
||||
const messages = event.getMessagesList() || [];
|
||||
const debug = config.debugMW && !skipDebug;
|
||||
const debugCommon = config.debugMW && !skipDebug;
|
||||
const debugThread = config.debugTH && !skipDebug;
|
||||
const pageId = Storage.get('pageId');
|
||||
|
||||
let globalParentIds: any = {};
|
||||
let globalChildrenIds: any = {};
|
||||
|
@ -129,10 +131,6 @@ class Dispatcher {
|
|||
let data = message[fn] ? message[fn]() : {};
|
||||
let childrenIds: string[] = [];
|
||||
|
||||
if (debug && (type != 'threadStatus')) {
|
||||
console.log('[Dispatcher.event] rootId', rootId, 'event', type, JSON.stringify(message.toObject(), null, 3));
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case 'blockSetChildrenIds':
|
||||
id = data.getId();
|
||||
|
@ -167,11 +165,18 @@ class Dispatcher {
|
|||
let block: any = null;
|
||||
let viewId: string = '';
|
||||
let view: any = null;
|
||||
let index: number = 0;
|
||||
let childrenIds: string[] = [];
|
||||
let type = this.eventType(message.getValueCase());
|
||||
let fn = 'get' + Util.ucFirst(type);
|
||||
let data = message[fn] ? message[fn]() : {};
|
||||
let log = () => { console.log('[Dispatcher.event] rootId', rootId, 'event', type, JSON.stringify(event.toObject(), null, 3)); };
|
||||
|
||||
if (debugThread && (type == 'threadStatus')) {
|
||||
log();
|
||||
} else
|
||||
if (debugCommon && (type != 'threadStatus')) {
|
||||
log();
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
|
||||
|
@ -179,6 +184,14 @@ class Dispatcher {
|
|||
authStore.accountAdd(Mapper.From.Account(data.getAccount()));
|
||||
break;
|
||||
|
||||
case 'threadStatus':
|
||||
authStore.threadSet(rootId, {
|
||||
summary: Mapper.From.ThreadSummary(data.getSummary()),
|
||||
cafe: Mapper.From.ThreadCafe(data.getCafe()),
|
||||
accounts: (data.getAccountsList() || []).map(Mapper.From.ThreadAccount),
|
||||
});
|
||||
break;
|
||||
|
||||
case 'blockShow':
|
||||
dbStore.relationsSet(rootId, (data.getRelationsList() || []).map(Mapper.From.Relation));
|
||||
dbStore.objectTypesSet((data.getObjecttypesList() || []).map(Mapper.From.ObjectType));
|
||||
|
|
|
@ -251,7 +251,52 @@ const Mapper = {
|
|||
};
|
||||
},
|
||||
|
||||
},
|
||||
ThreadSummary: (obj: any) => {
|
||||
return {
|
||||
status: Number(obj.getStatus() || I.ThreadStatus.Unknown),
|
||||
};
|
||||
},
|
||||
|
||||
ThreadCafe: (obj: any) => {
|
||||
return {
|
||||
status: Number(obj.getStatus() || I.ThreadStatus.Unknown),
|
||||
lastPulled: obj.getLastpulled(),
|
||||
lastPushSucceed: obj.getLastpushsucceed(),
|
||||
files: Mapper.From.ThreadFiles(obj.getFiles()),
|
||||
};
|
||||
},
|
||||
|
||||
ThreadFiles: (obj: any) => {
|
||||
return {
|
||||
pinning: obj.getPinning(),
|
||||
pinned: obj.getPinned(),
|
||||
failed: obj.getFailed(),
|
||||
updated: obj.getUpdated(),
|
||||
};
|
||||
},
|
||||
|
||||
ThreadDevice: (obj: any) => {
|
||||
return {
|
||||
name: obj.getName(),
|
||||
online: obj.getOnline(),
|
||||
lastPulled: obj.getLastpulled(),
|
||||
lastEdited: obj.getLastedited(),
|
||||
};
|
||||
},
|
||||
|
||||
ThreadAccount: (obj: any) => {
|
||||
return {
|
||||
id: obj.getId(),
|
||||
name: obj.getName(),
|
||||
imageHash: obj.getImagehash(),
|
||||
online: obj.getOnline(),
|
||||
lastPulled: obj.getLastpulled(),
|
||||
lastEdited: obj.getLastedited(),
|
||||
devices: (obj.getDevicesList() || []).map(Mapper.From.ThreadDevice),
|
||||
};
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
//------------------------------------------------------------
|
||||
|
||||
|
|
|
@ -367,6 +367,38 @@ class Util {
|
|||
return ret;
|
||||
});
|
||||
};
|
||||
|
||||
timeAgo (t: number): string {
|
||||
if (!t) {
|
||||
return '';
|
||||
};
|
||||
|
||||
let delta = this.time() - t;
|
||||
let d = Math.floor(delta / 86400);
|
||||
|
||||
delta -= d * 86400;
|
||||
let h = Math.floor(delta / 3600);
|
||||
|
||||
delta -= h * 3600;
|
||||
let m = Math.floor(delta / 60);
|
||||
|
||||
delta -= m * 60;
|
||||
let s = delta;
|
||||
|
||||
if (d > 0) {
|
||||
return sprintf('%d days ago', d);
|
||||
};
|
||||
if (h > 0) {
|
||||
return sprintf('%d hours ago', h);
|
||||
};
|
||||
if (m > 0) {
|
||||
return sprintf('%d minutes ago', m);
|
||||
};
|
||||
if (s > 0) {
|
||||
return sprintf('%d seconds ago', s);
|
||||
};
|
||||
return '';
|
||||
};
|
||||
|
||||
round (v: number, l: number) {
|
||||
let d = Math.pow(10, l);
|
||||
|
|
|
@ -16,17 +16,18 @@ class AuthStore {
|
|||
@observable public name: string = '';
|
||||
@observable public phrase: string = '';
|
||||
@observable public code: string = '';
|
||||
@observable public threadMap: Map<string, any> = new Map();
|
||||
|
||||
@computed
|
||||
get accounts(): I.Account[] {
|
||||
return this.accountList;
|
||||
};
|
||||
|
||||
|
||||
@computed
|
||||
get account(): I.Account {
|
||||
return this.accountItem;
|
||||
};
|
||||
|
||||
|
||||
@computed
|
||||
get path(): string {
|
||||
return this.dataPath || Storage.get('dataPath') || '';
|
||||
|
@ -67,7 +68,7 @@ class AuthStore {
|
|||
accountAdd (account: I.Account) {
|
||||
this.accountList.push(account);
|
||||
};
|
||||
|
||||
|
||||
accountClear () {
|
||||
this.accountList = [];
|
||||
};
|
||||
|
@ -79,7 +80,26 @@ class AuthStore {
|
|||
analytics.profile(account);
|
||||
Sentry.setUser({ id: account.id });
|
||||
};
|
||||
|
||||
|
||||
@action
|
||||
threadSet (rootId: string, obj: any) {
|
||||
const thread = this.threadMap.get(rootId);
|
||||
if (thread) {
|
||||
set(thread, observable(obj));
|
||||
} else {
|
||||
this.threadMap.set(rootId, observable(obj));
|
||||
};
|
||||
};
|
||||
|
||||
@action
|
||||
threadRemove (rootId: string) {
|
||||
this.threadMap.delete(rootId);
|
||||
};
|
||||
|
||||
threadGet (rootId) {
|
||||
return this.threadMap.get(rootId) || {};
|
||||
};
|
||||
|
||||
@action
|
||||
logout () {
|
||||
Storage.logout();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue