diff --git a/.husky/pre-commit b/.husky/pre-commit index 7d1e3f139d..17de0888d5 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -8,5 +8,5 @@ npx lint-staged --concurrent false gitleaks protect --verbose --redact --staged # Checking dependencies' licenses -npx license-checker --production --json --out licenses.json -node check-licenses.js \ No newline at end of file +#npx license-checker --production --json --out licenses.json +#node check-licenses.js \ No newline at end of file diff --git a/src/json/text.json b/src/json/text.json index 130befc619..168a6cf9ef 100644 --- a/src/json/text.json +++ b/src/json/text.json @@ -380,6 +380,11 @@ "pageMainBlockEmpty": "Block not found", "pageMainRelationObjectsCreated": "%s created", + "pageMainImportTitle": "Downloading manifest", + + "pageMainInviteTitle": "Downloading invite", + "pageMainInviteError": "Incorrect invite data", + "editorControlIcon": "Icon", "editorControlCover": "Cover", "editorControlLayout": "Layout", @@ -728,9 +733,7 @@ "popupSettingsOnboardingNetworkTitle": "Self-hosted Configuration", "popupSpaceJoinRequestTitle": "Join a space", - "popupSpaceJoinRequestTextPart1": "You’ve been invited to join ", - "popupSpaceJoinRequestTextPart2": " space, created by ", - "popupSpaceJoinRequestTextPart3": ". Send a request so space owner can let you in.", + "popupSpaceJoinRequestText": "You've been invited to join %s space, created by %s. Send a request so space owner can let you in.", "popupSpaceJoinRequestMessagePlaceholder": "Leave a private comment for a space owner", "popupSpaceJoinRequestRequestToJoin": "Request to join", "popupSpaceJoinRequestNote": "Once the space owner approves your request, you'll join the space with the access rights owner determined.", @@ -1457,6 +1460,7 @@ "spaceType0": "Private Space", "spaceType1": "Personal Space", + "spaceType2": "Shared Space", "spaceLast": "Last opened Object", "spaceExisting": "Existing Object", diff --git a/src/scss/page/common.scss b/src/scss/page/common.scss index 99ab0e3a7e..f71028bdac 100644 --- a/src/scss/page/common.scss +++ b/src/scss/page/common.scss @@ -14,4 +14,5 @@ @import "./main/navigation.scss"; @import "./main/block.scss"; @import "./main/empty.scss"; -@import "./main/import.scss"; \ No newline at end of file +@import "./main/import.scss"; +@import "./main/invite.scss"; \ No newline at end of file diff --git a/src/scss/page/main/invite.scss b/src/scss/page/main/invite.scss new file mode 100644 index 0000000000..85ef3d1d27 --- /dev/null +++ b/src/scss/page/main/invite.scss @@ -0,0 +1,10 @@ +@import "~scss/_vars"; + +.pageMainInvite { user-select: none; } +.pageMainInvite { + .wrapper { width: 704px; margin: 0px auto; } + .wrapper { + .frame { position: absolute; text-align: center; } + .title { @include text-header3; } + } +} \ No newline at end of file diff --git a/src/ts/component/page/index.tsx b/src/ts/component/page/index.tsx index 00a9728bf9..979ba30d4d 100644 --- a/src/ts/component/page/index.tsx +++ b/src/ts/component/page/index.tsx @@ -30,6 +30,7 @@ import PageMainCreate from './main/create'; import PageMainArchive from './main/archive'; import PageMainBlock from './main/block'; import PageMainImport from './main/import'; +import PageMainInvite from './main/invite'; const Components = { 'index/index': PageAuthSelect, @@ -57,6 +58,7 @@ const Components = { 'main/archive': PageMainArchive, 'main/block': PageMainBlock, 'main/import': PageMainImport, + 'main/invite': PageMainInvite, }; const Page = observer(class Page extends React.Component { diff --git a/src/ts/component/page/main/import.tsx b/src/ts/component/page/main/import.tsx index 07486bf4f3..a3c5c9b45f 100644 --- a/src/ts/component/page/main/import.tsx +++ b/src/ts/component/page/main/import.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { Loader, Title, Error, Frame, Button } from 'Component'; import { I, C, UtilCommon, UtilRouter, UtilObject, keyboard, translate } from 'Lib'; import { popupStore } from 'Store'; +import Constant from 'json/constant.json'; interface State { error: string; @@ -23,7 +24,7 @@ class PageMainImport extends React.Component { className="wrapper" > - + <Title text={translate('pageMainImportTitle')} /> <Loader /> <Error text={error} /> @@ -46,13 +47,15 @@ class PageMainImport extends React.Component<I.PageComponent, State> { this.setState({ error: message.error.description }); } else { UtilObject.openHome('route'); - popupStore.open('usecase', { data: { object: message.info } }); + window.setTimeout(() => popupStore.open('usecase', { data: { object: message.info } }), Constant.delay.popup); }; }); + + this.resize(); }; componentDidUpdate (): void { - $(window).trigger('resize'); + this.resize(); }; getSearch () { diff --git a/src/ts/component/page/main/invite.tsx b/src/ts/component/page/main/invite.tsx new file mode 100644 index 0000000000..71da103d55 --- /dev/null +++ b/src/ts/component/page/main/invite.tsx @@ -0,0 +1,80 @@ +import * as React from 'react'; +import { Loader, Title, Error, Frame, Button } from 'Component'; +import { I, UtilCommon, UtilRouter, UtilObject, keyboard, translate } from 'Lib'; +import { popupStore } from 'Store'; +import Constant from 'json/constant.json'; + +interface State { + error: string; +}; + +class PageMainImport extends React.Component<I.PageComponent, State> { + + state = { + error: '', + }; + node = null; + + render () { + const { error } = this.state; + + return ( + <div + ref={ref => this.node = ref} + className="wrapper" + > + <Frame> + <Title text={translate('pageMainInviteTitle')} /> + <Loader /> + + <Error text={error} /> + + {error ? ( + <div className="buttons"> + <Button text={translate('commonBack')} className="c28" onClick={() => keyboard.onBack()} /> + </div> + ) : ''} + </Frame> + </div> + ); + }; + + componentDidMount (): void { + const data = this.getSearch(); + + data.key = decodeURIComponent(data.key); + + if (!data.cid || !data.key) { + this.setState({ error: translate('pageMainInviteError') }); + } else { + UtilObject.openHome('route'); + window.setTimeout(() => popupStore.open('spaceJoinRequest', { data }), Constant.delay.popup); + }; + this.resize(); + }; + + componentDidUpdate (): void { + this.resize(); + }; + + getSearch () { + return UtilCommon.searchParam(UtilRouter.history.location.search); + }; + + resize () { + const { isPopup } = this.props; + const win = $(window); + const obj = UtilCommon.getPageContainer(isPopup); + const node = $(this.node); + const wrapper = obj.find('.wrapper'); + const oh = obj.height(); + const header = node.find('#header'); + const hh = header.height(); + const wh = isPopup ? oh - hh : win.height(); + + wrapper.css({ height: wh, paddingTop: isPopup ? 0 : hh }); + }; + +}; + +export default PageMainImport; \ No newline at end of file diff --git a/src/ts/component/popup/page/settings/space/index.tsx b/src/ts/component/popup/page/settings/space/index.tsx index 450300891f..de4a2ab4a5 100644 --- a/src/ts/component/popup/page/settings/space/index.tsx +++ b/src/ts/component/popup/page/settings/space/index.tsx @@ -38,6 +38,7 @@ const PopupSettingsSpaceIndex = observer(class PopupSettingsSpaceIndex extends R const space = UtilObject.getSpaceview(); const home = UtilObject.getSpaceDashboard(); const type = dbStore.getTypeById(commonStore.type); + const canShare = space.spaceType == I.SpaceType.Private; const usageCn = [ 'item' ]; const canDelete = space.targetSpaceId != accountSpaceId; @@ -45,6 +46,7 @@ const PopupSettingsSpaceIndex = observer(class PopupSettingsSpaceIndex extends R let bytesUsed = 0; let extend = null; let createdDate = null; + let button = null; const progressSegments = (spaces || []).map(space => { const object: any = commonStore.spaceStorage.spaces.find(it => it.spaceId == space.targetSpaceId) || {}; @@ -60,6 +62,10 @@ const PopupSettingsSpaceIndex = observer(class PopupSettingsSpaceIndex extends R extend = <Label text={translate(`popupSettingsSpaceIndexRemoteStorageExtend`)} onClick={this.onExtend} className="extend" />; }; + if (canShare) { + button = <Button className="c36" onClick={() => onPage('spaceShare')} text={translate('popupSettingsSpaceIndexShare')} />; + }; + // old accounts don't have space creation date if (space.createdDate) { createdDate = ( @@ -76,7 +82,6 @@ const PopupSettingsSpaceIndex = observer(class PopupSettingsSpaceIndex extends R return ( <React.Fragment> - <div className="spaceHeader"> <div className="sides"> <div className="side left"> @@ -114,7 +119,7 @@ const PopupSettingsSpaceIndex = observer(class PopupSettingsSpaceIndex extends R </div> </div> <div className="side right"> - <Button className="c36" onClick={() => onPage('spaceShare')} text={translate('popupSettingsSpaceIndexShare')} /> + {button} </div> </div> </div> diff --git a/src/ts/component/popup/page/settings/space/share.tsx b/src/ts/component/popup/page/settings/space/share.tsx index 9b3582797b..242bca97fe 100644 --- a/src/ts/component/popup/page/settings/space/share.tsx +++ b/src/ts/component/popup/page/settings/space/share.tsx @@ -1,34 +1,41 @@ import * as React from 'react'; import $ from 'jquery'; import { Title, Label, Icon, Input, Button, IconObject, ObjectName, Select, Tag } from 'Component'; -import { I, translate, UtilCommon, UtilData } from 'Lib'; +import { I, C, translate, UtilCommon, UtilData } from 'Lib'; import { observer } from 'mobx-react'; -import { detailStore, popupStore } from 'Store'; +import { detailStore, popupStore, commonStore } from 'Store'; import { AutoSizer, CellMeasurer, CellMeasurerCache, List, InfiniteLoader } from 'react-virtualized'; import Head from '../head'; +import Constant from 'json/constant.json'; + +interface State { + error: string; +}; const HEIGHT = 64; const LIMIT = 3; const MEMBER_LIMIT = 10; -const PopupSettingsSpaceShare = observer(class PopupSettingsSpaceShare extends React.Component<I.PopupSettings> { - - inviteLink: string = 'https://anytype.io/ibafyreifibafyreiffhfg6rxuerttufhfg6rxuerttu'; - membersLimit: number = 7; +const PopupSettingsSpaceShare = observer(class PopupSettingsSpaceShare extends React.Component<I.PopupSettings, State> { + cid = ''; + key = ''; node: any = null; team: any[] = []; cache: any = null; top = 0; - filter = ''; - refFilter: any = null; + refInput = null; refList: any = null; + state = { + error: '', + }; constructor (props: I.PopupSettings) { super(props); this.onScroll = this.onScroll.bind(this); - this.onInviteCopy = this.onInviteCopy.bind(this); + this.onCopy = this.onCopy.bind(this); + this.onGenerate = this.onGenerate.bind(this); this.onStopSharing = this.onStopSharing.bind(this); }; @@ -114,9 +121,9 @@ const PopupSettingsSpaceShare = observer(class PopupSettingsSpaceShare extends R <Label text={translate('popupSettingsSpaceShareInviteLinkLabel')} /> <div className="inviteLinkWrapper"> - <Input readonly={true} value={this.inviteLink} /> - <Button onClick={this.onInviteCopy} className="c40" color="black" text={translate('commonCopyLink')} /> - <Icon id="refreshInviteLink" className="refresh" onClick={this.onInviteRefresh} /> + <Input ref={ref => this.refInput = ref} readonly={true} /> + <Button onClick={this.onCopy} className="c40" color="black" text={translate('commonCopyLink')} /> + <Icon id="generate" className="refresh" onClick={() => this.onGenerate(false)} /> </div> <div className="invitesLimit"> @@ -169,6 +176,7 @@ const PopupSettingsSpaceShare = observer(class PopupSettingsSpaceShare extends R componentDidMount() { this.load(); + this.onGenerate(true); }; componentDidUpdate() { @@ -212,18 +220,41 @@ const PopupSettingsSpaceShare = observer(class PopupSettingsSpaceShare extends R }); }; - onInviteCopy () { - UtilCommon.copyToast(translate('commonLink'), this.inviteLink); + getLink () { + return `${Constant.protocol}://main/invite/?cid=${this.cid}&key=${encodeURIComponent(this.key)}` }; - onInviteRefresh () { - $('#refreshInviteLink').addClass('loading'); + onCopy () { + UtilCommon.copyToast(translate('commonLink'), this.getLink()); + }; - // refresh logic goes here + onGenerate (auto: boolean) { + const { space } = commonStore; + const node = $(this.node); + const button = node.find('#generate'); - window.setTimeout(() => { - $('#refreshInviteLink').removeClass('loading'); - }, 2000); + if (!auto) { + button.addClass('loading'); + }; + + C.SpaceInviteGenerate(space, (message: any) => { + if (!auto) { + button.removeClass('loading'); + }; + + if (message.error.code) { + this.setState({ error: message.error.description }); + return; + }; + + this.cid = message.inviteCid; + this.key = message.inviteKey; + this.refInput.setValue(this.getLink()); + + if (!auto) { + this.onCopy(); + }; + }); }; onStopSharing () { diff --git a/src/ts/component/popup/space/joinRequest.tsx b/src/ts/component/popup/space/joinRequest.tsx index 5fb4f9250c..54040b6541 100644 --- a/src/ts/component/popup/space/joinRequest.tsx +++ b/src/ts/component/popup/space/joinRequest.tsx @@ -1,12 +1,21 @@ import * as React from 'react'; -import { Title, Icon, Label, Button, Textarea, ObjectName, IconObject } from 'Component'; -import { I, translate, UtilCommon, UtilObject } from 'Lib'; +import { Title, Icon, Label, Button, Textarea, ObjectName, IconObject, Error } from 'Component'; +import { I, C, translate, UtilCommon, UtilObject } from 'Lib'; import { observer } from 'mobx-react'; import { authStore, dbStore, detailStore } from 'Store'; -const PopupSpaceJoinRequest = observer(class PopupSpaceJoinRequest extends React.Component<I.Popup> { +interface State { + error: string; +}; - message: string = ''; +const PopupSpaceJoinRequest = observer(class PopupSpaceJoinRequest extends React.Component<I.Popup, State> { + + refMessage = null; + state = { + error: '', + }; + spaceName = ''; + creatorName = ''; constructor (props: I.Popup) { super(props); @@ -15,43 +24,50 @@ const PopupSpaceJoinRequest = observer(class PopupSpaceJoinRequest extends React }; render() { - const space = UtilObject.getSpaceview(); - const owner = { name: 'Owner Name', layout: I.ObjectLayout.Human }; // mock, to be replaced with space owner - - const Profile = (item: any) => { - return ( - <div className="profileItem"> - <IconObject object={item} size={16} /> - <ObjectName object={item} /> - </div> - ); - }; + const { error } = this.state; return ( <React.Fragment> <Title text={translate('popupSpaceJoinRequestTitle')} /> - <div className="iconWrapper"><Icon /></div> - <div className="invitation"> - {translate('popupSpaceJoinRequestTextPart1')} - <Profile {...space} /> - {translate('popupSpaceJoinRequestTextPart2')} - <Profile {...owner} /> - {translate('popupSpaceJoinRequestTextPart3')} + + <div className="iconWrapper"> + <Icon /> </div> - <Textarea onKeyUp={(e, v) => this.message = v} placeholder={translate('popupSpaceJoinRequestMessagePlaceholder')} /> + + <Label className="invitation" text={UtilCommon.sprintf(translate('popupSpaceJoinRequestText'), this.spaceName, this.creatorName)} /> + <Textarea ref={ref => this.refMessage = ref} placeholder={translate('popupSpaceJoinRequestMessagePlaceholder')} /> <div className="buttons"> <Button onClick={this.onRequest} text={translate('popupSpaceJoinRequestRequestToJoin')} className="c36" /> </div> + <div className="note">{translate('popupSpaceJoinRequestNote')}</div> + + <Error text={error} /> </React.Fragment> ); }; + componentDidMount (): void { + const { param } = this.props; + const { data } = param; + const { cid, key } = data; + + C.SpaceInviteView(cid, key, (message: any) => { + if (message.error.code) { + this.setState({ error: message.error.description }); + return; + }; + + this.spaceName = message.spaceName; + this.creatorName = message.creatorName; + this.forceUpdate(); + }); + }; + onRequest () { - console.log('MESSAGE: ', this.message) }; }); -export default PopupSpaceJoinRequest; +export default PopupSpaceJoinRequest; \ No newline at end of file diff --git a/src/ts/interface/space.ts b/src/ts/interface/space.ts index 833dbaad3d..4eb012cb08 100644 --- a/src/ts/interface/space.ts +++ b/src/ts/interface/space.ts @@ -12,4 +12,5 @@ export enum SpaceStatus { export enum SpaceType { Private = 0, Personal = 1, + Shared = 2, }; \ No newline at end of file diff --git a/src/ts/lib/api/response.ts b/src/ts/lib/api/response.ts index eb99dbf1a9..7f2861d34a 100644 --- a/src/ts/lib/api/response.ts +++ b/src/ts/lib/api/response.ts @@ -431,7 +431,7 @@ export const NotificationList = (response: Rpc.Notification.List.Response) => { export const SpaceInviteGenerate = (response: Rpc.Space.InviteGenerate.Response) => { return { inviteCid: response.getInvitecid(), - inviteFileKey: response.getInvitefilekey(), + inviteKey: response.getInvitefilekey(), }; };