1
0
Fork 0
mirror of https://github.com/anyproto/anytype-ts.git synced 2025-06-08 05:57:02 +09:00
This commit is contained in:
Andrew Simachev 2020-12-07 12:06:21 +03:00
commit b966d7cc4a
30 changed files with 705 additions and 88 deletions

View file

@ -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: () => {

View file

@ -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
View file

@ -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",

View file

@ -13,5 +13,6 @@
"dist/img/**/*",
"dist/anytypeHelper.exe",
"dist/anytypeHelper",
"dist/*.node",
"!node_modules/**/*"
]

View file

@ -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
View 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

View file

@ -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": "Cant 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..." }
}

View file

@ -10,6 +10,7 @@ $colorBlue: #3e58eb;
$colorIce: #2aa7ee;
$colorTeal: #0fc8ba;
$colorLime: #5dd400;
$colorGreen: #57c600;
$transitionFast: all 0.2s ease-in-out;

View file

@ -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 {

View file

@ -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; }

View 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; }
}

View file

@ -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
View 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; }
}
}
}

View file

@ -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);

View file

@ -333,6 +333,7 @@ class EditorPage extends React.Component<Props, {}> {
C.BlockClose(id, (message: any) => {
blockStore.blocksClear(id);
dbStore.relationsRemove(id);
authStore.threadRemove(id);
});
};

View file

@ -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();

View file

@ -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,

View file

@ -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,

View 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;

View 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;

View file

@ -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>
);

View file

@ -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' },
]
},

View 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;

View file

@ -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,

View 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[];
};

View file

@ -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 = '';

View file

@ -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));

View file

@ -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),
};
},
},
//------------------------------------------------------------

View file

@ -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);

View file

@ -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();