From e9d3ea7b2b55ff6bb12cbfc2e382d6d3851cff23 Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 2 Nov 2020 03:33:39 +1100 Subject: [PATCH 01/67] Update CloudCode.react.js --- src/dashboard/Data/CloudCode/CloudCode.react.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/dashboard/Data/CloudCode/CloudCode.react.js b/src/dashboard/Data/CloudCode/CloudCode.react.js index 9387271680..5afef158b1 100644 --- a/src/dashboard/Data/CloudCode/CloudCode.react.js +++ b/src/dashboard/Data/CloudCode/CloudCode.react.js @@ -15,7 +15,8 @@ import styles from 'dashboard/Data/CloudCode/CloudCode.scss'; import Toolbar from 'components/Toolbar/Toolbar.react'; function getPath(params) { - return params.splat; + const last = params.location.pathname.split('cloud_code/')[1] + return last; } export default class CloudCode extends DashboardView { @@ -31,12 +32,12 @@ export default class CloudCode extends DashboardView { } componentWillMount() { - this.fetchSource(this.context.currentApp, getPath(this.props.params)); + this.fetchSource(this.context.currentApp, getPath(this.props)); } componentWillReceiveProps(nextProps, nextContext) { if (this.context !== nextContext) { - this.fetchSource(nextContext.currentApp, getPath(nextProps.params)); + this.fetchSource(nextContext.currentApp, getPath(nextProps)); } } @@ -52,7 +53,7 @@ export default class CloudCode extends DashboardView { if (!fileName || release.files[fileName] === undefined) { // Means we're still in /cloud_code/. Let's redirect to /cloud_code/main.js - history.replace(this.context.generatePath('cloud_code/main.js')) + history.replace(this.context.generatePath(`cloud_code/${Object.keys(release.files)[0]}`)) } else { // Means we can load /cloud_code/ app.getSource(fileName).then( @@ -66,7 +67,7 @@ export default class CloudCode extends DashboardView { } renderSidebar() { - let current = getPath(this.props.params) || ''; + let current = getPath(this.props) || ''; let files = this.state.files; if (!files) { return null; @@ -90,7 +91,7 @@ export default class CloudCode extends DashboardView { renderContent() { let toolbar = null; let content = null; - let fileName = getPath(this.props.params); + let fileName = getPath(this.props); if (!this.state.files || Object.keys(this.state.files).length === 0) { content = ( From 2a5c0503e8aaccb4e33dbecb62be38ea0489cde8 Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 2 Nov 2020 14:24:15 +1100 Subject: [PATCH 02/67] Allow Writing Cloud Code --- .../SaveButton/SaveButton.example.js | 3 ++ src/components/SaveButton/SaveButton.react.js | 2 +- .../Data/CloudCode/CloudCode.react.js | 47 ++++++++++++++++--- src/lib/ParseApp.js | 22 ++++----- 4 files changed, 55 insertions(+), 19 deletions(-) diff --git a/src/components/SaveButton/SaveButton.example.js b/src/components/SaveButton/SaveButton.example.js index f7e55af774..bd2d50a3f6 100644 --- a/src/components/SaveButton/SaveButton.example.js +++ b/src/components/SaveButton/SaveButton.example.js @@ -55,6 +55,9 @@ export const demos = [
+
+ +
) }, { diff --git a/src/components/SaveButton/SaveButton.react.js b/src/components/SaveButton/SaveButton.react.js index 0bb741fa86..f7aced23db 100644 --- a/src/components/SaveButton/SaveButton.react.js +++ b/src/components/SaveButton/SaveButton.react.js @@ -52,7 +52,7 @@ let SaveButton = ({ ; }; -SaveButton.States = keyMirror(['SAVING', 'SUCCEEDED', 'FAILED']); +SaveButton.States = keyMirror(['SAVING', 'SUCCEEDED', 'FAILED', 'WAITING']); let {...forwardedButtonProps} = Button.propTypes; delete forwardedButtonProps.value; diff --git a/src/dashboard/Data/CloudCode/CloudCode.react.js b/src/dashboard/Data/CloudCode/CloudCode.react.js index 5afef158b1..f07aa84704 100644 --- a/src/dashboard/Data/CloudCode/CloudCode.react.js +++ b/src/dashboard/Data/CloudCode/CloudCode.react.js @@ -5,7 +5,7 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import CodeSnippet from 'components/CodeSnippet/CodeSnippet.react'; +import CodeEditor from 'components/CodeEditor/CodeEditor.react'; import DashboardView from 'dashboard/DashboardView.react'; import EmptyState from 'components/EmptyState/EmptyState.react'; import FileTree from 'components/FileTree/FileTree.react'; @@ -13,6 +13,7 @@ import history from 'dashboard/history'; import React from 'react'; import styles from 'dashboard/Data/CloudCode/CloudCode.scss'; import Toolbar from 'components/Toolbar/Toolbar.react'; +import SaveButton from 'components/SaveButton/SaveButton.react'; function getPath(params) { const last = params.location.pathname.split('cloud_code/')[1] @@ -27,7 +28,9 @@ export default class CloudCode extends DashboardView { this.state = { files: undefined, - source: undefined + source: undefined, + saveState: SaveButton.States.WAITING, + saveError: '', }; } @@ -56,8 +59,14 @@ export default class CloudCode extends DashboardView { history.replace(this.context.generatePath(`cloud_code/${Object.keys(release.files)[0]}`)) } else { // Means we can load /cloud_code/ + this.setState({ source: undefined }) app.getSource(fileName).then( - (source) => this.setState({ source: source }), + (source) => { + this.setState({ source: source }) + if (this.editor) { + this.editor.value = source; + } + }, () => this.setState({ source: undefined }) ); } @@ -87,7 +96,23 @@ export default class CloudCode extends DashboardView { ); } - + async getCode() { + if (!this.editor) { + return; + } + this.setState({ saveState: SaveButton.States.SAVING }); + let fileName = getPath(this.props); + try { + await this.context.currentApp.saveSource(fileName,this.editor.value); + this.setState({ saveState: SaveButton.States.SUCCEEDED }); + setTimeout(()=> { + this.setState({ saveState: SaveButton.States.WAITING }); + },2000); + } catch (e) { + this.setState({ saveState: SaveButton.States.FAILED }); + this.setState({ saveError: e.message || e }); + } + } renderContent() { let toolbar = null; let content = null; @@ -111,10 +136,20 @@ export default class CloudCode extends DashboardView { subsection={fileName} />; let source = this.state.files[fileName]; - if (source && source.source) { + if ((source && source.source) || this.state.source) { content = (
- + (this.editor = editor)} + fontSize={'14px'} + /> + this.getCode(this)}>
); } diff --git a/src/lib/ParseApp.js b/src/lib/ParseApp.js index 0065109f06..f0b5f0110a 100644 --- a/src/lib/ParseApp.js +++ b/src/lib/ParseApp.js @@ -117,6 +117,15 @@ export default class ParseApp { return this.apiRequest('GET', path, {}, { useMasterKey: true }); } + /** + * Saves source of a Cloud Code hosted file from api.parse.com + * fileName - the name of the file to be fetched + * data - the text to save to the cloud file + */ + saveSource(fileName,data) { + return this.apiRequest('POST', `scripts/${fileName}`, {data}, { useMasterKey: true }); + } + /** * Fetches source of a Cloud Code hosted file from api.parse.com * fileName - the name of the file to be fetched @@ -127,22 +136,11 @@ export default class ParseApp { // No release yet return Promise.resolve(null); } - - let fileMetaData = release.files[fileName]; - if (fileMetaData && fileMetaData.source) { - return Promise.resolve(fileMetaData.source); - } - - let params = { - version: fileMetaData.version, - checksum: fileMetaData.checksum - } - return this.apiRequest('GET', `scripts/${fileName}`, params, { useMasterKey: true }); + return this.apiRequest('GET', `scripts/${fileName}`, {}, { useMasterKey: true }); }).then((source) => { if (this.latestRelease.files) { this.latestRelease.files[fileName].source = source; } - return Promise.resolve(source); }); } From 44eea2a436be2ffc63c006b0cb2468240835005f Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 2 Nov 2020 03:33:39 +1100 Subject: [PATCH 03/67] Update CloudCode.react.js --- src/dashboard/Data/CloudCode/CloudCode.react.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/dashboard/Data/CloudCode/CloudCode.react.js b/src/dashboard/Data/CloudCode/CloudCode.react.js index 9387271680..5afef158b1 100644 --- a/src/dashboard/Data/CloudCode/CloudCode.react.js +++ b/src/dashboard/Data/CloudCode/CloudCode.react.js @@ -15,7 +15,8 @@ import styles from 'dashboard/Data/CloudCode/CloudCode.scss'; import Toolbar from 'components/Toolbar/Toolbar.react'; function getPath(params) { - return params.splat; + const last = params.location.pathname.split('cloud_code/')[1] + return last; } export default class CloudCode extends DashboardView { @@ -31,12 +32,12 @@ export default class CloudCode extends DashboardView { } componentWillMount() { - this.fetchSource(this.context.currentApp, getPath(this.props.params)); + this.fetchSource(this.context.currentApp, getPath(this.props)); } componentWillReceiveProps(nextProps, nextContext) { if (this.context !== nextContext) { - this.fetchSource(nextContext.currentApp, getPath(nextProps.params)); + this.fetchSource(nextContext.currentApp, getPath(nextProps)); } } @@ -52,7 +53,7 @@ export default class CloudCode extends DashboardView { if (!fileName || release.files[fileName] === undefined) { // Means we're still in /cloud_code/. Let's redirect to /cloud_code/main.js - history.replace(this.context.generatePath('cloud_code/main.js')) + history.replace(this.context.generatePath(`cloud_code/${Object.keys(release.files)[0]}`)) } else { // Means we can load /cloud_code/ app.getSource(fileName).then( @@ -66,7 +67,7 @@ export default class CloudCode extends DashboardView { } renderSidebar() { - let current = getPath(this.props.params) || ''; + let current = getPath(this.props) || ''; let files = this.state.files; if (!files) { return null; @@ -90,7 +91,7 @@ export default class CloudCode extends DashboardView { renderContent() { let toolbar = null; let content = null; - let fileName = getPath(this.props.params); + let fileName = getPath(this.props); if (!this.state.files || Object.keys(this.state.files).length === 0) { content = ( From d7454a1cc338f440c06b9c3e411ca7cbce18a4df Mon Sep 17 00:00:00 2001 From: dblythy Date: Mon, 2 Nov 2020 14:24:15 +1100 Subject: [PATCH 04/67] Allow Writing Cloud Code --- .../SaveButton/SaveButton.example.js | 3 ++ src/components/SaveButton/SaveButton.react.js | 2 +- .../Data/CloudCode/CloudCode.react.js | 47 ++++++++++++++++--- src/lib/ParseApp.js | 22 ++++----- 4 files changed, 55 insertions(+), 19 deletions(-) diff --git a/src/components/SaveButton/SaveButton.example.js b/src/components/SaveButton/SaveButton.example.js index f7e55af774..bd2d50a3f6 100644 --- a/src/components/SaveButton/SaveButton.example.js +++ b/src/components/SaveButton/SaveButton.example.js @@ -55,6 +55,9 @@ export const demos = [
+
+ +
) }, { diff --git a/src/components/SaveButton/SaveButton.react.js b/src/components/SaveButton/SaveButton.react.js index 0bb741fa86..f7aced23db 100644 --- a/src/components/SaveButton/SaveButton.react.js +++ b/src/components/SaveButton/SaveButton.react.js @@ -52,7 +52,7 @@ let SaveButton = ({ ; }; -SaveButton.States = keyMirror(['SAVING', 'SUCCEEDED', 'FAILED']); +SaveButton.States = keyMirror(['SAVING', 'SUCCEEDED', 'FAILED', 'WAITING']); let {...forwardedButtonProps} = Button.propTypes; delete forwardedButtonProps.value; diff --git a/src/dashboard/Data/CloudCode/CloudCode.react.js b/src/dashboard/Data/CloudCode/CloudCode.react.js index 5afef158b1..f07aa84704 100644 --- a/src/dashboard/Data/CloudCode/CloudCode.react.js +++ b/src/dashboard/Data/CloudCode/CloudCode.react.js @@ -5,7 +5,7 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import CodeSnippet from 'components/CodeSnippet/CodeSnippet.react'; +import CodeEditor from 'components/CodeEditor/CodeEditor.react'; import DashboardView from 'dashboard/DashboardView.react'; import EmptyState from 'components/EmptyState/EmptyState.react'; import FileTree from 'components/FileTree/FileTree.react'; @@ -13,6 +13,7 @@ import history from 'dashboard/history'; import React from 'react'; import styles from 'dashboard/Data/CloudCode/CloudCode.scss'; import Toolbar from 'components/Toolbar/Toolbar.react'; +import SaveButton from 'components/SaveButton/SaveButton.react'; function getPath(params) { const last = params.location.pathname.split('cloud_code/')[1] @@ -27,7 +28,9 @@ export default class CloudCode extends DashboardView { this.state = { files: undefined, - source: undefined + source: undefined, + saveState: SaveButton.States.WAITING, + saveError: '', }; } @@ -56,8 +59,14 @@ export default class CloudCode extends DashboardView { history.replace(this.context.generatePath(`cloud_code/${Object.keys(release.files)[0]}`)) } else { // Means we can load /cloud_code/ + this.setState({ source: undefined }) app.getSource(fileName).then( - (source) => this.setState({ source: source }), + (source) => { + this.setState({ source: source }) + if (this.editor) { + this.editor.value = source; + } + }, () => this.setState({ source: undefined }) ); } @@ -87,7 +96,23 @@ export default class CloudCode extends DashboardView { ); } - + async getCode() { + if (!this.editor) { + return; + } + this.setState({ saveState: SaveButton.States.SAVING }); + let fileName = getPath(this.props); + try { + await this.context.currentApp.saveSource(fileName,this.editor.value); + this.setState({ saveState: SaveButton.States.SUCCEEDED }); + setTimeout(()=> { + this.setState({ saveState: SaveButton.States.WAITING }); + },2000); + } catch (e) { + this.setState({ saveState: SaveButton.States.FAILED }); + this.setState({ saveError: e.message || e }); + } + } renderContent() { let toolbar = null; let content = null; @@ -111,10 +136,20 @@ export default class CloudCode extends DashboardView { subsection={fileName} />; let source = this.state.files[fileName]; - if (source && source.source) { + if ((source && source.source) || this.state.source) { content = (
- + (this.editor = editor)} + fontSize={'14px'} + /> + this.getCode(this)}>
); } diff --git a/src/lib/ParseApp.js b/src/lib/ParseApp.js index 6cf0881f2b..863689111b 100644 --- a/src/lib/ParseApp.js +++ b/src/lib/ParseApp.js @@ -119,6 +119,15 @@ export default class ParseApp { return this.apiRequest('GET', path, {}, { useMasterKey: true }); } + /** + * Saves source of a Cloud Code hosted file from api.parse.com + * fileName - the name of the file to be fetched + * data - the text to save to the cloud file + */ + saveSource(fileName,data) { + return this.apiRequest('POST', `scripts/${fileName}`, {data}, { useMasterKey: true }); + } + /** * Fetches source of a Cloud Code hosted file from api.parse.com * fileName - the name of the file to be fetched @@ -129,22 +138,11 @@ export default class ParseApp { // No release yet return Promise.resolve(null); } - - let fileMetaData = release.files[fileName]; - if (fileMetaData && fileMetaData.source) { - return Promise.resolve(fileMetaData.source); - } - - let params = { - version: fileMetaData.version, - checksum: fileMetaData.checksum - } - return this.apiRequest('GET', `scripts/${fileName}`, params, { useMasterKey: true }); + return this.apiRequest('GET', `scripts/${fileName}`, {}, { useMasterKey: true }); }).then((source) => { if (this.latestRelease.files) { this.latestRelease.files[fileName].source = source; } - return Promise.resolve(source); }); } From b85fe40ab50927452e122345684a48551671c9f4 Mon Sep 17 00:00:00 2001 From: dblythy Date: Sat, 4 Sep 2021 16:11:30 +1000 Subject: [PATCH 05/67] Revert "Allow Writing Cloud Code" This reverts commit d7454a1cc338f440c06b9c3e411ca7cbce18a4df. --- .../SaveButton/SaveButton.example.js | 3 -- src/components/SaveButton/SaveButton.react.js | 2 +- .../Data/CloudCode/CloudCode.react.js | 47 +++---------------- src/lib/ParseApp.js | 22 +++++---- 4 files changed, 19 insertions(+), 55 deletions(-) diff --git a/src/components/SaveButton/SaveButton.example.js b/src/components/SaveButton/SaveButton.example.js index bd2d50a3f6..f7e55af774 100644 --- a/src/components/SaveButton/SaveButton.example.js +++ b/src/components/SaveButton/SaveButton.example.js @@ -55,9 +55,6 @@ export const demos = [
-
- -
) }, { diff --git a/src/components/SaveButton/SaveButton.react.js b/src/components/SaveButton/SaveButton.react.js index f7aced23db..0bb741fa86 100644 --- a/src/components/SaveButton/SaveButton.react.js +++ b/src/components/SaveButton/SaveButton.react.js @@ -52,7 +52,7 @@ let SaveButton = ({ ; }; -SaveButton.States = keyMirror(['SAVING', 'SUCCEEDED', 'FAILED', 'WAITING']); +SaveButton.States = keyMirror(['SAVING', 'SUCCEEDED', 'FAILED']); let {...forwardedButtonProps} = Button.propTypes; delete forwardedButtonProps.value; diff --git a/src/dashboard/Data/CloudCode/CloudCode.react.js b/src/dashboard/Data/CloudCode/CloudCode.react.js index f07aa84704..5afef158b1 100644 --- a/src/dashboard/Data/CloudCode/CloudCode.react.js +++ b/src/dashboard/Data/CloudCode/CloudCode.react.js @@ -5,7 +5,7 @@ * This source code is licensed under the license found in the LICENSE file in * the root directory of this source tree. */ -import CodeEditor from 'components/CodeEditor/CodeEditor.react'; +import CodeSnippet from 'components/CodeSnippet/CodeSnippet.react'; import DashboardView from 'dashboard/DashboardView.react'; import EmptyState from 'components/EmptyState/EmptyState.react'; import FileTree from 'components/FileTree/FileTree.react'; @@ -13,7 +13,6 @@ import history from 'dashboard/history'; import React from 'react'; import styles from 'dashboard/Data/CloudCode/CloudCode.scss'; import Toolbar from 'components/Toolbar/Toolbar.react'; -import SaveButton from 'components/SaveButton/SaveButton.react'; function getPath(params) { const last = params.location.pathname.split('cloud_code/')[1] @@ -28,9 +27,7 @@ export default class CloudCode extends DashboardView { this.state = { files: undefined, - source: undefined, - saveState: SaveButton.States.WAITING, - saveError: '', + source: undefined }; } @@ -59,14 +56,8 @@ export default class CloudCode extends DashboardView { history.replace(this.context.generatePath(`cloud_code/${Object.keys(release.files)[0]}`)) } else { // Means we can load /cloud_code/ - this.setState({ source: undefined }) app.getSource(fileName).then( - (source) => { - this.setState({ source: source }) - if (this.editor) { - this.editor.value = source; - } - }, + (source) => this.setState({ source: source }), () => this.setState({ source: undefined }) ); } @@ -96,23 +87,7 @@ export default class CloudCode extends DashboardView { ); } - async getCode() { - if (!this.editor) { - return; - } - this.setState({ saveState: SaveButton.States.SAVING }); - let fileName = getPath(this.props); - try { - await this.context.currentApp.saveSource(fileName,this.editor.value); - this.setState({ saveState: SaveButton.States.SUCCEEDED }); - setTimeout(()=> { - this.setState({ saveState: SaveButton.States.WAITING }); - },2000); - } catch (e) { - this.setState({ saveState: SaveButton.States.FAILED }); - this.setState({ saveError: e.message || e }); - } - } + renderContent() { let toolbar = null; let content = null; @@ -136,20 +111,10 @@ export default class CloudCode extends DashboardView { subsection={fileName} />; let source = this.state.files[fileName]; - if ((source && source.source) || this.state.source) { + if (source && source.source) { content = (
- (this.editor = editor)} - fontSize={'14px'} - /> - this.getCode(this)}> +
); } diff --git a/src/lib/ParseApp.js b/src/lib/ParseApp.js index 863689111b..6cf0881f2b 100644 --- a/src/lib/ParseApp.js +++ b/src/lib/ParseApp.js @@ -119,15 +119,6 @@ export default class ParseApp { return this.apiRequest('GET', path, {}, { useMasterKey: true }); } - /** - * Saves source of a Cloud Code hosted file from api.parse.com - * fileName - the name of the file to be fetched - * data - the text to save to the cloud file - */ - saveSource(fileName,data) { - return this.apiRequest('POST', `scripts/${fileName}`, {data}, { useMasterKey: true }); - } - /** * Fetches source of a Cloud Code hosted file from api.parse.com * fileName - the name of the file to be fetched @@ -138,11 +129,22 @@ export default class ParseApp { // No release yet return Promise.resolve(null); } - return this.apiRequest('GET', `scripts/${fileName}`, {}, { useMasterKey: true }); + + let fileMetaData = release.files[fileName]; + if (fileMetaData && fileMetaData.source) { + return Promise.resolve(fileMetaData.source); + } + + let params = { + version: fileMetaData.version, + checksum: fileMetaData.checksum + } + return this.apiRequest('GET', `scripts/${fileName}`, params, { useMasterKey: true }); }).then((source) => { if (this.latestRelease.files) { this.latestRelease.files[fileName].source = source; } + return Promise.resolve(source); }); } From eea1e2dad28795e55467091dc2f7d99790bdd3c4 Mon Sep 17 00:00:00 2001 From: dblythy Date: Sat, 4 Sep 2021 16:11:34 +1000 Subject: [PATCH 06/67] Revert "Update CloudCode.react.js" This reverts commit 44eea2a436be2ffc63c006b0cb2468240835005f. --- src/dashboard/Data/CloudCode/CloudCode.react.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/dashboard/Data/CloudCode/CloudCode.react.js b/src/dashboard/Data/CloudCode/CloudCode.react.js index 5afef158b1..9387271680 100644 --- a/src/dashboard/Data/CloudCode/CloudCode.react.js +++ b/src/dashboard/Data/CloudCode/CloudCode.react.js @@ -15,8 +15,7 @@ import styles from 'dashboard/Data/CloudCode/CloudCode.scss'; import Toolbar from 'components/Toolbar/Toolbar.react'; function getPath(params) { - const last = params.location.pathname.split('cloud_code/')[1] - return last; + return params.splat; } export default class CloudCode extends DashboardView { @@ -32,12 +31,12 @@ export default class CloudCode extends DashboardView { } componentWillMount() { - this.fetchSource(this.context.currentApp, getPath(this.props)); + this.fetchSource(this.context.currentApp, getPath(this.props.params)); } componentWillReceiveProps(nextProps, nextContext) { if (this.context !== nextContext) { - this.fetchSource(nextContext.currentApp, getPath(nextProps)); + this.fetchSource(nextContext.currentApp, getPath(nextProps.params)); } } @@ -53,7 +52,7 @@ export default class CloudCode extends DashboardView { if (!fileName || release.files[fileName] === undefined) { // Means we're still in /cloud_code/. Let's redirect to /cloud_code/main.js - history.replace(this.context.generatePath(`cloud_code/${Object.keys(release.files)[0]}`)) + history.replace(this.context.generatePath('cloud_code/main.js')) } else { // Means we can load /cloud_code/ app.getSource(fileName).then( @@ -67,7 +66,7 @@ export default class CloudCode extends DashboardView { } renderSidebar() { - let current = getPath(this.props) || ''; + let current = getPath(this.props.params) || ''; let files = this.state.files; if (!files) { return null; @@ -91,7 +90,7 @@ export default class CloudCode extends DashboardView { renderContent() { let toolbar = null; let content = null; - let fileName = getPath(this.props); + let fileName = getPath(this.props.params); if (!this.state.files || Object.keys(this.state.files).length === 0) { content = ( From 34536b3ba200728e38ff017e487b0339bf0bee6b Mon Sep 17 00:00:00 2001 From: dblythy Date: Thu, 7 Oct 2021 00:27:22 +1100 Subject: [PATCH 07/67] feat: allow graphQL headers --- src/dashboard/Data/ApiConsole/GraphQLConsole.react.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/dashboard/Data/ApiConsole/GraphQLConsole.react.js b/src/dashboard/Data/ApiConsole/GraphQLConsole.react.js index d082c819af..10cf3ae2ee 100644 --- a/src/dashboard/Data/ApiConsole/GraphQLConsole.react.js +++ b/src/dashboard/Data/ApiConsole/GraphQLConsole.react.js @@ -29,16 +29,18 @@ export default class GraphQLConsole extends Component { ); } else { - const headers = { + const parseHeaders = { 'X-Parse-Application-Id': applicationId, 'X-Parse-Master-Key': masterKey } if (clientKey) { - headers['X-Parse-Client-Key'] = clientKey + parseHeaders['X-Parse-Client-Key'] = clientKey } content = ( { + headers={JSON.stringify(parseHeaders)} + headerEditorEnabled={true} + fetcher={async (graphQLParams, {headers}) => { const data = await fetch( graphQLServerURL, { From eff7d5ad3fe09bbf14eb38fb1b8aa13646c7b689 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 8 Oct 2021 15:49:14 +0000 Subject: [PATCH 08/67] chore(release): 3.3.0-alpha.1 [skip ci] # [3.3.0-alpha.1](https://github.com/ParsePlatform/parse-dashboard/compare/3.2.1-alpha.1...3.3.0-alpha.1) (2021-10-08) ### Features * allow GraphIQL headers ([#1836](https://github.com/ParsePlatform/parse-dashboard/issues/1836)) ([3afcf73](https://github.com/ParsePlatform/parse-dashboard/commit/3afcf730c1303b3957ab03d683ada86242175579)) * allow graphQL headers ([34536b3](https://github.com/ParsePlatform/parse-dashboard/commit/34536b3ba200728e38ff017e487b0339bf0bee6b)) ### Reverts * Revert "Update CloudCode.react.js" ([eea1e2d](https://github.com/ParsePlatform/parse-dashboard/commit/eea1e2dad28795e55467091dc2f7d99790bdd3c4)) --- changelogs/CHANGELOG_alpha.md | 12 ++++++++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index ba770873c6..0fdd0c2c38 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -1,3 +1,15 @@ +# [3.3.0-alpha.1](https://github.com/ParsePlatform/parse-dashboard/compare/3.2.1-alpha.1...3.3.0-alpha.1) (2021-10-08) + + +### Features + +* allow GraphIQL headers ([#1836](https://github.com/ParsePlatform/parse-dashboard/issues/1836)) ([3afcf73](https://github.com/ParsePlatform/parse-dashboard/commit/3afcf730c1303b3957ab03d683ada86242175579)) +* allow graphQL headers ([34536b3](https://github.com/ParsePlatform/parse-dashboard/commit/34536b3ba200728e38ff017e487b0339bf0bee6b)) + +### Reverts + +* Revert "Update CloudCode.react.js" ([eea1e2d](https://github.com/ParsePlatform/parse-dashboard/commit/eea1e2dad28795e55467091dc2f7d99790bdd3c4)) + ## [3.2.1-alpha.1](https://github.com/ParsePlatform/parse-dashboard/compare/3.2.0...3.2.1-alpha.1) (2021-10-08) diff --git a/package-lock.json b/package-lock.json index 3fc5e71f62..eecde66585 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "parse-dashboard", - "version": "3.2.1-alpha.1", + "version": "3.3.0-alpha.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 1ba026e5d4..ef64be3ffb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "parse-dashboard", - "version": "3.2.1-alpha.1", + "version": "3.3.0-alpha.1", "repository": { "type": "git", "url": "https://github.com/ParsePlatform/parse-dashboard" From 44f7676d878b3f12a38f71c76068e844c2b5a8d1 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Fri, 8 Oct 2021 17:51:30 +0200 Subject: [PATCH 09/67] docs: change base branch name for PRs --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 76a7172bce..24b41c99f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,7 +12,7 @@ When working on React components, use `npm run pig` and visit `localhost:4041` t ## Pull Requests We actively welcome your pull requests. -1. Fork the repo and create your branch from `master`. +1. Fork the repo and create your branch from the `alpha` branch. 2. If you've added code that should be tested, add tests. 3. If you've changed APIs, update the documentation. 4. If you've updated/added an UI component, please add a screenshot. From 5efcea877940274a5c6202970308528257cfd163 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Fri, 8 Oct 2021 17:57:12 +0200 Subject: [PATCH 10/67] docs: fix changelog entry --- changelogs/CHANGELOG_alpha.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/changelogs/CHANGELOG_alpha.md b/changelogs/CHANGELOG_alpha.md index 0fdd0c2c38..47d63e2076 100644 --- a/changelogs/CHANGELOG_alpha.md +++ b/changelogs/CHANGELOG_alpha.md @@ -4,11 +4,6 @@ ### Features * allow GraphIQL headers ([#1836](https://github.com/ParsePlatform/parse-dashboard/issues/1836)) ([3afcf73](https://github.com/ParsePlatform/parse-dashboard/commit/3afcf730c1303b3957ab03d683ada86242175579)) -* allow graphQL headers ([34536b3](https://github.com/ParsePlatform/parse-dashboard/commit/34536b3ba200728e38ff017e487b0339bf0bee6b)) - -### Reverts - -* Revert "Update CloudCode.react.js" ([eea1e2d](https://github.com/ParsePlatform/parse-dashboard/commit/eea1e2dad28795e55467091dc2f7d99790bdd3c4)) ## [3.2.1-alpha.1](https://github.com/ParsePlatform/parse-dashboard/compare/3.2.0...3.2.1-alpha.1) (2021-10-08) From d7477860ebf972a1cb69a43761e77841831754e2 Mon Sep 17 00:00:00 2001 From: Manuel <5673677+mtrezza@users.noreply.github.com> Date: Mon, 11 Oct 2021 17:31:04 +0200 Subject: [PATCH 11/67] feat: add pointer representation by a chosen column instead of objectId (#1852) --- .gitignore | 3 + README.md | 15 + .../BrowserCell/BrowserCell.react.js | 299 ++++++++++-------- src/components/BrowserRow/BrowserRow.react.js | 1 + src/components/FileInput/FileInput.react.js | 2 +- src/dashboard/Data/Browser/Browser.react.js | 36 ++- .../Data/Browser/BrowserTable.react.js | 4 +- .../Data/Browser/BrowserToolbar.react.js | 4 +- .../Data/Browser/DataBrowser.react.js | 3 +- .../Data/Browser/PointerKeyDialog.react.js | 73 +++++ .../Data/Browser/RemoveColumnDialog.react.js | 2 +- src/lib/ColumnPreferences.js | 19 ++ 12 files changed, 321 insertions(+), 140 deletions(-) create mode 100644 src/dashboard/Data/Browser/PointerKeyDialog.react.js diff --git a/.gitignore b/.gitignore index 5dbbc37203..cb08935ec4 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,6 @@ npm-debug.log logs/ test_logs + +# visual studio code +.vscode diff --git a/README.md b/README.md index 0e4b11f018..f14070003e 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ Parse Dashboard is a standalone dashboard for managing your [Parse Server](https - [Run with Docker](#run-with-docker) - [Features](#features) - [Browse as User](#browse-as-user) + - [Change Pointer Key](#change-pointer-key) + - [Limitations](#limitations) - [CSV Export](#csv-export) - [Contributing](#contributing) @@ -605,6 +607,19 @@ This feature allows you to use the data browser as another user, respecting that > ⚠️ Logging in as another user will trigger the same Cloud Triggers as if the user logged in themselves using any other login method. Logging in as another user requires to enter that user's password. +## Change Pointer Key + +▶️ *Core > Browser > Edit > Change pointer key* + +This feature allows you to change how a pointer is represented in the browser. By default, a pointer is represented by the `objectId` of the linked object. You can change this to any other column of the object class. For example, if class `Installation` has a field that contains a pointer to class `User`, the pointer will show the `objectId` of the user by default. You can change this to display the field `email` of the user, so that a pointer displays the user's email address instead. + +### Limitations + +- This does not work for an array of pointers; the pointer will always display the `objectId`. +- System columns like `createdAt`, `updatedAt`, `ACL` cannot be set as pointer key. +- This feature uses browser storage; switching to a different browser resets the pointer key to `objectId`. + +> ⚠️ For each custom pointer key in each row, a server request is triggered to resolve the custom pointer key. For example, if the browser shows a class with 50 rows and each row contains 3 custom pointer keys, a total of 150 separate server requests are triggered. ## CSV Export ▶️ *Core > Browser > Export* diff --git a/src/components/BrowserCell/BrowserCell.react.js b/src/components/BrowserCell/BrowserCell.react.js index a2106503dd..8414e8f13d 100644 --- a/src/components/BrowserCell/BrowserCell.react.js +++ b/src/components/BrowserCell/BrowserCell.react.js @@ -15,6 +15,7 @@ import React, { Component } from 'react'; import styles from 'components/BrowserCell/BrowserCell.scss'; import { unselectable } from 'stylesheets/base.scss'; import Tooltip from '../Tooltip/PopperTooltip.react'; +import * as ColumnPreferences from 'lib/ColumnPreferences'; export default class BrowserCell extends Component { constructor() { @@ -23,13 +24,161 @@ export default class BrowserCell extends Component { this.cellRef = React.createRef(); this.copyableValue = undefined; this.state = { - showTooltip: false + showTooltip: false, + content: null, + classes: [] + }; + } + + async renderCellContent() { + let content = this.props.value; + let isNewRow = this.props.row < 0; + this.copyableValue = content; + let classes = [styles.cell, unselectable]; + if (this.props.hidden) { + content = this.props.value !== undefined || !isNewRow ? '(hidden)' : this.props.isRequired ? '(required)' : '(undefined)'; + classes.push(styles.empty); + } else if (this.props.value === undefined) { + if (this.props.type === 'ACL') { + this.copyableValue = content = 'Public Read + Write'; + } else { + this.copyableValue = content = '(undefined)'; + classes.push(styles.empty); + } + content = isNewRow && this.props.isRequired && this.props.value === undefined ? '(required)' : content; + } else if (this.props.value === null) { + this.copyableValue = content = '(null)'; + classes.push(styles.empty); + } else if (this.props.value === '') { + content =  ; + classes.push(styles.empty); + } else if (this.props.type === 'Pointer') { + const defaultPointerKey = await ColumnPreferences.getPointerDefaultKey(this.props.appId, this.props.value.className); + let dataValue = this.props.value.id; + if( defaultPointerKey !== 'objectId' ) { + dataValue = this.props.value.get(defaultPointerKey); + if ( dataValue && typeof dataValue === 'object' ){ + if ( dataValue instanceof Date ) { + dataValue = dataValue.toLocaleString(); + } + else { + if ( !this.props.value.id ) { + dataValue = this.props.value.id; + } else { + dataValue = '(undefined)'; + } + } + } + if ( !dataValue ) { + if ( this.props.value.id ) { + dataValue = this.props.value.id; + } else { + dataValue = '(undefined)'; + } + } + } + + if (this.props.value && this.props.value.__type) { + const object = new Parse.Object(this.props.value.className); + object.id = this.props.value.objectId; + this.props.value = object; + } + + content = this.props.onPointerClick ? ( + + ) : ( + dataValue + ); + + this.copyableValue = this.props.value.id; + } + else if (this.props.type === 'Array') { + if ( this.props.value[0] && typeof this.props.value[0] === 'object' && this.props.value[0].__type === 'Pointer' ) { + const array = []; + this.props.value.map( (v, i) => { + if ( typeof v !== 'object' || v.__type !== 'Pointer' ) { + throw new Error('Invalid type found in pointer array'); + } + const object = new Parse.Object(v.className); + object.id = v.objectId; + array.push( + + ); + }); + this.copyableValue = content =
    + { array.map( a =>
  • {a}
  • ) } +
+ if ( array.length > 1 ) { + classes.push(styles.hasMore); + } + } + else { + this.copyableValue = content = JSON.stringify(this.props.value); + } + } + else if (this.props.type === 'Date') { + if (typeof value === 'object' && this.props.value.__type) { + this.props.value = new Date(this.props.value.iso); + } else if (typeof value === 'string') { + this.props.value = new Date(this.props.value); + } + this.copyableValue = content = dateStringUTC(this.props.value); + } else if (this.props.type === 'Boolean') { + this.copyableValue = content = this.props.value ? 'True' : 'False'; + } else if (this.props.type === 'Object' || this.props.type === 'Bytes') { + this.copyableValue = content = JSON.stringify(this.props.value); + } else if (this.props.type === 'File') { + const fileName = this.props.value.url() ? getFileName(this.props.value) : 'Uploading\u2026'; + content = ; + this.copyableValue = fileName; + } else if (this.props.type === 'ACL') { + let pieces = []; + let json = this.props.value.toJSON(); + if (Object.prototype.hasOwnProperty.call(json, '*')) { + if (json['*'].read && json['*'].write) { + pieces.push('Public Read + Write'); + } else if (json['*'].read) { + pieces.push('Public Read'); + } else if (json['*'].write) { + pieces.push('Public Write'); + } + } + for (let role in json) { + if (role !== '*') { + pieces.push(role); + } + } + if (pieces.length === 0) { + pieces.push('Master Key Only'); + } + this.copyableValue = content = pieces.join(', '); + } else if (this.props.type === 'GeoPoint') { + this.copyableValue = content = `(${this.props.value.latitude}, ${this.props.value.longitude})`; + } else if (this.props.type === 'Polygon') { + this.copyableValue = content = this.props.value.coordinates.map(coord => `(${coord})`) + } else if (this.props.type === 'Relation') { + content = this.props.setRelation ? ( +
+ this.props.setRelation(this.props.value)} value='View relation' followClick={true} /> +
+ ) : ( + 'Relation' + ); + this.copyableValue = undefined; } this.onContextMenu = this.onContextMenu.bind(this); + if (this.props.markRequiredField && this.props.isRequired && !this.props.value) { + classes.push(styles.required); + } + + this.setState({ ...this.state, content, classes }) } - componentDidUpdate(prevProps) { + async componentDidUpdate(prevProps) { + if ( this.props.value !== prevProps.value ) { + await this.renderCellContent(); + } if (this.props.current) { const node = this.cellRef.current; const { setRelation } = this.props; @@ -58,7 +207,7 @@ export default class BrowserCell extends Component { } shouldComponentUpdate(nextProps, nextState) { - if (nextState.showTooltip !== this.state.showTooltip) { + if (nextState.showTooltip !== this.state.showTooltip || nextState.content !== this.state.content ) { return true; } const shallowVerifyProps = [...new Set(Object.keys(this.props).concat(Object.keys(nextProps)))] @@ -225,139 +374,27 @@ export default class BrowserCell extends Component { }))); } + componentDidMount(){ + this.renderCellContent(); + } + //#endregion render() { - let { type, value, hidden, width, current, onSelect, onEditChange, setCopyableValue, setRelation, onPointerClick, onPointerCmdClick, row, col, field, onEditSelectedRow, readonly, isRequired, markRequiredFieldRow } = this.props; - let content = value; + let { type, value, hidden, width, current, onSelect, onEditChange, setCopyableValue, onPointerCmdClick, row, col, field, onEditSelectedRow, readonly, isRequired, markRequiredFieldRow } = this.props; let isNewRow = row < 0; - this.copyableValue = content; - let classes = [styles.cell, unselectable]; - if (hidden) { - content = value !== undefined || !isNewRow ? '(hidden)' : isRequired ? '(required)' : '(undefined)'; - classes.push(styles.empty); - } else if (value === undefined) { - if (type === 'ACL') { - this.copyableValue = content = 'Public Read + Write'; - } else { - this.copyableValue = content = '(undefined)'; - classes.push(styles.empty); - } - content = isNewRow && isRequired && value === undefined ? '(required)' : content; - } else if (value === null) { - this.copyableValue = content = '(null)'; - classes.push(styles.empty); - } else if (value === '') { - content =  ; - classes.push(styles.empty); - } else if (type === 'Pointer') { - if (value && value.__type) { - const object = new Parse.Object(value.className); - object.id = value.objectId; - value = object; - } - content = onPointerClick ? ( - - ) : ( - value.id - ); - this.copyableValue = value.id; - } - else if (type === 'Array') { - if (value[0] && typeof value[0] === 'object' && value[0].__type === 'Pointer') { - const array = []; - value.map((v, i) => { - if (typeof v !== 'object' || v.__type !== 'Pointer') { - throw new Error('Invalid type found in pointer array'); - } - const object = new Parse.Object(v.className); - object.id = v.objectId; - array.push( - - ); - }); - content =
    - {array.map(a =>
  • {a}
  • )} -
- this.copyableValue = JSON.stringify(value); - if (array.length > 1) { - classes.push(styles.removePadding); - } - } - else { - this.copyableValue = content = JSON.stringify(value); - } - } - else if (type === 'Date') { - if (typeof value === 'object' && value.__type) { - value = new Date(value.iso); - } else if (typeof value === 'string') { - value = new Date(value); - } - this.copyableValue = content = dateStringUTC(value); - } else if (type === 'Boolean') { - this.copyableValue = content = value ? 'True' : 'False'; - } else if (type === 'Object' || type === 'Bytes') { - this.copyableValue = content = JSON.stringify(value); - } else if (type === 'File') { - const fileName = value.url() ? getFileName(value) : 'Uploading\u2026'; - content = ; - this.copyableValue = fileName; - } else if (type === 'ACL') { - let pieces = []; - let json = value.toJSON(); - if (Object.prototype.hasOwnProperty.call(json, '*')) { - if (json['*'].read && json['*'].write) { - pieces.push('Public Read + Write'); - } else if (json['*'].read) { - pieces.push('Public Read'); - } else if (json['*'].write) { - pieces.push('Public Write'); - } - } - for (let role in json) { - if (role !== '*') { - pieces.push(role); - } - } - if (pieces.length === 0) { - pieces.push('Master Key Only'); - } - this.copyableValue = content = pieces.join(', '); - } else if (type === 'GeoPoint') { - this.copyableValue = content = `(${value.latitude}, ${value.longitude})`; - } else if (type === 'Polygon') { - this.copyableValue = content = value.coordinates.map(coord => `(${coord})`) - } else if (type === 'Relation') { - content = setRelation ? ( -
- setRelation(value)} value='View relation' followClick={true} /> -
- ) : ( - 'Relation' - ); - this.copyableValue = undefined; - } - if (current) { + let classes = [...this.state.classes]; + + if ( current ) { classes.push(styles.current); } - if (markRequiredFieldRow === row && isRequired && !value) { classes.push(styles.required); } return readonly ? ( - + - {isNewRow ? '(auto)' : content} + {row < 0 || isNewRow ? '(auto)' : this.state.content} ) : ( @@ -413,13 +450,11 @@ export default class BrowserCell extends Component { if (['ACL', 'Boolean', 'File'].includes(type)) { e.preventDefault(); } - onEditChange(true); - } - }} - onContextMenu={this.onContextMenu} - > - {content} - + }}} + onContextMenu={this.onContextMenu.bind(this)} + > + {this.state.content} + ); } } diff --git a/src/components/BrowserRow/BrowserRow.react.js b/src/components/BrowserRow/BrowserRow.react.js index 25924d651e..5ae4e78ac3 100644 --- a/src/components/BrowserRow/BrowserRow.react.js +++ b/src/components/BrowserRow/BrowserRow.react.js @@ -74,6 +74,7 @@ export default class BrowserRow extends Component { let isRequired = requiredCols.includes(name); return ( -
+
{this.props.uploading ? (
) : label ? ( diff --git a/src/dashboard/Data/Browser/Browser.react.js b/src/dashboard/Data/Browser/Browser.react.js index b6e91ea62c..38669f2471 100644 --- a/src/dashboard/Data/Browser/Browser.react.js +++ b/src/dashboard/Data/Browser/Browser.react.js @@ -29,6 +29,7 @@ import prettyNumber from 'lib/prettyNumber'; import queryFromFilters from 'lib/queryFromFilters'; import React from 'react'; import RemoveColumnDialog from 'dashboard/Data/Browser/RemoveColumnDialog.react'; +import PointerKeyDialog from 'dashboard/Data/Browser/PointerKeyDialog.react'; import SidebarAction from 'components/Sidebar/SidebarAction'; import stringCompare from 'lib/stringCompare'; import styles from 'dashboard/Data/Browser/Browser.scss'; @@ -58,6 +59,7 @@ class Browser extends DashboardView { showExportDialog: false, showAttachRowsDialog: false, showEditRowDialog: false, + showPointerKeyDialog: false, rowsToDelete: null, rowsToExport: null, @@ -141,6 +143,8 @@ class Browser extends DashboardView { this.addEditCloneRows = this.addEditCloneRows.bind(this); this.abortAddRow = this.abortAddRow.bind(this); this.saveNewRow = this.saveNewRow.bind(this); + this.showPointerKeyDialog = this.showPointerKeyDialog.bind(this); + this.onChangeDefaultKey = this.onChangeDefaultKey.bind(this); this.saveEditCloneRow = this.saveEditCloneRow.bind(this); this.abortEditCloneRow = this.abortEditCloneRow.bind(this); this.cancelPendingEditRows = this.cancelPendingEditRows.bind(this); @@ -451,7 +455,7 @@ class Browser extends DashboardView { }); }, error => { - let msg = typeof error === "string" ? error : error.message; + let msg = typeof error === 'string' ? error : error.message; if (msg) { msg = msg[0].toUpperCase() + msg.substr(1); } @@ -471,7 +475,7 @@ class Browser extends DashboardView { this.setState(state); }, error => { - let msg = typeof error === "string" ? error : error.message; + let msg = typeof error === 'string' ? error : error.message; if (msg) { msg = msg[0].toUpperCase() + msg.substr(1); } @@ -1419,6 +1423,10 @@ class Browser extends DashboardView { }); } + showPointerKeyDialog() { + this.setState({ showPointerKeyDialog: true }); + } + closeEditRowDialog() { this.setState({ showEditRowDialog: false, @@ -1435,6 +1443,16 @@ class Browser extends DashboardView { this.setState({showPermissionsDialog: opened}); } + async onChangeDefaultKey (name) { + ColumnPreferences.setPointerDefaultKey( + this.context.currentApp.applicationId, + this.props.params.className, + name + ); + this.setState({ showPointerKeyDialog: false }); + } + + renderContent() { let browser = null; let className = this.props.params.className; @@ -1513,6 +1531,7 @@ class Browser extends DashboardView { onExportSelectedRows={this.showExportSelectedRowsDialog} onSaveNewRow={this.saveNewRow} + onShowPointerKey={this.showPointerKeyDialog} onAbortAddRow={this.abortAddRow} onSaveEditCloneRow={this.saveEditCloneRow} onAbortEditCloneRow={this.abortEditCloneRow} @@ -1552,7 +1571,18 @@ class Browser extends DashboardView { } } let extras = null; - if (this.state.showCreateClassDialog) { + if(this.state.showPointerKeyDialog){ + let currentColumns = this.getClassColumns(className).map(column => column.name); + extras = ( + this.setState({ showPointerKeyDialog: false })} + onConfirm={this.onChangeDefaultKey} /> + ); + } + else if (this.state.showCreateClassDialog) { extras = ( :